fixes that made it work

This commit is contained in:
Mikkeli Matlock
2026-02-15 04:15:30 +09:00
parent 19db125619
commit 610f776ecf
33 changed files with 1271 additions and 516 deletions

View File

@@ -1,3 +1,8 @@
{ {
"outputStyle": "iseri" "outputStyle": "iseri",
"permissions": {
"allow": [
"Bash(echo:*)"
]
}
} }

6
.gitignore vendored
View File

@@ -1 +1,5 @@
# something something build/
managed_components/
sdkconfig
sdkconfig.old
dependencies.lock

11
.vscode/settings.json vendored
View File

@@ -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"
}
} }

View File

@@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS components/ExternLib) set(EXTRA_COMPONENT_DIRS components/ExternLib)
project(03_Fac) project(pi_dashboard)

View File

@@ -1,19 +1,9 @@
idf_component_register( idf_component_register(
SRCS SRCS
"lvgl_bsp.cpp" "lvgl_bsp.cpp"
"ble_scan_bsp.c"
"esp_wifi_bsp.c"
PRIV_REQUIRES PRIV_REQUIRES
esp_wifi
esp_event
nvs_flash
ui_bsp
espressif__avi_player
espressif__esp_new_jpeg
port_bsp
esp_timer esp_timer
REQUIRES REQUIRES
bt
lvgl__lvgl lvgl__lvgl
INCLUDE_DIRS INCLUDE_DIRS
"./") "./")

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -0,0 +1,4 @@
idf_component_register(
SRCS "dashboard_ui.c"
REQUIRES lvgl__lvgl ws_client
INCLUDE_DIRS "./")

View 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);
}

View 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

View File

@@ -1,9 +1,5 @@
idf_component_register( idf_component_register(
SRCS "esp_wifi_bsp.c" SRCS "esp_wifi_bsp.c"
REQUIRES esp_wifi
PRIV_REQUIRES PRIV_REQUIRES esp_event nvs_flash
esp_event
nvs_flash
REQUIRES
esp_wifi
INCLUDE_DIRS "./") INCLUDE_DIRS "./")

View File

@@ -1,42 +1,113 @@
#include "esp_wifi_bsp.h" #include "esp_wifi_bsp.h"
#include "wifi_config.h"
#include "esp_wifi.h"
#include "esp_event.h" #include "esp_event.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "esp_log.h"
#include <string.h>
#include <stdio.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) { static EventGroupHandle_t s_wifi_event_group;
nvs_flash_init(); // Initialize default NVS storage static char s_ip_str[20] = "N/A";
esp_netif_init(); // Initialize TCP/IP stack static int s_retry_count = 0;
esp_event_loop_create_default(); // Create default event loop #define MAX_RETRY 10
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 static void wifi_event_handler(void *arg, esp_event_base_t event_base,
esp_wifi_init(&cfg); // Initialize Wi-Fi int32_t event_id, void *event_data)
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", if (event_base == WIFI_EVENT) {
.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_id == WIFI_EVENT_STA_START) { if (event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect(); 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) { } 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';
}

View File

@@ -1,11 +1,25 @@
#ifndef ESP_WIFI_BSP_H #ifndef ESP_WIFI_BSP_H
#define 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 #endif

View 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

View File

@@ -15,6 +15,8 @@ idf_component_register(
codec_board codec_board
esp_adc esp_adc
REQUIRES REQUIRES
esp_lcd
esp_adc
esp_driver_sdmmc esp_driver_sdmmc
fatfs fatfs
INCLUDE_DIRS INCLUDE_DIRS

View File

@@ -1,7 +1,7 @@
file(GLOB_RECURSE scrld ./*.c) file(GLOB_RECURSE scrld ./*.c)
idf_component_register( idf_component_register(
SRCS ${scrld} SRCS ${scrld}
REQUIRES lvgl REQUIRES lvgl__lvgl
INCLUDE_DIRS "custom" "generated" "generated/guider_customer_fonts") INCLUDE_DIRS "custom" "generated" "generated/guider_customer_fonts")
target_compile_definitions(${COMPONENT_LIB} PRIVATE LV_LVGL_H_INCLUDE_SIMPLE) target_compile_definitions(${COMPONENT_LIB} PRIVATE LV_LVGL_H_INCLUDE_SIMPLE)

View 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 "./")

View 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;
}

View 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

View 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");
}
}
}

View 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

View 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 "./")

View 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;
}

View 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

View File

@@ -1,4 +1,4 @@
idf_component_register( idf_component_register(
SRCS "main.cpp" SRCS "main.cpp"
PRIV_REQUIRES user_app app_bsp port_bsp esp_wifi_bsp
INCLUDE_DIRS "./") INCLUDE_DIRS "./")

View File

@@ -1,19 +1,6 @@
## IDF Component Manager Manifest File ## IDF Component Manager Manifest File
dependencies: dependencies:
## Required IDF version
idf: idf:
version: '>=4.1.0' 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 lvgl/lvgl: ^8.4.0
espressif/avi_player: ^1.0.0 espressif/esp_websocket_client: "*"
espressif/esp_new_jpeg: ^0.6.1

View File

@@ -6,17 +6,18 @@
#include "display_bsp.h" #include "display_bsp.h"
#include "lvgl_bsp.h" #include "lvgl_bsp.h"
#include "esp_wifi_bsp.h"
#include "user_app.h" #include "user_app.h"
static const char *TAG = "main";
DisplayPort RlcdPort(12, 11, 5, 40, 41, 400, 300); 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) 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; uint16_t *buffer = (uint16_t *)color_map;
for(int y = area->y1; y <= area->y2; y++) for (int y = area->y1; y <= area->y2; y++) {
{ for (int x = area->x1; x <= area->x2; x++) {
for(int x = area->x1; x <= area->x2; x++)
{
uint8_t color = (*buffer < 0x7fff) ? ColorBlack : ColorWhite; uint8_t color = (*buffer < 0x7fff) ? ColorBlack : ColorWhite;
RlcdPort.RLCD_SetPixel(x, y, color); RlcdPort.RLCD_SetPixel(x, y, color);
buffer++; 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) extern "C" void app_main(void)
{ {
/* --- Hardware init (non-WiFi) --- */
UserApp_AppInit(); UserApp_AppInit();
/* --- Display + LVGL --- */
RlcdPort.RLCD_Init(); RlcdPort.RLCD_Init();
Lvgl_PortInit(400, 300, Lvgl_FlushCallback); Lvgl_PortInit(400, 300, Lvgl_FlushCallback);
/* --- Boot screen: show WiFi connection status --- */
lv_obj_t *boot_label = NULL;
if (Lvgl_lock(-1)) { 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(); UserApp_UiInit();
Lvgl_unlock(); Lvgl_unlock();
} }
/* --- Start background tasks --- */
UserApp_TaskInit(); UserApp_TaskInit();
ESP_LOGI(TAG, "Pi Dashboard running");
} }

View File

@@ -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
View 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
View File

@@ -0,0 +1 @@
websockets>=12.0

View File

@@ -5,10 +5,7 @@ CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_BT_ENABLED=y CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
CONFIG_BT_ABORT_WHEN_ALLOCATION_FAILS=y
CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
CONFIG_SPIRAM=y CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_OCT=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_INDEV_DEF_READ_PERIOD=50
CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_)}" CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_)}"
CONFIG_LV_USE_SNAPSHOT=n 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