Compare commits
3 Commits
610f776ecf
...
89f75c23ef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f75c23ef | ||
|
|
747b22df28 | ||
|
|
86583f8410 |
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"outputStyle": "iseri",
|
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(echo:*)"
|
"Bash(echo:*)"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"outputStyle": "iseri",
|
||||||
|
"prefersReducedMotion": false
|
||||||
}
|
}
|
||||||
|
|||||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Pi Dashboard
|
||||||
|
|
||||||
|
Raspberry Pi system monitor running on a Waveshare ESP32-S3 RLCD 4.2" board. Connects to a Pi over WebSocket, parses JSON stats, and renders a live dashboard on a 400x300 1-bit monochrome reflective LCD using LVGL.
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
- **Board:** Waveshare ESP32-S3 RLCD 4.2"
|
||||||
|
- **Display:** 400x300 reflective LCD (1-bit monochrome, no backlight needed)
|
||||||
|
- **Sensors:** SHTC3 temp/humidity (I2C 0x70), PCF85063 RTC (I2C 0x51)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Pi (server) ESP32-S3 (client)
|
||||||
|
stats_server.py --WS/JSON--> ws_client --> dashboard_ui (LVGL)
|
||||||
|
+ local sensors (SHTC3)
|
||||||
|
```
|
||||||
|
|
||||||
|
The Pi runs a WebSocket server that pushes system stats (CPU, memory, disk, temperature, network, services) as JSON every 2 seconds. The ESP32 parses the JSON and updates LVGL widgets. A data staleness watchdog forces reconnection if the server goes silent.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit `components/esp_wifi_bsp/wifi_config.h`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define WIFI_SSID "your_ssid"
|
||||||
|
#define WIFI_PASSWORD "your_password"
|
||||||
|
#define WS_SERVER_URI "ws://192.168.x.x:8765"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build and Flash
|
||||||
|
|
||||||
|
Requires ESP-IDF v5.5+.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
idf.py build
|
||||||
|
idf.py flash monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mock Server
|
||||||
|
|
||||||
|
Test without a real Pi using the mock server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r pi/requirements.txt
|
||||||
|
python pi/mock_server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Sends randomized stats on `ws://0.0.0.0:8765` every 2 seconds.
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
#define WIFI_SSID "Novoyuuparosk_H3C"
|
#define WIFI_SSID "Novoyuuparosk_H3C"
|
||||||
#define WIFI_PASSWORD "northwich"
|
#define WIFI_PASSWORD "northwich"
|
||||||
#define WS_SERVER_URI "ws://192.168.1.100:8765"
|
#define WS_SERVER_URI "ws://192.168.2.199:8765"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ static const char *TAG = "alert";
|
|||||||
#define SAMPLE_RATE 24000
|
#define SAMPLE_RATE 24000
|
||||||
#define CHANNELS 2
|
#define CHANNELS 2
|
||||||
#define BITS 16
|
#define BITS 16
|
||||||
#define TONE_FREQ 1000
|
#define TONE_FREQ 762
|
||||||
#define TONE_DURATION_MS 200
|
#define TONE_DURATION_MS 200
|
||||||
|
|
||||||
/* Tone buffer: 200ms at 24kHz * 2ch * 2bytes = 19200 bytes */
|
/* Tone buffer: 200ms at 24kHz * 2ch * 2bytes = 19200 bytes */
|
||||||
@@ -41,7 +41,7 @@ static const int64_t s_cooldown_us[ALERT_TYPE_COUNT] = {
|
|||||||
static const int s_beep_count[ALERT_TYPE_COUNT] = {
|
static const int s_beep_count[ALERT_TYPE_COUNT] = {
|
||||||
0, /* SERVICE_DOWN: triple beep */ /* DISABLED FOR TESTING */
|
0, /* SERVICE_DOWN: triple beep */ /* DISABLED FOR TESTING */
|
||||||
2, /* HIGH_TEMP: double beep */
|
2, /* HIGH_TEMP: double beep */
|
||||||
1, /* WS_DISCONNECT: single beep */
|
0, /* WS_DISCONNECT: single beep */ /* DISABLED FOR TESTING */
|
||||||
1, /* CONNECT_OK: single short beep */
|
1, /* CONNECT_OK: single short beep */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,32 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/timers.h>
|
||||||
|
|
||||||
static const char *TAG = "ws_client";
|
static const char *TAG = "ws_client";
|
||||||
|
|
||||||
|
#define WS_WATCHDOG_PERIOD_MS 5000
|
||||||
|
#define WS_DATA_TIMEOUT_MS 10000
|
||||||
|
|
||||||
static esp_websocket_client_handle_t s_client = NULL;
|
static esp_websocket_client_handle_t s_client = NULL;
|
||||||
static pi_stats_t s_stats = {};
|
static pi_stats_t s_stats = {};
|
||||||
static SemaphoreHandle_t s_stats_mutex = NULL;
|
static SemaphoreHandle_t s_stats_mutex = NULL;
|
||||||
static ws_state_t s_state = WS_STATE_DISCONNECTED;
|
static ws_state_t s_state = WS_STATE_DISCONNECTED;
|
||||||
static ws_data_callback_t s_data_cb = NULL;
|
static ws_data_callback_t s_data_cb = NULL;
|
||||||
static ws_state_callback_t s_state_cb = NULL;
|
static ws_state_callback_t s_state_cb = NULL;
|
||||||
|
static TickType_t s_last_data_tick = 0;
|
||||||
|
static TimerHandle_t s_watchdog_timer = NULL;
|
||||||
|
|
||||||
|
static void watchdog_callback(TimerHandle_t timer)
|
||||||
|
{
|
||||||
|
(void)timer;
|
||||||
|
if (s_state == WS_STATE_CONNECTED &&
|
||||||
|
(xTaskGetTickCount() - s_last_data_tick) > pdMS_TO_TICKS(WS_DATA_TIMEOUT_MS)) {
|
||||||
|
ESP_LOGW(TAG, "WS watchdog: no data for %ds, forcing reconnect",
|
||||||
|
WS_DATA_TIMEOUT_MS / 1000);
|
||||||
|
esp_websocket_client_close(s_client, pdMS_TO_TICKS(2000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void set_state(ws_state_t state)
|
static void set_state(ws_state_t state)
|
||||||
{
|
{
|
||||||
@@ -83,6 +100,8 @@ static void parse_stats_json(const char *data, int len)
|
|||||||
s_stats.valid = true;
|
s_stats.valid = true;
|
||||||
xSemaphoreGive(s_stats_mutex);
|
xSemaphoreGive(s_stats_mutex);
|
||||||
|
|
||||||
|
s_last_data_tick = xTaskGetTickCount();
|
||||||
|
|
||||||
if (s_data_cb) {
|
if (s_data_cb) {
|
||||||
s_data_cb(&s_stats);
|
s_data_cb(&s_stats);
|
||||||
}
|
}
|
||||||
@@ -99,6 +118,7 @@ static void ws_event_handler(void *arg, esp_event_base_t event_base,
|
|||||||
switch (event_id) {
|
switch (event_id) {
|
||||||
case WEBSOCKET_EVENT_CONNECTED:
|
case WEBSOCKET_EVENT_CONNECTED:
|
||||||
ESP_LOGI(TAG, "WebSocket connected");
|
ESP_LOGI(TAG, "WebSocket connected");
|
||||||
|
s_last_data_tick = xTaskGetTickCount();
|
||||||
set_state(WS_STATE_CONNECTED);
|
set_state(WS_STATE_CONNECTED);
|
||||||
break;
|
break;
|
||||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||||
@@ -132,6 +152,9 @@ void ws_client_init(const char *uri)
|
|||||||
s_client = esp_websocket_client_init(&config);
|
s_client = esp_websocket_client_init(&config);
|
||||||
esp_websocket_register_events(s_client, WEBSOCKET_EVENT_ANY, ws_event_handler, NULL);
|
esp_websocket_register_events(s_client, WEBSOCKET_EVENT_ANY, ws_event_handler, NULL);
|
||||||
|
|
||||||
|
s_watchdog_timer = xTimerCreate("ws_wd", pdMS_TO_TICKS(WS_WATCHDOG_PERIOD_MS),
|
||||||
|
pdTRUE, NULL, watchdog_callback);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "WS client initialized: %s", uri);
|
ESP_LOGI(TAG, "WS client initialized: %s", uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,12 +163,18 @@ void ws_client_start(void)
|
|||||||
if (s_client) {
|
if (s_client) {
|
||||||
set_state(WS_STATE_CONNECTING);
|
set_state(WS_STATE_CONNECTING);
|
||||||
esp_websocket_client_start(s_client);
|
esp_websocket_client_start(s_client);
|
||||||
|
if (s_watchdog_timer) {
|
||||||
|
xTimerStart(s_watchdog_timer, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ws_client_stop(void)
|
void ws_client_stop(void)
|
||||||
{
|
{
|
||||||
if (s_client) {
|
if (s_client) {
|
||||||
|
if (s_watchdog_timer) {
|
||||||
|
xTimerStop(s_watchdog_timer, 0);
|
||||||
|
}
|
||||||
esp_websocket_client_stop(s_client);
|
esp_websocket_client_stop(s_client);
|
||||||
set_state(WS_STATE_DISCONNECTED);
|
set_state(WS_STATE_DISCONNECTED);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user