fixes that made it work
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user