From 610f776ecfd0da29dd71aa06fe32abe724bd8281 Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Sun, 15 Feb 2026 04:15:30 +0900 Subject: [PATCH] fixes that made it work --- .claude/settings.local.json | 7 +- .gitignore | 6 +- .vscode/settings.json | 11 +- CMakeLists.txt | 2 +- components/app_bsp/CMakeLists.txt | 12 +- components/app_bsp/ble_scan_bsp.c | 257 -------------------- components/app_bsp/ble_scan_bsp.h | 31 --- components/app_bsp/esp_wifi_bsp.c | 89 ------- components/app_bsp/esp_wifi_bsp.h | 29 --- components/dashboard_ui/CMakeLists.txt | 4 + components/dashboard_ui/dashboard_ui.c | 318 +++++++++++++++++++++++++ components/dashboard_ui/dashboard_ui.h | 41 ++++ components/esp_wifi_bsp/CMakeLists.txt | 12 +- components/esp_wifi_bsp/esp_wifi_bsp.c | 137 ++++++++--- components/esp_wifi_bsp/esp_wifi_bsp.h | 26 +- components/esp_wifi_bsp/wifi_config.h | 8 + components/port_bsp/CMakeLists.txt | 2 + components/ui_bsp/CMakeLists.txt | 2 +- components/user_app/CMakeLists.txt | 5 + components/user_app/alert.cpp | 134 +++++++++++ components/user_app/alert.h | 25 ++ components/user_app/user_app.cpp | 192 +++++++++++++++ components/user_app/user_app.h | 26 ++ components/ws_client/CMakeLists.txt | 5 + components/ws_client/ws_client.c | 175 ++++++++++++++ components/ws_client/ws_client.h | 56 +++++ main/CMakeLists.txt | 2 +- main/idf_component.yml | 15 +- main/main.cpp | 88 +++++-- main/main_wifi_sta.c | 7 - pi/mock_server.py | 52 ++++ pi/requirements.txt | 1 + sdkconfig.defaults | 10 +- 33 files changed, 1271 insertions(+), 516 deletions(-) delete mode 100644 components/app_bsp/ble_scan_bsp.c delete mode 100644 components/app_bsp/ble_scan_bsp.h delete mode 100644 components/app_bsp/esp_wifi_bsp.c delete mode 100644 components/app_bsp/esp_wifi_bsp.h create mode 100644 components/dashboard_ui/CMakeLists.txt create mode 100644 components/dashboard_ui/dashboard_ui.c create mode 100644 components/dashboard_ui/dashboard_ui.h create mode 100644 components/esp_wifi_bsp/wifi_config.h create mode 100644 components/user_app/CMakeLists.txt create mode 100644 components/user_app/alert.cpp create mode 100644 components/user_app/alert.h create mode 100644 components/user_app/user_app.cpp create mode 100644 components/user_app/user_app.h create mode 100644 components/ws_client/CMakeLists.txt create mode 100644 components/ws_client/ws_client.c create mode 100644 components/ws_client/ws_client.h delete mode 100644 main/main_wifi_sta.c create mode 100644 pi/mock_server.py create mode 100644 pi/requirements.txt diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 729e072..8bce4f3 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,3 +1,8 @@ { - "outputStyle": "iseri" + "outputStyle": "iseri", + "permissions": { + "allow": [ + "Bash(echo:*)" + ] + } } diff --git a/.gitignore b/.gitignore index 050a8cc..9f3fae2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -# something something \ No newline at end of file +build/ +managed_components/ +sdkconfig +sdkconfig.old +dependencies.lock diff --git a/.vscode/settings.json b/.vscode/settings.json index 0819ed8..078e67d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,12 @@ { - "idf.currentSetup": "J:\\esp\\.espressif\\v5.5.2\\esp-idf" + "idf.currentSetup": "J:\\esp\\.espressif\\v5.5.2\\esp-idf", + "idf.flashType": "UART", + "idf.portWin": "COM7", + "idf.openOcdConfigs": [ + "interface/ftdi/esp_ftdi.cfg", + "target/esp32s3.cfg" + ], + "idf.customExtraVars": { + "IDF_TARGET": "esp32s3" + } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e517be..6131db0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,4 +3,4 @@ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(EXTRA_COMPONENT_DIRS components/ExternLib) -project(03_Fac) +project(pi_dashboard) diff --git a/components/app_bsp/CMakeLists.txt b/components/app_bsp/CMakeLists.txt index f0bad68..5dd7d20 100644 --- a/components/app_bsp/CMakeLists.txt +++ b/components/app_bsp/CMakeLists.txt @@ -1,19 +1,9 @@ idf_component_register( SRCS "lvgl_bsp.cpp" - "ble_scan_bsp.c" - "esp_wifi_bsp.c" PRIV_REQUIRES - esp_wifi - esp_event - nvs_flash - ui_bsp - espressif__avi_player - espressif__esp_new_jpeg - port_bsp esp_timer REQUIRES - bt lvgl__lvgl - INCLUDE_DIRS + INCLUDE_DIRS "./") diff --git a/components/app_bsp/ble_scan_bsp.c b/components/app_bsp/ble_scan_bsp.c deleted file mode 100644 index 772ef66..0000000 --- a/components/app_bsp/ble_scan_bsp.c +++ /dev/null @@ -1,257 +0,0 @@ -#include -#include "ble_scan_bsp.h" -#include "freertos/FreeRTOS.h" -#include "esp_log.h" -#include -#include -#include -#include - -#define GATTC_TAG "GATTC_DEMO" -QueueHandle_t ble_queue; -#define REMOTE_SERVICE_UUID 0x00FF -#define REMOTE_NOTIFY_CHAR_UUID 0xFF01 -#define PROFILE_NUM 1 -#define PROFILE_A_APP_ID 0 -#define INVALID_HANDLE 0 - - -/* Declare static functions */ -static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); -static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); -static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); - - -static esp_ble_scan_params_t ble_scan_params = { //ble scan parameter Settings - .scan_type = BLE_SCAN_TYPE_ACTIVE, - .own_addr_type = BLE_ADDR_TYPE_PUBLIC, - .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, - .scan_interval = 0x50, - .scan_window = 0x30, - .scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE //Filter duplicate ads -}; - -struct gattc_profile_inst { - esp_gattc_cb_t gattc_cb; - uint16_t gattc_if; - uint16_t app_id; - uint16_t conn_id; - uint16_t service_start_handle; - uint16_t service_end_handle; - uint16_t char_handle; - esp_bd_addr_t remote_bda; -}; - - -/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ -static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { - [PROFILE_A_APP_ID] = { - .gattc_cb = gattc_profile_event_handler, - .gattc_if = ESP_GATT_IF_NONE, /* Didn't get the initial value, so it's ESP GATT IF NONE */ - }, -}; - - -static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) // This function is called by the esp gattc cb callback function and has the opportunity to be executed -{ - //esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; - switch (event) - { - case ESP_GATTC_REG_EVT: //This callback function, executed by the esp gattc cb callback function, passes in the event parameters - ESP_LOGI(GATTC_TAG, "REG_EVT"); - //esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); //Set scan parameters - //if (scan_ret) - //{ - // ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret); - //} - break; - case ESP_GATTC_CFG_MTU_EVT: //This event is triggered when the GATT client successfully configures the mtu by calling esp ble gattc set mtu() - if (param->cfg_mtu.status != ESP_GATT_OK) - { - ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status); - } - ESP_LOGI(GATTC_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); - break; - default: - break; - } -} -static uint8_t value = 0; -static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) -{ - //uint8_t *adv_name = NULL; - //uint8_t adv_name_len = 0; - switch (event) - { - case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: //This event is triggered after the esp ble gap set scan params() function is successfully invoked to set scan parameters - { - //esp_ble_gap_start_scanning(duration); //Scan time of 30 seconds - break; - } - case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: //This event is triggered after the esp ble gap start scanning() function is successfully invoked to start scanning - { - if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) //To check whether the ESP BT is successfully started, the value must equal to ESP BT STATUS SUCCESS - { - ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status); - break; - } - ESP_LOGI(GATTC_TAG, "scan start success"); - break; - } - case ESP_GAP_BLE_SCAN_RESULT_EVT: //Enter once every scan, not wait for the scan to finish before entering - { - esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; - switch (scan_result->scan_rst.search_evt) // Search event type - { - case ESP_GAP_SEARCH_INQ_RES_EVT: - ////esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); //Bluetooth device address adv data len is the length of the AD data for the scanned device. - //Purpose: Advertising data is the message that the device sends when it broadcasts. This data typically includes the name of the device, service UUID, manufacturer data, and so on. - //The length of the AD data is specified by adv data len Scan response data is additional information returned by the device after receiving a request from the scan device. Typically, it contains more information about the device, such as the full device name or other service information. - //Scenario: When a device (such as a phone or other BLE device) scans an AD pack, it can send a scan request to request more information. The target device will respond to this request and send a scan of the response data - ////ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); - //adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv,ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); //NULL is returned if the name is not scanned - if(xQueueSend(ble_queue,scan_result->scan_rst.bda,0) == pdTRUE) - { - value++; - if(value == 20) - { - value = 0; - esp_ble_gap_stop_scanning(); //Stop scanning - } - } - /*if (adv_name != NULL) - { - //ESP_LOGI(GATTC_TAG, "adv_name: %s", adv_name); - //esp_ble_gap_stop_scanning(); //Stop scanning - //esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true); //Open gattc and start the connection - }*/ - break; - case ESP_GAP_SEARCH_INQ_CMPL_EVT: //The event is used to notify the scanning time of the BLE device when the scanning operation is completed - value = 0; - break; - default: - break; - } - break; - } - case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: //Usually triggered after you call esp ble gap stop scanning() function to stop scanning - if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) - { - ESP_LOGE(GATTC_TAG, "scan stop failed, error status = %x", param->scan_stop_cmpl.status); - break; - } - ESP_LOGI(GATTC_TAG, "stop scan successfully"); - - break; - default: - break; - } -} - -static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) -{ - /* If event is register event, store the gattc_if for each profile */ - if (event == ESP_GATTC_REG_EVT) // Description The event occurred when the GATT client was registered - { - if (param->reg.status == ESP_GATT_OK) // Get running status - { - gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; // param->reg.app id Get the ID, which is the gattc if generated automatically by the system - printf("gattc_if:%d\n",gattc_if); - } - else - { - ESP_LOGI(GATTC_TAG, "reg app failed, app_id %04x, status %d",param->reg.app_id,param->reg.status); // Otherwise, the corresponding error structure is displayed - return; //Returns, does not execute the following - } - } - do - { - int idx; - for (idx = 0; idx < PROFILE_NUM; idx++) - { - if (gattc_if == ESP_GATT_IF_NONE || gattc_if == gl_profile_tab[idx].gattc_if) //if the callback reports gattc if/gatts if as an ESP GATT IF NONE macro, this event does not correspond to any application - { - if (gl_profile_tab[idx].gattc_cb) - { - gl_profile_tab[idx].gattc_cb(event, gattc_if, param); //Call the callback function - } - } - } - } while (0); -} - -static void ble_scan_resources_init(void) -{ - ble_queue = xQueueCreate( 60 , sizeof(uint8_t) * 6); -} -void ble_scan_prepare(void) -{ - ble_scan_resources_init(); - ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); -} -void ble_stack_init(void) -{ - esp_err_t ret; - //ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - ret = esp_bt_controller_init(&bt_cfg); - if (ret) - { - ESP_LOGE(GATTC_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); - return; - } - ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); - if (ret) - { - ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); - return; - } - ret = esp_bluedroid_init(); - if (ret) - { - ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); - return; - } - ret = esp_bluedroid_enable(); - if (ret) - { - ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); - return; - } - ret = esp_ble_gap_register_callback(esp_gap_cb); - if (ret) - { - ESP_LOGE(GATTC_TAG, "%s gap register failed, error code = %x\n", __func__, ret); - return; - } - ret = esp_ble_gattc_register_callback(esp_gattc_cb); - if(ret) - { - ESP_LOGE(GATTC_TAG, "%s gattc register failed, error code = %x\n", __func__, ret); - return; - } - ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); - if (ret) - { - ESP_LOGE(GATTC_TAG, "%s gattc app register failed, error code = %x\n", __func__, ret); - } - esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); - if (local_mtu_ret) - { - ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret); - } -} -void ble_scan_start(void) -{ - esp_ble_gap_set_scan_params(&ble_scan_params);//set scan - esp_ble_gap_start_scanning(3); //Scan time of 3 seconds -} -void ble_stack_deinit(void) -{ - if(value != 0)esp_ble_gap_stop_scanning(); - esp_ble_gattc_app_unregister(gl_profile_tab[0].gattc_if); - esp_bluedroid_disable(); - esp_bluedroid_deinit(); - esp_bt_controller_disable(); - esp_bt_controller_deinit(); -} \ No newline at end of file diff --git a/components/app_bsp/ble_scan_bsp.h b/components/app_bsp/ble_scan_bsp.h deleted file mode 100644 index 5916e86..0000000 --- a/components/app_bsp/ble_scan_bsp.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef BLE_SCAN_BSP_H -#define BLE_SCAN_BSP_H - -#include "esp_bt.h" -#include "esp_gap_ble_api.h" -#include "esp_gattc_api.h" -#include "esp_gatt_defs.h" -#include "esp_bt_main.h" -#include "esp_gatt_common_api.h" -#include "freertos/event_groups.h" -#include "freertos/queue.h" - - -#ifdef __cplusplus -extern "C" { -#endif - -void ble_scan_prepare(void); //初始化之前的释放内存 -void ble_stack_init(void); //ble初始化 -void ble_scan_start(void); //ble扫描开始 -void ble_stack_deinit(void); //ble反初始化 - -extern QueueHandle_t ble_queue; - - - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/components/app_bsp/esp_wifi_bsp.c b/components/app_bsp/esp_wifi_bsp.c deleted file mode 100644 index 636d1fd..0000000 --- a/components/app_bsp/esp_wifi_bsp.c +++ /dev/null @@ -1,89 +0,0 @@ -#include -#include "esp_wifi_bsp.h" -#include "esp_wifi.h" -#include "esp_event.h" -#include "nvs_flash.h" -#include "esp_log.h" - - -#include "string.h" - -EventGroupHandle_t wifi_even_ = NULL; - -esp_bsp_t user_esp_bsp; - -static esp_netif_t *net = NULL; - -static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); -static void example_scan_wifi_task(void *arg); -void espwifi_init(void) -{ - memset(&user_esp_bsp,0,sizeof(esp_bsp_t)); - wifi_even_ = xEventGroupCreate(); - nvs_flash_init(); // Initialize default NVS storage - esp_netif_init(); // Initialize TCP/IP stack - esp_event_loop_create_default(); // Create default event loop - net = esp_netif_create_default_wifi_sta(); // Add TCP/IP stack to the default event loop - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); // Default configuration - esp_wifi_init(&cfg); // Initialize WiFi - esp_event_handler_instance_t Instance_WIFI_IP; - esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &Instance_WIFI_IP); - esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &Instance_WIFI_IP); - wifi_config_t wifi_config = { - .sta = { - .ssid = "PDCN", - .password = "1234567890", - }, - }; - esp_wifi_set_mode(WIFI_MODE_STA); // Set mode to STA - esp_wifi_set_config(WIFI_IF_STA, &wifi_config); // Configure WiFi - esp_wifi_start(); // Start WiFi - xTaskCreatePinnedToCore(example_scan_wifi_task, "example_scan_wifi_task", 3000, NULL, 2, NULL,0); -} - -static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) -{ - if (event_id == WIFI_EVENT_STA_START) - { - xEventGroupSetBits(wifi_even_,0x01); - } - else if (event_id == IP_EVENT_STA_GOT_IP) - { - ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; - char ip[25]; - uint32_t pxip = event->ip_info.ip.addr; - sprintf(ip, "%d.%d.%d.%d", (uint8_t)(pxip), (uint8_t)(pxip >> 8), (uint8_t)(pxip >> 16), (uint8_t)(pxip >> 24)); - ESP_LOGI("wifiSta","%s",ip); - } - else if(event_id == WIFI_EVENT_STA_DISCONNECTED) - { - ESP_LOGD("wifiSta","Disconnected"); - } -} - -void espwifi_deinit(void) -{ - esp_wifi_stop(); - esp_wifi_deinit(); - esp_netif_destroy_default_wifi(net); - esp_event_loop_delete_default(); - //esp_netif_deinit(); - //nvs_flash_deinit(); -} - - - -static void example_scan_wifi_task(void *arg) -{ - uint16_t rec = 0; - EventBits_t even = xEventGroupWaitBits(wifi_even_,0x01,pdTRUE,pdTRUE,pdMS_TO_TICKS(15000)); - if( even & 0x01 ) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_scan_start(NULL,true)); - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_scan_get_ap_num(&rec)); - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_clear_ap_list()); - } - user_esp_bsp.apNum = rec; - xEventGroupSetBits(wifi_even_,0x02); - vTaskDelete(NULL); -} - diff --git a/components/app_bsp/esp_wifi_bsp.h b/components/app_bsp/esp_wifi_bsp.h deleted file mode 100644 index ba82bb3..0000000 --- a/components/app_bsp/esp_wifi_bsp.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ESP_WIFI_BSP_H -#define ESP_WIFI_BSP_H - -#include "freertos/FreeRTOS.h" -#include "freertos/event_groups.h" -extern EventGroupHandle_t wifi_even_; - - -typedef struct -{ - char _ip[25]; - int8_t rssi; - int8_t apNum; -}esp_bsp_t; -extern esp_bsp_t user_esp_bsp; - -#ifdef __cplusplus -extern "C" { -#endif - -void espwifi_init(void); -void espwifi_deinit(void); - - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/components/dashboard_ui/CMakeLists.txt b/components/dashboard_ui/CMakeLists.txt new file mode 100644 index 0000000..cff71a5 --- /dev/null +++ b/components/dashboard_ui/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRCS "dashboard_ui.c" + REQUIRES lvgl__lvgl ws_client + INCLUDE_DIRS "./") diff --git a/components/dashboard_ui/dashboard_ui.c b/components/dashboard_ui/dashboard_ui.c new file mode 100644 index 0000000..c094876 --- /dev/null +++ b/components/dashboard_ui/dashboard_ui.c @@ -0,0 +1,318 @@ +#include "dashboard_ui.h" +#include +#include + +/* ---------- Layout constants ---------- */ +#define SCREEN_W 400 +#define SCREEN_H 300 +#define TOP_BAR_H 24 +#define STATS_H 80 +#define MID_H 140 +#define BOT_H 32 + +/* ---------- Static widget handles ---------- */ + +/* Top bar */ +static lv_obj_t *lbl_ip; +static lv_obj_t *lbl_batt; +static lv_obj_t *lbl_time; +static lv_obj_t *lbl_ws; + +/* Pi stats bars + labels */ +static lv_obj_t *bar_cpu; +static lv_obj_t *lbl_cpu_val; +static lv_obj_t *bar_ram; +static lv_obj_t *lbl_ram_val; +static lv_obj_t *bar_disk; +static lv_obj_t *lbl_disk_val; +static lv_obj_t *lbl_cpu_temp; + +/* Services table */ +static lv_obj_t *tbl_services; + +/* Local sensors */ +static lv_obj_t *lbl_room_temp; +static lv_obj_t *lbl_room_humi; +static lv_obj_t *lbl_local_batt; +static lv_obj_t *lbl_uptime; + +/* Network */ +static lv_obj_t *lbl_net; + +/* ---------- Style ---------- */ +static lv_style_t style_bar_bg; +static lv_style_t style_bar_ind; + +static void init_styles(void) +{ + /* Bar background: white with 1px black border */ + lv_style_init(&style_bar_bg); + lv_style_set_bg_color(&style_bar_bg, lv_color_white()); + lv_style_set_bg_opa(&style_bar_bg, LV_OPA_COVER); + lv_style_set_border_color(&style_bar_bg, lv_color_black()); + lv_style_set_border_width(&style_bar_bg, 1); + lv_style_set_radius(&style_bar_bg, 0); + + /* Bar indicator: solid black fill */ + lv_style_init(&style_bar_ind); + lv_style_set_bg_color(&style_bar_ind, lv_color_black()); + lv_style_set_bg_opa(&style_bar_ind, LV_OPA_COVER); + lv_style_set_radius(&style_bar_ind, 0); +} + +static lv_obj_t *create_bar(lv_obj_t *parent, int x, int y, int w, int h) +{ + lv_obj_t *bar = lv_bar_create(parent); + lv_obj_set_pos(bar, x, y); + lv_obj_set_size(bar, w, h); + lv_bar_set_range(bar, 0, 100); + lv_bar_set_value(bar, 0, LV_ANIM_OFF); + lv_obj_add_style(bar, &style_bar_bg, LV_PART_MAIN); + lv_obj_add_style(bar, &style_bar_ind, LV_PART_INDICATOR); + return bar; +} + +static lv_obj_t *create_label(lv_obj_t *parent, int x, int y, const lv_font_t *font, const char *text) +{ + lv_obj_t *lbl = lv_label_create(parent); + lv_obj_set_pos(lbl, x, y); + lv_obj_set_style_text_font(lbl, font, 0); + lv_obj_set_style_text_color(lbl, lv_color_black(), 0); + lv_label_set_text(lbl, text); + return lbl; +} + +/* ---------- Create UI ---------- */ + +static void create_top_bar(lv_obj_t *parent) +{ + lv_obj_t *bar_cont = lv_obj_create(parent); + lv_obj_set_pos(bar_cont, 0, 0); + lv_obj_set_size(bar_cont, SCREEN_W, TOP_BAR_H); + lv_obj_set_style_bg_color(bar_cont, lv_color_black(), 0); + lv_obj_set_style_bg_opa(bar_cont, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(bar_cont, 0, 0); + lv_obj_set_style_radius(bar_cont, 0, 0); + lv_obj_set_style_pad_all(bar_cont, 2, 0); + lv_obj_clear_flag(bar_cont, LV_OBJ_FLAG_SCROLLABLE); + + lbl_ip = lv_label_create(bar_cont); + lv_obj_set_style_text_color(lbl_ip, lv_color_white(), 0); + lv_obj_set_style_text_font(lbl_ip, &lv_font_montserrat_12, 0); + lv_obj_align(lbl_ip, LV_ALIGN_LEFT_MID, 2, 0); + lv_label_set_text(lbl_ip, "N/A"); + + lbl_batt = lv_label_create(bar_cont); + lv_obj_set_style_text_color(lbl_batt, lv_color_white(), 0); + lv_obj_set_style_text_font(lbl_batt, &lv_font_montserrat_12, 0); + lv_obj_align(lbl_batt, LV_ALIGN_LEFT_MID, 120, 0); + lv_label_set_text(lbl_batt, "Batt:--%"); + + lbl_time = lv_label_create(bar_cont); + lv_obj_set_style_text_color(lbl_time, lv_color_white(), 0); + lv_obj_set_style_text_font(lbl_time, &lv_font_montserrat_12, 0); + lv_obj_align(lbl_time, LV_ALIGN_LEFT_MID, 220, 0); + lv_label_set_text(lbl_time, "--:--"); + + lbl_ws = lv_label_create(bar_cont); + lv_obj_set_style_text_color(lbl_ws, lv_color_white(), 0); + lv_obj_set_style_text_font(lbl_ws, &lv_font_montserrat_12, 0); + lv_obj_align(lbl_ws, LV_ALIGN_RIGHT_MID, -2, 0); + lv_label_set_text(lbl_ws, "WS:---"); +} + +static void create_stats_section(lv_obj_t *parent) +{ + int y0 = TOP_BAR_H + 2; + int col_w = 80; + int bar_w = 50; + int bar_h = 40; + + /* Section header */ + create_label(parent, 4, y0, &lv_font_montserrat_12, "PI SERVER STATS"); + + y0 += 14; + + /* CPU */ + create_label(parent, 10, y0, &lv_font_montserrat_12, "CPU"); + bar_cpu = create_bar(parent, 10, y0 + 14, bar_w, bar_h); + lbl_cpu_val = create_label(parent, 10, y0 + 56, &lv_font_montserrat_14, "--%"); + + /* RAM */ + create_label(parent, 10 + col_w, y0, &lv_font_montserrat_12, "RAM"); + bar_ram = create_bar(parent, 10 + col_w, y0 + 14, bar_w, bar_h); + lbl_ram_val = create_label(parent, 10 + col_w, y0 + 56, &lv_font_montserrat_14, "--%"); + + /* DISK */ + create_label(parent, 10 + col_w * 2, y0, &lv_font_montserrat_12, "DISK"); + bar_disk = create_bar(parent, 10 + col_w * 2, y0 + 14, bar_w, bar_h); + lbl_disk_val = create_label(parent, 10 + col_w * 2, y0 + 56, &lv_font_montserrat_14, "--%"); + + /* CPU TEMP - no bar, just big value */ + create_label(parent, 10 + col_w * 3, y0, &lv_font_montserrat_12, "TEMP"); + lbl_cpu_temp = create_label(parent, 10 + col_w * 3, y0 + 24, &lv_font_montserrat_20, "--C"); +} + +static void create_mid_section(lv_obj_t *parent) +{ + int y0 = TOP_BAR_H + STATS_H + 4; + + /* --- Left column: Services --- */ + create_label(parent, 4, y0, &lv_font_montserrat_12, "SERVICES"); + + tbl_services = lv_table_create(parent); + lv_obj_set_pos(tbl_services, 4, y0 + 14); + lv_obj_set_size(tbl_services, 195, 110); + lv_table_set_col_cnt(tbl_services, 2); + lv_table_set_col_width(tbl_services, 0, 110); + lv_table_set_col_width(tbl_services, 1, 70); + lv_obj_set_style_text_font(tbl_services, &lv_font_montserrat_12, 0); + lv_obj_set_style_border_color(tbl_services, lv_color_black(), 0); + lv_obj_set_style_border_width(tbl_services, 1, 0); + lv_obj_set_style_pad_ver(tbl_services, 2, LV_PART_ITEMS); + lv_obj_set_style_pad_hor(tbl_services, 4, LV_PART_ITEMS); + lv_obj_clear_flag(tbl_services, LV_OBJ_FLAG_SCROLLABLE); + + /* Pre-fill with empty rows */ + for (int i = 0; i < WS_MAX_SERVICES; i++) { + lv_table_set_cell_value(tbl_services, i, 0, "---"); + lv_table_set_cell_value(tbl_services, i, 1, "---"); + } + + /* --- Right column: Local sensors --- */ + int rx = 205; + create_label(parent, rx, y0, &lv_font_montserrat_12, "LOCAL SENSORS"); + + lbl_room_temp = create_label(parent, rx, y0 + 18, &lv_font_montserrat_14, "Room: --.-C"); + lbl_room_humi = create_label(parent, rx, y0 + 38, &lv_font_montserrat_14, "Humi: --%"); + lbl_local_batt = create_label(parent, rx, y0 + 58, &lv_font_montserrat_14, "Batt: --%"); + lbl_uptime = create_label(parent, rx, y0 + 78, &lv_font_montserrat_14, "Uptime: --h"); +} + +static void create_bottom_bar(lv_obj_t *parent) +{ + int y0 = SCREEN_H - BOT_H; + + lv_obj_t *bot_cont = lv_obj_create(parent); + lv_obj_set_pos(bot_cont, 0, y0); + lv_obj_set_size(bot_cont, SCREEN_W, BOT_H); + lv_obj_set_style_bg_color(bot_cont, lv_color_white(), 0); + lv_obj_set_style_bg_opa(bot_cont, LV_OPA_COVER, 0); + lv_obj_set_style_border_color(bot_cont, lv_color_black(), 0); + lv_obj_set_style_border_width(bot_cont, 1, 0); + lv_obj_set_style_border_side(bot_cont, LV_BORDER_SIDE_TOP, 0); + lv_obj_set_style_radius(bot_cont, 0, 0); + lv_obj_set_style_pad_all(bot_cont, 4, 0); + lv_obj_clear_flag(bot_cont, LV_OBJ_FLAG_SCROLLABLE); + + lbl_net = lv_label_create(bot_cont); + lv_obj_set_style_text_font(lbl_net, &lv_font_montserrat_12, 0); + lv_obj_set_style_text_color(lbl_net, lv_color_black(), 0); + lv_obj_align(lbl_net, LV_ALIGN_LEFT_MID, 0, 0); + lv_label_set_text(lbl_net, "NETWORK RX: ---- kbps TX: ---- kbps"); +} + +void dashboard_ui_create(void) +{ + init_styles(); + + lv_obj_t *scr = lv_scr_act(); + lv_obj_set_style_bg_color(scr, lv_color_white(), 0); + lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); + lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE); + + create_top_bar(scr); + create_stats_section(scr); + create_mid_section(scr); + create_bottom_bar(scr); +} + +/* ---------- Update functions ---------- */ + +void dashboard_ui_update_stats(const pi_stats_t *stats) +{ + if (!stats || !stats->valid) return; + + char buf[32]; + + /* Bars */ + lv_bar_set_value(bar_cpu, (int)stats->cpu_pct, LV_ANIM_OFF); + snprintf(buf, sizeof(buf), "%d%%", (int)stats->cpu_pct); + lv_label_set_text(lbl_cpu_val, buf); + + lv_bar_set_value(bar_ram, (int)stats->mem_pct, LV_ANIM_OFF); + snprintf(buf, sizeof(buf), "%d%%", (int)stats->mem_pct); + lv_label_set_text(lbl_ram_val, buf); + + lv_bar_set_value(bar_disk, (int)stats->disk_pct, LV_ANIM_OFF); + snprintf(buf, sizeof(buf), "%d%%", (int)stats->disk_pct); + lv_label_set_text(lbl_disk_val, buf); + + /* CPU temp */ + snprintf(buf, sizeof(buf), "%.0fC", stats->cpu_temp); + lv_label_set_text(lbl_cpu_temp, buf); + + /* Services table */ + for (int i = 0; i < stats->service_count && i < WS_MAX_SERVICES; i++) { + lv_table_set_cell_value(tbl_services, i, 0, stats->services[i].name); + lv_table_set_cell_value(tbl_services, i, 1, + stats->services[i].running ? "[RUN]" : "[STOP]"); + } + /* Clear unused rows */ + for (int i = stats->service_count; i < WS_MAX_SERVICES; i++) { + lv_table_set_cell_value(tbl_services, i, 0, ""); + lv_table_set_cell_value(tbl_services, i, 1, ""); + } + + /* Uptime */ + snprintf(buf, sizeof(buf), "Uptime: %.0fh", stats->uptime_hrs); + lv_label_set_text(lbl_uptime, buf); + + /* Network */ + char net_buf[64]; + snprintf(net_buf, sizeof(net_buf), "NETWORK RX: %.0f kbps TX: %.0f kbps", + stats->net_rx_kbps, stats->net_tx_kbps); + lv_label_set_text(lbl_net, net_buf); +} + +void dashboard_ui_update_local(float temp, float humidity, uint8_t battery) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "Room: %.1fC", temp); + lv_label_set_text(lbl_room_temp, buf); + + snprintf(buf, sizeof(buf), "Humi: %.0f%%", humidity); + lv_label_set_text(lbl_room_humi, buf); + + snprintf(buf, sizeof(buf), "Batt: %d%%", battery); + lv_label_set_text(lbl_local_batt, buf); + + /* Also update top bar battery */ + snprintf(buf, sizeof(buf), "Batt:%d%%", battery); + lv_label_set_text(lbl_batt, buf); +} + +void dashboard_ui_update_time(int h, int m, int s) +{ + char buf[16]; + snprintf(buf, sizeof(buf), "%02d:%02d", h, m); + lv_label_set_text(lbl_time, buf); +} + +void dashboard_ui_update_connection(ws_state_t ws_state, bool wifi_connected, const char *ip_str) +{ + /* IP / WiFi status */ + lv_label_set_text(lbl_ip, ip_str ? ip_str : "N/A"); + + /* WS status */ + const char *ws_str; + switch (ws_state) { + case WS_STATE_CONNECTED: ws_str = "WS:LIVE"; break; + case WS_STATE_CONNECTING: ws_str = "WS:..."; break; + case WS_STATE_DISCONNECTED: ws_str = "WS:OFF"; break; + case WS_STATE_ERROR: ws_str = "WS:ERR"; break; + default: ws_str = "WS:---"; break; + } + lv_label_set_text(lbl_ws, ws_str); +} diff --git a/components/dashboard_ui/dashboard_ui.h b/components/dashboard_ui/dashboard_ui.h new file mode 100644 index 0000000..3771cda --- /dev/null +++ b/components/dashboard_ui/dashboard_ui.h @@ -0,0 +1,41 @@ +#ifndef DASHBOARD_UI_H +#define DASHBOARD_UI_H + +#include "ws_client.h" +#include "lvgl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Create the full dashboard UI. Must be called with LVGL lock held. + */ +void dashboard_ui_create(void); + +/** + * Update Pi server stats display. LVGL lock must be held by caller. + */ +void dashboard_ui_update_stats(const pi_stats_t *stats); + +/** + * Update local sensor readings. LVGL lock must be held by caller. + */ +void dashboard_ui_update_local(float temp, float humidity, uint8_t battery); + +/** + * Update time display. LVGL lock must be held by caller. + */ +void dashboard_ui_update_time(int h, int m, int s); + +/** + * Update connection status indicators. LVGL lock must be held by caller. + * ip_str: IP address string when connected, "Connecting..." during reconnect, "N/A" when disconnected + */ +void dashboard_ui_update_connection(ws_state_t ws_state, bool wifi_connected, const char *ip_str); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/esp_wifi_bsp/CMakeLists.txt b/components/esp_wifi_bsp/CMakeLists.txt index 8449b4b..d4086eb 100644 --- a/components/esp_wifi_bsp/CMakeLists.txt +++ b/components/esp_wifi_bsp/CMakeLists.txt @@ -1,9 +1,5 @@ idf_component_register( - SRCS "esp_wifi_bsp.c" - - PRIV_REQUIRES - esp_event - nvs_flash - REQUIRES - esp_wifi - INCLUDE_DIRS "./") + SRCS "esp_wifi_bsp.c" + REQUIRES esp_wifi + PRIV_REQUIRES esp_event nvs_flash + INCLUDE_DIRS "./") diff --git a/components/esp_wifi_bsp/esp_wifi_bsp.c b/components/esp_wifi_bsp/esp_wifi_bsp.c index 6a4b6b2..0a6763c 100644 --- a/components/esp_wifi_bsp/esp_wifi_bsp.c +++ b/components/esp_wifi_bsp/esp_wifi_bsp.c @@ -1,42 +1,113 @@ #include "esp_wifi_bsp.h" +#include "wifi_config.h" +#include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" +#include "esp_log.h" +#include #include -static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); +static const char *TAG = "wifi_sta"; -void espwifi_Init(void) { - nvs_flash_init(); // Initialize default NVS storage - esp_netif_init(); // Initialize TCP/IP stack - esp_event_loop_create_default(); // Create default event loop - esp_netif_create_default_wifi_sta(); // Attach TCP/IP stack to default event loop - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); // Default config - esp_wifi_init(&cfg); // Initialize Wi-Fi - esp_event_handler_instance_t Instance_WIFI_IP; - esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &Instance_WIFI_IP); - esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &Instance_WIFI_IP); - wifi_config_t wifi_config = { - .sta = - { - .ssid = "K2P", - .password = "1234567890", - }, - }; - esp_wifi_set_mode(WIFI_MODE_STA); // Set mode to STA - esp_wifi_set_config(WIFI_IF_STA, &wifi_config); // Configure Wi-Fi - esp_wifi_start(); // Start Wi-Fi -} +static EventGroupHandle_t s_wifi_event_group; +static char s_ip_str[20] = "N/A"; +static int s_retry_count = 0; +#define MAX_RETRY 10 -static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { - if (event_id == WIFI_EVENT_STA_START) { - esp_wifi_connect(); - } else if (event_id == IP_EVENT_STA_GOT_IP) { - ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; - char ip[25]; - uint32_t pxip = event->ip_info.ip.addr; - sprintf(ip, "%d.%d.%d.%d", (uint8_t) (pxip), (uint8_t) (pxip >> 8), (uint8_t) (pxip >> 16), (uint8_t) (pxip >> 24)); - printf("IP: %s\n", ip); - } else if (event_id == WIFI_EVENT_STA_DISCONNECTED) { - printf("disconnected\n"); +static void wifi_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT) { + if (event_id == WIFI_EVENT_STA_START) { + esp_wifi_connect(); + } else if (event_id == WIFI_EVENT_STA_DISCONNECTED) { + ESP_LOGW(TAG, "Disconnected"); + xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + xEventGroupSetBits(s_wifi_event_group, WIFI_DISCONNECTED_BIT); + strncpy(s_ip_str, "N/A", sizeof(s_ip_str)); + if (s_retry_count < MAX_RETRY) { + vTaskDelay(pdMS_TO_TICKS(2000)); + s_retry_count++; + ESP_LOGI(TAG, "Retry %d/%d", s_retry_count, MAX_RETRY); + esp_wifi_connect(); + } else { + ESP_LOGE(TAG, "Max retries reached, will retry later"); + // Reset counter so background reconnect can try again + vTaskDelay(pdMS_TO_TICKS(10000)); + s_retry_count = 0; + esp_wifi_connect(); + } + } + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + uint32_t ip = event->ip_info.ip.addr; + snprintf(s_ip_str, sizeof(s_ip_str), "%d.%d.%d.%d", + (uint8_t)(ip), (uint8_t)(ip >> 8), + (uint8_t)(ip >> 16), (uint8_t)(ip >> 24)); + ESP_LOGI(TAG, "Connected: %s", s_ip_str); + s_retry_count = 0; + xEventGroupClearBits(s_wifi_event_group, WIFI_DISCONNECTED_BIT); + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } } + +void wifi_sta_init(void) +{ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + nvs_flash_erase(); + nvs_flash_init(); + } + + s_wifi_event_group = xEventGroupCreate(); + + esp_netif_init(); + esp_event_loop_create_default(); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_wifi_init(&cfg); + + esp_event_handler_instance_t inst_any; + esp_event_handler_instance_t inst_got_ip; + esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, + &wifi_event_handler, NULL, &inst_any); + esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, + &wifi_event_handler, NULL, &inst_got_ip); + + wifi_config_t wifi_config = {}; + strncpy((char *)wifi_config.sta.ssid, WIFI_SSID, sizeof(wifi_config.sta.ssid)); + strncpy((char *)wifi_config.sta.password, WIFI_PASSWORD, sizeof(wifi_config.sta.password)); + + esp_wifi_set_mode(WIFI_MODE_STA); + esp_wifi_set_config(WIFI_IF_STA, &wifi_config); + esp_wifi_start(); + + ESP_LOGI(TAG, "WiFi STA init complete, connecting to %s", WIFI_SSID); +} + +bool wifi_sta_wait_connected(uint32_t timeout_ms) +{ + EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT, + pdFALSE, pdTRUE, + pdMS_TO_TICKS(timeout_ms)); + return (bits & WIFI_CONNECTED_BIT) != 0; +} + +bool wifi_sta_is_connected(void) +{ + EventBits_t bits = xEventGroupGetBits(s_wifi_event_group); + return (bits & WIFI_CONNECTED_BIT) != 0; +} + +EventGroupHandle_t wifi_sta_get_event_group(void) +{ + return s_wifi_event_group; +} + +void wifi_sta_get_ip_str(char *buf, size_t len) +{ + strncpy(buf, s_ip_str, len); + buf[len - 1] = '\0'; +} diff --git a/components/esp_wifi_bsp/esp_wifi_bsp.h b/components/esp_wifi_bsp/esp_wifi_bsp.h index 440433f..f447908 100644 --- a/components/esp_wifi_bsp/esp_wifi_bsp.h +++ b/components/esp_wifi_bsp/esp_wifi_bsp.h @@ -1,11 +1,25 @@ #ifndef ESP_WIFI_BSP_H #define ESP_WIFI_BSP_H -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "esp_wifi.h" //WIFI -void espwifi_Init(void); +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#endif -#endif \ No newline at end of file +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_DISCONNECTED_BIT BIT1 + +void wifi_sta_init(void); +bool wifi_sta_wait_connected(uint32_t timeout_ms); +bool wifi_sta_is_connected(void); +EventGroupHandle_t wifi_sta_get_event_group(void); +void wifi_sta_get_ip_str(char *buf, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/esp_wifi_bsp/wifi_config.h b/components/esp_wifi_bsp/wifi_config.h new file mode 100644 index 0000000..6e9529f --- /dev/null +++ b/components/esp_wifi_bsp/wifi_config.h @@ -0,0 +1,8 @@ +#ifndef WIFI_CONFIG_H +#define WIFI_CONFIG_H + +#define WIFI_SSID "Novoyuuparosk_H3C" +#define WIFI_PASSWORD "northwich" +#define WS_SERVER_URI "ws://192.168.1.100:8765" + +#endif diff --git a/components/port_bsp/CMakeLists.txt b/components/port_bsp/CMakeLists.txt index 92d4316..0c52a70 100644 --- a/components/port_bsp/CMakeLists.txt +++ b/components/port_bsp/CMakeLists.txt @@ -15,6 +15,8 @@ idf_component_register( codec_board esp_adc REQUIRES + esp_lcd + esp_adc esp_driver_sdmmc fatfs INCLUDE_DIRS diff --git a/components/ui_bsp/CMakeLists.txt b/components/ui_bsp/CMakeLists.txt index b6fb2a2..5f9f90e 100644 --- a/components/ui_bsp/CMakeLists.txt +++ b/components/ui_bsp/CMakeLists.txt @@ -1,7 +1,7 @@ file(GLOB_RECURSE scrld ./*.c) idf_component_register( SRCS ${scrld} - REQUIRES lvgl + REQUIRES lvgl__lvgl INCLUDE_DIRS "custom" "generated" "generated/guider_customer_fonts") target_compile_definitions(${COMPONENT_LIB} PRIVATE LV_LVGL_H_INCLUDE_SIMPLE) diff --git a/components/user_app/CMakeLists.txt b/components/user_app/CMakeLists.txt new file mode 100644 index 0000000..6770671 --- /dev/null +++ b/components/user_app/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "user_app.cpp" "alert.cpp" + REQUIRES app_bsp + PRIV_REQUIRES esp_wifi_bsp ws_client dashboard_ui port_bsp esp_timer codec_board + INCLUDE_DIRS "./") diff --git a/components/user_app/alert.cpp b/components/user_app/alert.cpp new file mode 100644 index 0000000..d9b70e8 --- /dev/null +++ b/components/user_app/alert.cpp @@ -0,0 +1,134 @@ +#include "alert.h" +#include "codec_bsp.h" +#include "i2c_bsp.h" +#include +#include +#include +#include +#include +#include + +static const char *TAG = "alert"; + +/* Audio params: 24kHz, 16-bit, stereo */ +#define SAMPLE_RATE 24000 +#define CHANNELS 2 +#define BITS 16 +#define TONE_FREQ 1000 +#define TONE_DURATION_MS 200 + +/* Tone buffer: 200ms at 24kHz * 2ch * 2bytes = 19200 bytes */ +#define TONE_SAMPLES (SAMPLE_RATE * TONE_DURATION_MS / 1000) +#define TONE_BUF_SIZE (TONE_SAMPLES * CHANNELS * (BITS / 8)) + +static int16_t *s_tone_buf = NULL; +static bool s_muted = false; +static CodecPort *s_codec = NULL; +static SemaphoreHandle_t s_alert_mutex = NULL; + +/* Per-type cooldown tracking (microseconds) */ +static int64_t s_last_trigger[ALERT_TYPE_COUNT] = {}; + +/* Cooldown periods in microseconds */ +static const int64_t s_cooldown_us[ALERT_TYPE_COUNT] = { + 60 * 1000000LL, /* SERVICE_DOWN: 60s */ + 60 * 1000000LL, /* HIGH_TEMP: 60s */ + 30 * 1000000LL, /* WS_DISCONNECT: 30s */ + 0, /* CONNECT_OK: no cooldown */ +}; + +/* Beep patterns: number of beeps per alert type */ +static const int s_beep_count[ALERT_TYPE_COUNT] = { + 0, /* SERVICE_DOWN: triple beep */ /* DISABLED FOR TESTING */ + 2, /* HIGH_TEMP: double beep */ + 1, /* WS_DISCONNECT: single beep */ + 1, /* CONNECT_OK: single short beep */ +}; + +static void generate_tone_buffer(void) +{ + /* Allocate in PSRAM */ + s_tone_buf = (int16_t *)heap_caps_malloc(TONE_BUF_SIZE, MALLOC_CAP_SPIRAM); + if (!s_tone_buf) { + ESP_LOGE(TAG, "Failed to allocate tone buffer"); + return; + } + + /* Generate 1kHz square wave */ + int half_period = SAMPLE_RATE / (TONE_FREQ * 2); + int16_t amplitude = 16000; /* ~50% of max to avoid clipping */ + + for (int i = 0; i < TONE_SAMPLES; i++) { + int16_t val = ((i / half_period) % 2 == 0) ? amplitude : -amplitude; + /* Stereo: write same value to L and R */ + s_tone_buf[i * 2] = val; + s_tone_buf[i * 2 + 1] = val; + } +} + +void alert_init(void) +{ + s_alert_mutex = xSemaphoreCreateMutex(); + generate_tone_buffer(); + memset(s_last_trigger, 0, sizeof(s_last_trigger)); + ESP_LOGI(TAG, "Alert system initialized"); +} + +void alert_set_codec(void *codec) +{ + s_codec = (CodecPort *)codec; +} + +void alert_trigger(alert_type_t type) +{ + if (type >= ALERT_TYPE_COUNT) return; + if (s_muted) return; + if (!s_tone_buf || !s_codec) return; + + if (xSemaphoreTake(s_alert_mutex, pdMS_TO_TICKS(50)) != pdTRUE) return; + + /* Check cooldown */ + int64_t now = esp_timer_get_time(); + if (s_cooldown_us[type] > 0 && (now - s_last_trigger[type]) < s_cooldown_us[type]) { + xSemaphoreGive(s_alert_mutex); + return; + } + s_last_trigger[type] = now; + + ESP_LOGI(TAG, "Alert triggered: type=%d", type); + + /* Open codec for playback */ + s_codec->CodecPort_SetInfo("es8311", 1, SAMPLE_RATE, CHANNELS, BITS); + s_codec->CodecPort_SetSpeakerVol(70); + + /* Play beep pattern */ + int beeps = s_beep_count[type]; + for (int b = 0; b < beeps; b++) { + /* Play tone in chunks */ + uint8_t *ptr = (uint8_t *)s_tone_buf; + int remaining = TONE_BUF_SIZE; + while (remaining > 0) { + int chunk = (remaining > 512) ? 512 : remaining; + s_codec->CodecPort_PlayWrite(ptr, chunk); + ptr += chunk; + remaining -= chunk; + } + /* Gap between beeps */ + if (b < beeps - 1) { + vTaskDelay(pdMS_TO_TICKS(150)); + } + } + + xSemaphoreGive(s_alert_mutex); +} + +void alert_mute(bool muted) +{ + s_muted = muted; + ESP_LOGI(TAG, "Alerts %s", muted ? "MUTED" : "UNMUTED"); +} + +bool alert_is_muted(void) +{ + return s_muted; +} diff --git a/components/user_app/alert.h b/components/user_app/alert.h new file mode 100644 index 0000000..a503b5b --- /dev/null +++ b/components/user_app/alert.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ALERT_SERVICE_DOWN = 0, + ALERT_HIGH_TEMP, + ALERT_WS_DISCONNECT, + ALERT_CONNECT_OK, + ALERT_TYPE_COUNT, +} alert_type_t; + +void alert_init(void); +void alert_set_codec(void *codec); +void alert_trigger(alert_type_t type); +void alert_mute(bool muted); +bool alert_is_muted(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/user_app/user_app.cpp b/components/user_app/user_app.cpp new file mode 100644 index 0000000..93926c0 --- /dev/null +++ b/components/user_app/user_app.cpp @@ -0,0 +1,192 @@ +#include "user_app.h" +#include "dashboard_ui.h" +#include "ws_client.h" +#include "esp_wifi_bsp.h" +#include "wifi_config.h" +#include "i2c_bsp.h" +#include "i2c_equipment.h" +#include "adc_bsp.h" +#include "button_bsp.h" +#include "codec_bsp.h" +#include "alert.h" +#include "lvgl_bsp.h" + +#include +#include +#include +#include +#include + +static const char *TAG = "user_app"; + +/* Hardware objects */ +static I2cMasterBus *s_i2c = nullptr; +static Shtc3Port *s_shtc3 = nullptr; +static CodecPort *s_codec = nullptr; + +/* Forward declarations */ +static void sensor_task(void *arg); +static void button_task(void *arg); +static void ws_data_cb(const pi_stats_t *stats); +static void ws_state_cb(ws_state_t state); + +void UserApp_AppInit(void) +{ + ESP_LOGI(TAG, "Initializing hardware..."); + + /* I2C bus (SCL=14, SDA=13, port 0) */ + s_i2c = new I2cMasterBus(14, 13, 0); + + /* SHTC3 temperature/humidity sensor */ + s_shtc3 = new Shtc3Port(*s_i2c); + ESP_LOGI(TAG, "SHTC3 ID: 0x%04X", s_shtc3->Shtc3_GetShtc3Id()); + + /* RTC */ + Rtc_Setup(s_i2c, 0x51); + + /* ADC for battery */ + Adc_PortInit(); + + /* Buttons */ + Custom_ButtonInit(); + + /* Codec */ + s_codec = new CodecPort(*s_i2c, "S3_RLCD_4_2"); + + /* Alert system */ + alert_init(); + alert_set_codec(s_codec); + + /* WebSocket client init (not started yet) */ + ws_client_init(WS_SERVER_URI); + ws_client_set_data_callback(ws_data_cb); + ws_client_set_state_callback(ws_state_cb); + + ESP_LOGI(TAG, "Hardware init complete"); +} + +void UserApp_UiInit(void) +{ + dashboard_ui_create(); + ESP_LOGI(TAG, "Dashboard UI created"); +} + +void UserApp_TaskInit(void) +{ + ESP_LOGI(TAG, "Starting background tasks..."); + + /* Start WebSocket client */ + ws_client_start(); + + /* Sensor polling task - Core 1, 4KB stack */ + xTaskCreatePinnedToCore(sensor_task, "sensor", 4 * 1024, NULL, 3, NULL, 1); + + /* Button handling task - Core 1 */ + xTaskCreatePinnedToCore(button_task, "button", 2 * 1024, NULL, 2, NULL, 1); + + ESP_LOGI(TAG, "All tasks started"); +} + +/* ---------- WebSocket callbacks ---------- */ + +static void ws_data_cb(const pi_stats_t *stats) +{ + /* Check alert conditions */ + if (stats->cpu_temp > 80.0f) { + alert_trigger(ALERT_HIGH_TEMP); + } + + for (int i = 0; i < stats->service_count; i++) { + if (!stats->services[i].running) { + alert_trigger(ALERT_SERVICE_DOWN); + break; + } + } + + /* Update UI under LVGL lock */ + if (Lvgl_lock(100)) { + dashboard_ui_update_stats(stats); + + char ip_buf[20]; + wifi_sta_get_ip_str(ip_buf, sizeof(ip_buf)); + dashboard_ui_update_connection( + ws_client_get_state(), + wifi_sta_is_connected(), + ip_buf + ); + + Lvgl_unlock(); + } +} + +static void ws_state_cb(ws_state_t state) +{ + ESP_LOGI(TAG, "WS state changed: %d", state); + + if (state == WS_STATE_DISCONNECTED || state == WS_STATE_ERROR) { + alert_trigger(ALERT_WS_DISCONNECT); + } else if (state == WS_STATE_CONNECTED) { + alert_trigger(ALERT_CONNECT_OK); + } + + /* Update connection indicator */ + if (Lvgl_lock(100)) { + char ip_buf[20]; + wifi_sta_get_ip_str(ip_buf, sizeof(ip_buf)); + dashboard_ui_update_connection(state, wifi_sta_is_connected(), ip_buf); + Lvgl_unlock(); + } +} + +/* ---------- Sensor task ---------- */ + +static void sensor_task(void *arg) +{ + float temp = 0, humidity = 0; + rtcTimeStruct_t rtc_time = {}; + + for (;;) { + /* Read SHTC3 */ + s_shtc3->Shtc3_Wakeup(); + vTaskDelay(pdMS_TO_TICKS(20)); + s_shtc3->Shtc3_ReadTempHumi(&temp, &humidity); + s_shtc3->Shtc3_Sleep(); + + /* Read RTC */ + Rtc_GetTime(&rtc_time); + + /* Read battery */ + uint8_t batt = Adc_GetBatteryLevel(); + + /* Update UI under LVGL lock */ + if (Lvgl_lock(100)) { + dashboard_ui_update_local(temp, humidity, batt); + dashboard_ui_update_time(rtc_time.hour, rtc_time.minute, rtc_time.second); + Lvgl_unlock(); + } + + vTaskDelay(pdMS_TO_TICKS(5000)); + } +} + +/* ---------- Button task ---------- */ + +static void button_task(void *arg) +{ + for (;;) { + /* Wait for GP18 button event (single click = bit 0) */ + EventBits_t bits = xEventGroupWaitBits( + GP18ButtonGroups, + set_bit_button(0), /* single press bit */ + pdTRUE, /* clear on exit */ + pdFALSE, /* any bit */ + pdMS_TO_TICKS(500) + ); + + if (bits & set_bit_button(0)) { + bool muted = !alert_is_muted(); + alert_mute(muted); + ESP_LOGI(TAG, "GP18 pressed: alerts %s", muted ? "muted" : "unmuted"); + } + } +} diff --git a/components/user_app/user_app.h b/components/user_app/user_app.h new file mode 100644 index 0000000..59fbfaa --- /dev/null +++ b/components/user_app/user_app.h @@ -0,0 +1,26 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialize hardware peripherals (I2C, sensors, ADC, buttons, codec, alerts). + * Called early in boot, before UI. + */ +void UserApp_AppInit(void); + +/** + * Create the dashboard UI. Must be called with LVGL lock held. + */ +void UserApp_UiInit(void); + +/** + * Start background tasks (WS client, sensor polling, button handling). + * Called after UI is created. + */ +void UserApp_TaskInit(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/ws_client/CMakeLists.txt b/components/ws_client/CMakeLists.txt new file mode 100644 index 0000000..8736c6d --- /dev/null +++ b/components/ws_client/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "ws_client.c" + REQUIRES espressif__esp_websocket_client + PRIV_REQUIRES esp_event json esp_wifi_bsp + INCLUDE_DIRS "./") diff --git a/components/ws_client/ws_client.c b/components/ws_client/ws_client.c new file mode 100644 index 0000000..2532da6 --- /dev/null +++ b/components/ws_client/ws_client.c @@ -0,0 +1,175 @@ +#include "ws_client.h" +#include "esp_websocket_client.h" +#include "esp_log.h" +#include "cJSON.h" +#include +#include +#include + +static const char *TAG = "ws_client"; + +static esp_websocket_client_handle_t s_client = NULL; +static pi_stats_t s_stats = {}; +static SemaphoreHandle_t s_stats_mutex = NULL; +static ws_state_t s_state = WS_STATE_DISCONNECTED; +static ws_data_callback_t s_data_cb = NULL; +static ws_state_callback_t s_state_cb = NULL; + +static void set_state(ws_state_t state) +{ + s_state = state; + if (s_state_cb) { + s_state_cb(state); + } +} + +static void parse_stats_json(const char *data, int len) +{ + cJSON *root = cJSON_ParseWithLength(data, len); + if (!root) { + ESP_LOGW(TAG, "JSON parse failed"); + return; + } + + if (xSemaphoreTake(s_stats_mutex, pdMS_TO_TICKS(100)) == pdTRUE) { + cJSON *item; + + item = cJSON_GetObjectItem(root, "cpu_pct"); + if (item) s_stats.cpu_pct = (float)item->valuedouble; + + item = cJSON_GetObjectItem(root, "mem_pct"); + if (item) s_stats.mem_pct = (float)item->valuedouble; + + item = cJSON_GetObjectItem(root, "mem_used_mb"); + if (item) s_stats.mem_used_mb = (uint32_t)item->valuedouble; + + item = cJSON_GetObjectItem(root, "disk_pct"); + if (item) s_stats.disk_pct = (float)item->valuedouble; + + item = cJSON_GetObjectItem(root, "cpu_temp"); + if (item) s_stats.cpu_temp = (float)item->valuedouble; + + item = cJSON_GetObjectItem(root, "uptime_hrs"); + if (item) s_stats.uptime_hrs = (float)item->valuedouble; + + item = cJSON_GetObjectItem(root, "net_rx_kbps"); + if (item) s_stats.net_rx_kbps = (float)item->valuedouble; + + item = cJSON_GetObjectItem(root, "net_tx_kbps"); + if (item) s_stats.net_tx_kbps = (float)item->valuedouble; + + item = cJSON_GetObjectItem(root, "timestamp"); + if (item) s_stats.last_update = (uint32_t)item->valuedouble; + + cJSON *services = cJSON_GetObjectItem(root, "services"); + if (cJSON_IsArray(services)) { + int count = cJSON_GetArraySize(services); + if (count > WS_MAX_SERVICES) count = WS_MAX_SERVICES; + s_stats.service_count = count; + for (int i = 0; i < count; i++) { + cJSON *svc = cJSON_GetArrayItem(services, i); + cJSON *name = cJSON_GetObjectItem(svc, "name"); + cJSON *status = cJSON_GetObjectItem(svc, "status"); + if (name && name->valuestring) { + strncpy(s_stats.services[i].name, name->valuestring, WS_SERVICE_NAME_LEN - 1); + s_stats.services[i].name[WS_SERVICE_NAME_LEN - 1] = '\0'; + } + if (status && status->valuestring) { + s_stats.services[i].running = (strcmp(status->valuestring, "running") == 0); + } + } + } + + s_stats.valid = true; + xSemaphoreGive(s_stats_mutex); + + if (s_data_cb) { + s_data_cb(&s_stats); + } + } + + cJSON_Delete(root); +} + +static void ws_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; + + switch (event_id) { + case WEBSOCKET_EVENT_CONNECTED: + ESP_LOGI(TAG, "WebSocket connected"); + set_state(WS_STATE_CONNECTED); + break; + case WEBSOCKET_EVENT_DISCONNECTED: + ESP_LOGW(TAG, "WebSocket disconnected"); + set_state(WS_STATE_DISCONNECTED); + break; + case WEBSOCKET_EVENT_DATA: + if (data->op_code == 0x01 && data->data_len > 0) { // text frame + ESP_LOGD(TAG, "Received %d bytes", data->data_len); + parse_stats_json(data->data_ptr, data->data_len); + } + break; + case WEBSOCKET_EVENT_ERROR: + ESP_LOGE(TAG, "WebSocket error"); + set_state(WS_STATE_ERROR); + break; + default: + break; + } +} + +void ws_client_init(const char *uri) +{ + s_stats_mutex = xSemaphoreCreateMutex(); + + esp_websocket_client_config_t config = {}; + config.uri = uri; + config.reconnect_timeout_ms = 5000; + config.buffer_size = 2048; + + s_client = esp_websocket_client_init(&config); + esp_websocket_register_events(s_client, WEBSOCKET_EVENT_ANY, ws_event_handler, NULL); + + ESP_LOGI(TAG, "WS client initialized: %s", uri); +} + +void ws_client_start(void) +{ + if (s_client) { + set_state(WS_STATE_CONNECTING); + esp_websocket_client_start(s_client); + } +} + +void ws_client_stop(void) +{ + if (s_client) { + esp_websocket_client_stop(s_client); + set_state(WS_STATE_DISCONNECTED); + } +} + +ws_state_t ws_client_get_state(void) +{ + return s_state; +} + +void ws_client_get_stats(pi_stats_t *out) +{ + if (xSemaphoreTake(s_stats_mutex, pdMS_TO_TICKS(100)) == pdTRUE) { + memcpy(out, &s_stats, sizeof(pi_stats_t)); + xSemaphoreGive(s_stats_mutex); + } +} + +void ws_client_set_data_callback(ws_data_callback_t cb) +{ + s_data_cb = cb; +} + +void ws_client_set_state_callback(ws_state_callback_t cb) +{ + s_state_cb = cb; +} diff --git a/components/ws_client/ws_client.h b/components/ws_client/ws_client.h new file mode 100644 index 0000000..76271ec --- /dev/null +++ b/components/ws_client/ws_client.h @@ -0,0 +1,56 @@ +#ifndef WS_CLIENT_H +#define WS_CLIENT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WS_MAX_SERVICES 8 +#define WS_SERVICE_NAME_LEN 16 + +typedef enum { + WS_STATE_DISCONNECTED = 0, + WS_STATE_CONNECTING, + WS_STATE_CONNECTED, + WS_STATE_ERROR, +} ws_state_t; + +typedef struct { + char name[WS_SERVICE_NAME_LEN]; + bool running; +} ws_service_t; + +typedef struct { + float cpu_pct; + float mem_pct; + uint32_t mem_used_mb; + float disk_pct; + float cpu_temp; + float uptime_hrs; + float net_rx_kbps; + float net_tx_kbps; + ws_service_t services[WS_MAX_SERVICES]; + uint8_t service_count; + uint32_t last_update; // timestamp from server + bool valid; // set true after first successful parse +} pi_stats_t; + +typedef void (*ws_data_callback_t)(const pi_stats_t *stats); +typedef void (*ws_state_callback_t)(ws_state_t state); + +void ws_client_init(const char *uri); +void ws_client_start(void); +void ws_client_stop(void); +ws_state_t ws_client_get_state(void); +void ws_client_get_stats(pi_stats_t *out); +void ws_client_set_data_callback(ws_data_callback_t cb); +void ws_client_set_state_callback(ws_state_callback_t cb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ef76995..2c51387 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( SRCS "main.cpp" - + PRIV_REQUIRES user_app app_bsp port_bsp esp_wifi_bsp INCLUDE_DIRS "./") diff --git a/main/idf_component.yml b/main/idf_component.yml index d648e55..761b5fe 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -1,19 +1,6 @@ ## IDF Component Manager Manifest File dependencies: - ## Required IDF version idf: version: '>=4.1.0' - # # Put list of dependencies here - # # For components maintained by Espressif: - # component: "~1.0.0" - # # For 3rd party components: - # username/component: ">=1.0.0,<2.0.0" - # username2/component2: - # version: "~1.0.0" - # # For transient dependencies `public` flag can be set. - # # `public` flag doesn't have an effect dependencies of the `main` component. - # # All dependencies of `main` are public by default. - # public: true lvgl/lvgl: ^8.4.0 - espressif/avi_player: ^1.0.0 - espressif/esp_new_jpeg: ^0.6.1 + espressif/esp_websocket_client: "*" diff --git a/main/main.cpp b/main/main.cpp index a204306..202c328 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -6,34 +6,80 @@ #include "display_bsp.h" #include "lvgl_bsp.h" +#include "esp_wifi_bsp.h" #include "user_app.h" -DisplayPort RlcdPort(12,11,5,40,41,400,300); +static const char *TAG = "main"; + +DisplayPort RlcdPort(12, 11, 5, 40, 41, 400, 300); static void Lvgl_FlushCallback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) { - uint16_t *buffer = (uint16_t *)color_map; - for(int y = area->y1; y <= area->y2; y++) - { - for(int x = area->x1; x <= area->x2; x++) - { - uint8_t color = (*buffer < 0x7fff) ? ColorBlack : ColorWhite; - RlcdPort.RLCD_SetPixel(x, y, color); - buffer++; - } - } - RlcdPort.RLCD_Display(); - lv_disp_flush_ready(drv); + uint16_t *buffer = (uint16_t *)color_map; + for (int y = area->y1; y <= area->y2; y++) { + for (int x = area->x1; x <= area->x2; x++) { + uint8_t color = (*buffer < 0x7fff) ? ColorBlack : ColorWhite; + RlcdPort.RLCD_SetPixel(x, y, color); + buffer++; + } + } + RlcdPort.RLCD_Display(); + lv_disp_flush_ready(drv); } extern "C" void app_main(void) { - UserApp_AppInit(); - RlcdPort.RLCD_Init(); - Lvgl_PortInit(400,300,Lvgl_FlushCallback); - if(Lvgl_lock(-1)) { - UserApp_UiInit(); - Lvgl_unlock(); - } - UserApp_TaskInit(); + /* --- Hardware init (non-WiFi) --- */ + UserApp_AppInit(); + + /* --- Display + LVGL --- */ + RlcdPort.RLCD_Init(); + Lvgl_PortInit(400, 300, Lvgl_FlushCallback); + + /* --- Boot screen: show WiFi connection status --- */ + lv_obj_t *boot_label = NULL; + if (Lvgl_lock(-1)) { + boot_label = lv_label_create(lv_scr_act()); + lv_obj_set_style_text_font(boot_label, &lv_font_montserrat_16, 0); + lv_obj_set_style_text_color(boot_label, lv_color_black(), 0); + lv_label_set_text(boot_label, "Connecting to WiFi..."); + lv_obj_center(boot_label); + Lvgl_unlock(); + } + + /* --- Start WiFi --- */ + wifi_sta_init(); + ESP_LOGI(TAG, "Waiting for WiFi connection (15s timeout)..."); + bool connected = wifi_sta_wait_connected(15000); + + /* --- Update boot screen with result --- */ + if (Lvgl_lock(-1)) { + if (connected) { + char ip_buf[20]; + wifi_sta_get_ip_str(ip_buf, sizeof(ip_buf)); + char msg[48]; + snprintf(msg, sizeof(msg), "Connected: %s", ip_buf); + lv_label_set_text(boot_label, msg); + ESP_LOGI(TAG, "WiFi connected: %s", ip_buf); + } else { + lv_label_set_text(boot_label, "WiFi Failed - Retrying..."); + ESP_LOGW(TAG, "WiFi connection timed out, continuing..."); + } + Lvgl_unlock(); + } + + /* Brief pause so the user can read the boot status */ + vTaskDelay(pdMS_TO_TICKS(1000)); + + /* --- Transition to full dashboard UI --- */ + if (Lvgl_lock(-1)) { + lv_obj_del(boot_label); + UserApp_UiInit(); + Lvgl_unlock(); + } + + /* --- Start background tasks --- */ + UserApp_TaskInit(); + + ESP_LOGI(TAG, "Pi Dashboard running"); } diff --git a/main/main_wifi_sta.c b/main/main_wifi_sta.c deleted file mode 100644 index f60bbca..0000000 --- a/main/main_wifi_sta.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "user_app.h" - -void app_main(void) -{ - user_top_init(); -} diff --git a/pi/mock_server.py b/pi/mock_server.py new file mode 100644 index 0000000..4bf2450 --- /dev/null +++ b/pi/mock_server.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Mock WebSocket server that sends randomized Pi stats every 2 seconds.""" + +import asyncio +import json +import random +import time + +import websockets + + +def generate_stats(): + services = [ + {"name": "docker", "status": random.choice(["running", "running", "running", "stopped"])}, + {"name": "pihole", "status": random.choice(["running", "running", "running", "stopped"])}, + {"name": "nginx", "status": random.choice(["running", "running", "stopped"])}, + {"name": "sshd", "status": "running"}, + ] + return { + "cpu_pct": round(random.uniform(5, 95), 1), + "mem_pct": round(random.uniform(30, 85), 1), + "mem_used_mb": random.randint(512, 3200), + "disk_pct": round(random.uniform(20, 80), 1), + "cpu_temp": round(random.uniform(35, 78), 1), + "uptime_hrs": round(random.uniform(1, 2000), 1), + "net_rx_kbps": round(random.uniform(0, 5000), 1), + "net_tx_kbps": round(random.uniform(0, 2000), 1), + "services": services, + "timestamp": int(time.time()), + } + + +async def handler(websocket): + addr = websocket.remote_address + print(f"Client connected: {addr}") + try: + while True: + stats = generate_stats() + await websocket.send(json.dumps(stats)) + await asyncio.sleep(2) + except websockets.ConnectionClosed: + print(f"Client disconnected: {addr}") + + +async def main(): + print("Mock Pi stats server starting on ws://0.0.0.0:8765") + async with websockets.serve(handler, "0.0.0.0", 8765): + await asyncio.Future() # run forever + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/pi/requirements.txt b/pi/requirements.txt new file mode 100644 index 0000000..31b5e2f --- /dev/null +++ b/pi/requirements.txt @@ -0,0 +1 @@ +websockets>=12.0 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 58177f6..020ff49 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -5,10 +5,7 @@ CONFIG_IDF_TARGET="esp32s3" CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_BT_ENABLED=y -CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n -CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y -CONFIG_BT_ABORT_WHEN_ALLOCATION_FAILS=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y @@ -20,3 +17,8 @@ CONFIG_LV_DISP_DEF_REFR_PERIOD=1 CONFIG_LV_INDEV_DEF_READ_PERIOD=50 CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_)}" CONFIG_LV_USE_SNAPSHOT=n +CONFIG_LV_FONT_MONTSERRAT_12=y +CONFIG_LV_FONT_MONTSERRAT_14=y +CONFIG_LV_FONT_MONTSERRAT_16=y +CONFIG_LV_FONT_MONTSERRAT_20=y +CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y