fixes that made it work
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
{
|
||||
"outputStyle": "iseri"
|
||||
"outputStyle": "iseri",
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(echo:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1 +1,5 @@
|
||||
# something something
|
||||
build/
|
||||
managed_components/
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
dependencies.lock
|
||||
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
"./")
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include "ble_scan_bsp.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_log.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,89 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
4
components/dashboard_ui/CMakeLists.txt
Normal file
4
components/dashboard_ui/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "dashboard_ui.c"
|
||||
REQUIRES lvgl__lvgl ws_client
|
||||
INCLUDE_DIRS "./")
|
||||
318
components/dashboard_ui/dashboard_ui.c
Normal file
318
components/dashboard_ui/dashboard_ui.c
Normal file
@@ -0,0 +1,318 @@
|
||||
#include "dashboard_ui.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---------- 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);
|
||||
}
|
||||
41
components/dashboard_ui/dashboard_ui.h
Normal file
41
components/dashboard_ui/dashboard_ui.h
Normal file
@@ -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
|
||||
@@ -1,9 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "esp_wifi_bsp.c"
|
||||
|
||||
PRIV_REQUIRES
|
||||
esp_event
|
||||
nvs_flash
|
||||
REQUIRES
|
||||
esp_wifi
|
||||
REQUIRES esp_wifi
|
||||
PRIV_REQUIRES esp_event nvs_flash
|
||||
INCLUDE_DIRS "./")
|
||||
|
||||
@@ -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 <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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 =
|
||||
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 wifi_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
.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 void 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 == 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");
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -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 <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#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
|
||||
8
components/esp_wifi_bsp/wifi_config.h
Normal file
8
components/esp_wifi_bsp/wifi_config.h
Normal file
@@ -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
|
||||
@@ -15,6 +15,8 @@ idf_component_register(
|
||||
codec_board
|
||||
esp_adc
|
||||
REQUIRES
|
||||
esp_lcd
|
||||
esp_adc
|
||||
esp_driver_sdmmc
|
||||
fatfs
|
||||
INCLUDE_DIRS
|
||||
|
||||
@@ -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)
|
||||
|
||||
5
components/user_app/CMakeLists.txt
Normal file
5
components/user_app/CMakeLists.txt
Normal file
@@ -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 "./")
|
||||
134
components/user_app/alert.cpp
Normal file
134
components/user_app/alert.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#include "alert.h"
|
||||
#include "codec_bsp.h"
|
||||
#include "i2c_bsp.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
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;
|
||||
}
|
||||
25
components/user_app/alert.h
Normal file
25
components/user_app/alert.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
192
components/user_app/user_app.cpp
Normal file
192
components/user_app/user_app.cpp
Normal file
@@ -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 <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <string.h>
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
26
components/user_app/user_app.h
Normal file
26
components/user_app/user_app.h
Normal file
@@ -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
|
||||
5
components/ws_client/CMakeLists.txt
Normal file
5
components/ws_client/CMakeLists.txt
Normal file
@@ -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 "./")
|
||||
175
components/ws_client/ws_client.c
Normal file
175
components/ws_client/ws_client.c
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "ws_client.h"
|
||||
#include "esp_websocket_client.h"
|
||||
#include "esp_log.h"
|
||||
#include "cJSON.h"
|
||||
#include <string.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
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;
|
||||
}
|
||||
56
components/ws_client/ws_client.h
Normal file
56
components/ws_client/ws_client.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef WS_CLIENT_H
|
||||
#define WS_CLIENT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
@@ -1,4 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.cpp"
|
||||
|
||||
PRIV_REQUIRES user_app app_bsp port_bsp esp_wifi_bsp
|
||||
INCLUDE_DIRS "./")
|
||||
|
||||
@@ -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: "*"
|
||||
|
||||
@@ -6,17 +6,18 @@
|
||||
|
||||
#include "display_bsp.h"
|
||||
#include "lvgl_bsp.h"
|
||||
#include "esp_wifi_bsp.h"
|
||||
#include "user_app.h"
|
||||
|
||||
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++)
|
||||
{
|
||||
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++;
|
||||
@@ -28,12 +29,57 @@ static void Lvgl_FlushCallback(lv_disp_drv_t *drv, const lv_area_t *area, lv_col
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
/* --- 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");
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include "user_app.h"
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
user_top_init();
|
||||
}
|
||||
52
pi/mock_server.py
Normal file
52
pi/mock_server.py
Normal file
@@ -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())
|
||||
1
pi/requirements.txt
Normal file
1
pi/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
websockets>=12.0
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user