|
|
|
@@ -2,6 +2,17 @@
|
|
|
|
|
|
|
|
|
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
|
|
|
|
#define VALUE_CLAMP(v, lo, hi) ((v) < (lo) ? (lo) : ((v) > (hi) ? (hi) : (v)))
|
|
|
|
|
#define CLEAR_ROW_CACHE() memset(_row_valid, 0, sizeof(_row_valid))
|
|
|
|
|
|
|
|
|
|
#define LABEL_BOOT_SCREEN "Boot: "
|
|
|
|
|
#define LABEL_MAIN_SCREEN "Main: "
|
|
|
|
|
#define LABEL_AIR_CONDITION_TEMP_ON "Temp ON: "
|
|
|
|
|
#define LABEL_AIR_CONDITION_TEMP_OFF "Temp OFF: "
|
|
|
|
|
#define LABEL_EXIT_CHANNEL_1_POWER "PW CH 1: "
|
|
|
|
|
#define LABEL_EXIT_CHANNEL_2_POWER "PW CH 2: "
|
|
|
|
|
#define LABEL_EXIT_CHANNEL_3_POWER "PW CH 3: "
|
|
|
|
|
#define LABEL_EXIT_CHANNEL_4_POWER "PW CH 4: "
|
|
|
|
|
#define LABEL_BULB_BRIGHTNESS "PW BULB: "
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
@@ -13,7 +24,18 @@ typedef struct
|
|
|
|
|
uint8_t main_screen_number;
|
|
|
|
|
} _zh_nvs_setting_data_blob_t;
|
|
|
|
|
|
|
|
|
|
// Хэндлы - функции - переменные
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
const char *name;
|
|
|
|
|
const char *const lines[LCD_TOTAL_ROWS];
|
|
|
|
|
char *values[LCD_TOTAL_ROWS];
|
|
|
|
|
} _zh_main_screen_description_t;
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
const char *name;
|
|
|
|
|
const char *const lines[LCD_TOTAL_ROWS];
|
|
|
|
|
} _zh_boot_screen_description_t;
|
|
|
|
|
|
|
|
|
|
static httpd_handle_t _http_server_handle = NULL;
|
|
|
|
|
static zh_encoder_handle_t *_mode_encoder_handle = NULL;
|
|
|
|
@@ -21,7 +43,9 @@ static zh_menu_handle_t *_menu_handle = NULL;
|
|
|
|
|
// static zh_pcf8574_handle_t *_button_handle = NULL;
|
|
|
|
|
static zh_pcf8574_handle_t *_lcd_handle = NULL;
|
|
|
|
|
static zh_160x_i2c_handle_t *_lcd_160x_handle = NULL;
|
|
|
|
|
// static TaskHandle_t _stats_task_handle = NULL;
|
|
|
|
|
static zh_aht_handle_t *_aht_handle = NULL;
|
|
|
|
|
static TaskHandle_t _stats_task_handle = NULL;
|
|
|
|
|
static TaskHandle_t _aht_task_handle = NULL;
|
|
|
|
|
|
|
|
|
|
static void _zh_load_settings(void);
|
|
|
|
|
static void _zh_save_settings(void);
|
|
|
|
@@ -30,335 +54,119 @@ static void _zh_webserver_init(void);
|
|
|
|
|
static void _zh_encoder_init(void);
|
|
|
|
|
static void _zh_pcf8574_init(void);
|
|
|
|
|
static void _zh_menu_init(void);
|
|
|
|
|
static void _zh_aht_init(void);
|
|
|
|
|
static void _zh_system_load(void);
|
|
|
|
|
static void _zh_pcf8574_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
|
|
|
|
static void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
|
|
|
|
static void _zh_stats_processing_task(void *pvParameter);
|
|
|
|
|
void zh_menu_enter_main_menu_callback(void *arg);
|
|
|
|
|
void zh_menu_exit_main_menu_callback(void *arg);
|
|
|
|
|
void zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_index);
|
|
|
|
|
void zh_menu_selectable_item_callback(void *arg);
|
|
|
|
|
void zh_menu_back_command_callback(void *arg);
|
|
|
|
|
void zh_menu_boot_screen_callback(void *arg);
|
|
|
|
|
void zh_menu_main_screen_callback(void *arg);
|
|
|
|
|
static void _zh_aht_processing_task(void *pvParameter);
|
|
|
|
|
static void _zh_menu_enter_main_menu_callback(void *arg);
|
|
|
|
|
static void _zh_menu_exit_main_menu_callback(void *arg);
|
|
|
|
|
static void _zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_index);
|
|
|
|
|
static void _zh_menu_selectable_item_callback(void *arg);
|
|
|
|
|
static void _zh_menu_back_command_callback(void *arg);
|
|
|
|
|
static void _zh_menu_boot_screen_callback(void *arg);
|
|
|
|
|
static void _zh_menu_main_screen_callback(void *arg);
|
|
|
|
|
static void _zh_draw_main_screen(void);
|
|
|
|
|
static void _zh_draw_boot_screen(void);
|
|
|
|
|
static void _zh_draw_main_menu(zh_menu_node_t *current_node, uint8_t current_index, const zh_menu_status_t *status);
|
|
|
|
|
|
|
|
|
|
// #define ENCODER_NUMBER 0x01
|
|
|
|
|
// #define LCD_VIEWPORT_ROWS 3
|
|
|
|
|
// #define LCD_TOTAL_COLS 16
|
|
|
|
|
// #define LCD_TOTAL_ROWS 4
|
|
|
|
|
#define BOOT_SPLASH_MS 5000
|
|
|
|
|
|
|
|
|
|
// #define MAIN_SCREEN_COUNT 5
|
|
|
|
|
// #define LOAD_SCREEN_COUNT 5
|
|
|
|
|
|
|
|
|
|
// #define NVS_NAMESPACE "sim_race_box" /* NEW */
|
|
|
|
|
// #define NVS_KEY_SETTINGS "settings" /* NEW */
|
|
|
|
|
|
|
|
|
|
// static const char *TAG = "sim_race_box";
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
* Данные main-экранов (idle)
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
int16_t main_race_data[VIEW_CELLS] = {185, 7500, 4, 234, 0};
|
|
|
|
|
int16_t main_engine_data[VIEW_CELLS] = {78, 82, 12, 45, 0};
|
|
|
|
|
int16_t main_times_data[VIEW_CELLS] = {1234, 1201, 1250, -49, 0};
|
|
|
|
|
int16_t main_debug_data[VIEW_CELLS] = {45, -42, 152, 12, 0};
|
|
|
|
|
int16_t main_blank_data[VIEW_CELLS] = {0, 0, 0, 0, 0};
|
|
|
|
|
|
|
|
|
|
static const zh_main_screen_description_t main_screens[] = {
|
|
|
|
|
{"Race", {"SPD", "RPM", "GER", "LAP"}, main_race_data},
|
|
|
|
|
{"Engine", {"OIL", "WTR", "BAT", "FUL"}, main_engine_data},
|
|
|
|
|
{"Times", {"LAP", "BST", "LST", "DLT"}, main_times_data},
|
|
|
|
|
{"Debug", {"HEP", "SIG", "UPT", "CPU"}, main_debug_data},
|
|
|
|
|
{"Blank", {"---", "---", "---", "---"}, main_blank_data},
|
|
|
|
|
static const _zh_boot_screen_description_t _boot_screens[] = {
|
|
|
|
|
{"Boot", {"Booting...", "RAM: OK", "I2C: OK", "Starting..."}},
|
|
|
|
|
{"Version", {"FW v1.0", "Build 2025-10", "ESP-IDF v5.4", ""}},
|
|
|
|
|
{"Diag", {"I2C: OK", "LCD: OK", "ENC: OK", "All: OK"}},
|
|
|
|
|
{"Ready", {"System ready", "Ready for race", "Let's go", ""}},
|
|
|
|
|
{"Blank", {"---", "---", "---", "---"}},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool _boot_done = false;
|
|
|
|
|
static bool _prev_selected = false; /* NEW: для детекции выхода из edit */
|
|
|
|
|
static char _cabin_temperature_status[5] = "";
|
|
|
|
|
static char _cabin_humidity_status[5] = "";
|
|
|
|
|
static char _air_condition_status[5] = "AUTO";
|
|
|
|
|
static char _side_light_status[5] = "ON";
|
|
|
|
|
static char _flash_light_status[5] = "OFF";
|
|
|
|
|
|
|
|
|
|
/* ---------- Динамические буферы меток ---------- */
|
|
|
|
|
// char lbl_boot_screen[LCD_TOTAL_COLS + 1] = "B:Boot";
|
|
|
|
|
// char lbl_main_screen[LCD_TOTAL_COLS + 1] = "M:Race";
|
|
|
|
|
// char lbl_temp_on[LCD_TOTAL_COLS + 1] = "Temp ON 15C";
|
|
|
|
|
// char lbl_temp_off[LCD_TOTAL_COLS + 1] = "Temp OFF 35C";
|
|
|
|
|
// char lbl_pw_ch_1[LCD_TOTAL_COLS + 1] = "PW CH 1 100%";
|
|
|
|
|
// char lbl_pw_ch_2[LCD_TOTAL_COLS + 1] = "PW CH 2 100%";
|
|
|
|
|
// char lbl_pw_ch_3[LCD_TOTAL_COLS + 1] = "PW CH 3 100%";
|
|
|
|
|
// char lbl_pw_ch_4[LCD_TOTAL_COLS + 1] = "PW CH 4 100%";
|
|
|
|
|
static _zh_main_screen_description_t _main_screens[] = {
|
|
|
|
|
{"Race", {"CABIN TEMP:", "AIR COND:", "SIDE LT:", "FLASH LT:"}, {_cabin_temperature_status, _air_condition_status, _side_light_status, _flash_light_status}},
|
|
|
|
|
{"Test", {"Temperature:", "Humidity:", "Lights:", "Strobe:"}, {_cabin_temperature_status, _cabin_humidity_status, _side_light_status, _flash_light_status}},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* ---------- Handle'ы ---------- */
|
|
|
|
|
// zh_encoder_handle_t *encoder_handle = NULL;
|
|
|
|
|
// zh_menu_handle_t *menu_handle = NULL;
|
|
|
|
|
// zh_pcf8574_handle_t *pcf8574_handle = NULL;
|
|
|
|
|
// zh_160x_i2c_handle_t *lcd_handle = NULL;
|
|
|
|
|
static char _label_boot_screen[LCD_TOTAL_COLS + 1] = LABEL_BOOT_SCREEN;
|
|
|
|
|
static char _label_main_screen[LCD_TOTAL_COLS + 1] = LABEL_MAIN_SCREEN;
|
|
|
|
|
static char _label_air_condition_temp_on[LCD_TOTAL_COLS + 1] = LABEL_AIR_CONDITION_TEMP_ON;
|
|
|
|
|
static char _label_air_condition_temp_off[LCD_TOTAL_COLS + 1] = LABEL_AIR_CONDITION_TEMP_OFF;
|
|
|
|
|
static char _label_exit_channel_1_power[LCD_TOTAL_COLS + 1] = LABEL_EXIT_CHANNEL_1_POWER;
|
|
|
|
|
static char _label_exit_channel_2_power[LCD_TOTAL_COLS + 1] = LABEL_EXIT_CHANNEL_2_POWER;
|
|
|
|
|
static char _label_exit_channel_3_power[LCD_TOTAL_COLS + 1] = LABEL_EXIT_CHANNEL_3_POWER;
|
|
|
|
|
static char _label_exit_channel_4_power[LCD_TOTAL_COLS + 1] = LABEL_EXIT_CHANNEL_4_POWER;
|
|
|
|
|
static char _label_bulb_brightness[LCD_TOTAL_COLS + 1] = LABEL_BULB_BRIGHTNESS;
|
|
|
|
|
|
|
|
|
|
/* ---------- Мьютекс LCD ---------- */
|
|
|
|
|
static SemaphoreHandle_t _lcd_mutex = NULL;
|
|
|
|
|
static zh_menu_node_t _main_menu = {
|
|
|
|
|
.label = "Settings",
|
|
|
|
|
.submenu = (zh_menu_node_t[]){
|
|
|
|
|
{.label = "Screens", .submenu = (zh_menu_node_t[]){
|
|
|
|
|
{.label = _label_boot_screen, .item_cb = &_zh_menu_boot_screen_callback, .selectable = true},
|
|
|
|
|
{.label = _label_main_screen, .item_cb = &_zh_menu_main_screen_callback, .selectable = true},
|
|
|
|
|
{.label = "Back", .item_cb = &_zh_menu_back_command_callback},
|
|
|
|
|
},
|
|
|
|
|
.item_numbers = 3},
|
|
|
|
|
{.label = "Air condition", .submenu = (zh_menu_node_t[]){
|
|
|
|
|
{.label = _label_air_condition_temp_on, .item_cb = &_zh_menu_selectable_item_callback, .selectable = true},
|
|
|
|
|
{.label = _label_air_condition_temp_off, .item_cb = &_zh_menu_selectable_item_callback, .selectable = true},
|
|
|
|
|
{.label = "Back", .item_cb = &_zh_menu_back_command_callback},
|
|
|
|
|
},
|
|
|
|
|
.item_numbers = 3},
|
|
|
|
|
{.label = "Lights", .submenu = (zh_menu_node_t[]){
|
|
|
|
|
{.label = _label_exit_channel_1_power, .item_cb = &_zh_menu_selectable_item_callback, .selectable = true},
|
|
|
|
|
{.label = _label_exit_channel_2_power, .item_cb = &_zh_menu_selectable_item_callback, .selectable = true},
|
|
|
|
|
{.label = _label_exit_channel_3_power, .item_cb = &_zh_menu_selectable_item_callback, .selectable = true},
|
|
|
|
|
{.label = _label_exit_channel_4_power, .item_cb = &_zh_menu_selectable_item_callback, .selectable = true},
|
|
|
|
|
{.label = _label_bulb_brightness, .item_cb = &_zh_menu_selectable_item_callback, .selectable = true},
|
|
|
|
|
{.label = "Back", .item_cb = &_zh_menu_back_command_callback},
|
|
|
|
|
},
|
|
|
|
|
.item_numbers = 6},
|
|
|
|
|
{.label = "Exit", .item_cb = &_zh_menu_back_command_callback},
|
|
|
|
|
},
|
|
|
|
|
.item_numbers = 4,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// static void zh_load_settings(void);
|
|
|
|
|
// static void zh_save_settings(void);
|
|
|
|
|
static zh_menu_node_t *_last_node = NULL;
|
|
|
|
|
static uint8_t _last_index = 0;
|
|
|
|
|
static zh_menu_node_t *_cached_node = NULL;
|
|
|
|
|
static bool _prev_selected = false;
|
|
|
|
|
static char _row_cache[LCD_TOTAL_ROWS][LCD_TOTAL_COLS] = {0};
|
|
|
|
|
static bool _row_valid[LCD_TOTAL_ROWS] = {0};
|
|
|
|
|
volatile static uint8_t _air_condition_temp_on = 30;
|
|
|
|
|
volatile static uint8_t _air_condition_temp_off = 15;
|
|
|
|
|
volatile static uint8_t _exit_channel_power[4] = {100, 100, 100, 100};
|
|
|
|
|
volatile static uint8_t _bulb_brightness = 100;
|
|
|
|
|
volatile static uint8_t _boot_screen_number = 0;
|
|
|
|
|
volatile static uint8_t _main_screen_number = 0;
|
|
|
|
|
|
|
|
|
|
// void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
|
|
|
|
|
|
|
|
|
// void zh_menu_enter_main_menu_callback(void *arg);
|
|
|
|
|
// void zh_menu_exit_main_menu_callback(void *arg);
|
|
|
|
|
// void zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_index);
|
|
|
|
|
// void zh_menu_selectable_item_callback(void *arg);
|
|
|
|
|
// void zh_menu_back_command_callback(void *arg);
|
|
|
|
|
// void zh_menu_boot_screen_callback(void *arg);
|
|
|
|
|
// void zh_menu_main_screen_callback(void *arg);
|
|
|
|
|
|
|
|
|
|
/* ---------- Состояние меню ---------- */
|
|
|
|
|
static zh_menu_node_t *last_node = NULL;
|
|
|
|
|
static uint8_t last_index = 0;
|
|
|
|
|
static zh_menu_node_t *cached_node = NULL;
|
|
|
|
|
|
|
|
|
|
/* ---------- Кэш строк LCD ---------- */
|
|
|
|
|
static char row_cache[LCD_TOTAL_ROWS][LCD_TOTAL_COLS];
|
|
|
|
|
static bool row_valid[LCD_TOTAL_ROWS] = {false, false, false, false};
|
|
|
|
|
|
|
|
|
|
volatile static uint8_t air_condition_temp_on = 25;
|
|
|
|
|
volatile static uint8_t air_condition_temp_off = 20;
|
|
|
|
|
volatile static uint8_t exit_channel_power[4] = {100, 100, 100, 100};
|
|
|
|
|
volatile static uint8_t bulb_brightness = 100;
|
|
|
|
|
volatile static uint8_t boot_screen_number = 0;
|
|
|
|
|
volatile static uint8_t main_screen_number = 0;
|
|
|
|
|
|
|
|
|
|
/* ---------- Утилиты ---------- */
|
|
|
|
|
// static inline int16_t _clamp_i16(int16_t v, int16_t lo, int16_t hi)
|
|
|
|
|
// {
|
|
|
|
|
// return v < lo ? lo : (v > hi ? hi : v);
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// static inline uint8_t _clamp_u8(int16_t v, uint8_t lo, uint8_t hi)
|
|
|
|
|
// {
|
|
|
|
|
// return v < lo ? lo : (v > hi ? hi : v);
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
static void _invalidaterow_cache(void)
|
|
|
|
|
{
|
|
|
|
|
memset(row_valid, 0, sizeof(row_valid));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Форматирование ---------- */
|
|
|
|
|
static void _format_row(char *buf, const char *label, bool selected, bool edit, bool arrow_up, bool arrow_down)
|
|
|
|
|
{
|
|
|
|
|
memset(buf, ' ', LCD_TOTAL_COLS);
|
|
|
|
|
buf[LCD_TOTAL_COLS] = '\0';
|
|
|
|
|
buf[0] = edit ? '*' : (selected ? '>' : ' ');
|
|
|
|
|
|
|
|
|
|
uint8_t text_cols = (arrow_up || arrow_down) ? (LCD_TOTAL_COLS - 2) : (LCD_TOTAL_COLS - 1);
|
|
|
|
|
size_t len = strlen(label);
|
|
|
|
|
if (len > text_cols)
|
|
|
|
|
len = text_cols;
|
|
|
|
|
memcpy(&buf[1], label, len);
|
|
|
|
|
|
|
|
|
|
if (arrow_up)
|
|
|
|
|
{
|
|
|
|
|
buf[LCD_TOTAL_COLS - 1] = '^';
|
|
|
|
|
}
|
|
|
|
|
else if (arrow_down)
|
|
|
|
|
{
|
|
|
|
|
buf[LCD_TOTAL_COLS - 1] = 'v';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _format_main_row(char *buf, const char *label, int16_t value)
|
|
|
|
|
{
|
|
|
|
|
snprintf(buf, LCD_TOTAL_COLS + 1, "%3.3s:%5d", label, (int)value);
|
|
|
|
|
// memset(buf, ' ', LCD_TOTAL_COLS);
|
|
|
|
|
// buf[LCD_TOTAL_COLS] = '\0';
|
|
|
|
|
// snprintf(buf, LCD_TOTAL_COLS + 1, "%3s:%5d", label, (int)value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _format_load_row(char *buf, const char *line)
|
|
|
|
|
{
|
|
|
|
|
snprintf(buf, LCD_TOTAL_COLS + 1, "%-*.*s", LCD_TOTAL_COLS, LCD_TOTAL_COLS, line);
|
|
|
|
|
// memset(buf, ' ', LCD_TOTAL_COLS);
|
|
|
|
|
// buf[LCD_TOTAL_COLS] = '\0';
|
|
|
|
|
// size_t len = strlen(line);
|
|
|
|
|
// if (len > LCD_TOTAL_COLS)
|
|
|
|
|
// len = LCD_TOTAL_COLS;
|
|
|
|
|
// memcpy(buf, line, len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _lcd_put_row(uint8_t row, const char *text)
|
|
|
|
|
{
|
|
|
|
|
if (row_valid[row] && memcmp(row_cache[row], text, LCD_TOTAL_COLS) == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
zh_160x_set_cursor(&_lcd_160x_handle, row, 0);
|
|
|
|
|
zh_160x_print_char(&_lcd_160x_handle, text);
|
|
|
|
|
memcpy(row_cache[row], text, LCD_TOTAL_COLS);
|
|
|
|
|
row_valid[row] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Точечное обновление меток ---------- */
|
|
|
|
|
static void _refresh_dynamic_labels(zh_menu_node_t *node)
|
|
|
|
|
{
|
|
|
|
|
if (node == NULL || node->submenu == NULL)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (uint8_t i = 0; i < node->item_numbers; ++i)
|
|
|
|
|
{
|
|
|
|
|
const char *lbl = node->submenu[i].label;
|
|
|
|
|
if (lbl == lbl_temp_on)
|
|
|
|
|
{
|
|
|
|
|
snprintf(lbl_temp_on, sizeof(lbl_temp_on), "Temp ON %2dC", (int)air_condition_temp_on);
|
|
|
|
|
}
|
|
|
|
|
else if (lbl == lbl_temp_off)
|
|
|
|
|
{
|
|
|
|
|
snprintf(lbl_temp_off, sizeof(lbl_temp_off), "Temp OFF %2dC", (int)air_condition_temp_off);
|
|
|
|
|
}
|
|
|
|
|
else if (lbl == lbl_pw_ch_1)
|
|
|
|
|
{
|
|
|
|
|
snprintf(lbl_pw_ch_1, sizeof(lbl_pw_ch_1), "PW CH 1 %3d%%", (int)exit_channel_power[0]);
|
|
|
|
|
}
|
|
|
|
|
else if (lbl == lbl_pw_ch_2)
|
|
|
|
|
{
|
|
|
|
|
snprintf(lbl_pw_ch_2, sizeof(lbl_pw_ch_2), "PW CH 2 %3d%%", (int)exit_channel_power[1]);
|
|
|
|
|
}
|
|
|
|
|
else if (lbl == lbl_pw_ch_3)
|
|
|
|
|
{
|
|
|
|
|
snprintf(lbl_pw_ch_3, sizeof(lbl_pw_ch_3), "PW CH 3 %3d%%", (int)exit_channel_power[2]);
|
|
|
|
|
}
|
|
|
|
|
else if (lbl == lbl_pw_ch_4)
|
|
|
|
|
{
|
|
|
|
|
snprintf(lbl_pw_ch_4, sizeof(lbl_pw_ch_4), "PW CH 4 %3d%%", (int)exit_channel_power[3]);
|
|
|
|
|
}
|
|
|
|
|
else if (lbl == lbl_boot_screen)
|
|
|
|
|
{
|
|
|
|
|
snprintf(lbl_boot_screen, sizeof(lbl_boot_screen), "B:%s", boot_screens[boot_screen_number].name);
|
|
|
|
|
}
|
|
|
|
|
else if (lbl == lbl_main_screen)
|
|
|
|
|
{
|
|
|
|
|
snprintf(lbl_main_screen, sizeof(lbl_main_screen), "M:%s", main_screens[main_screen_number].name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Отрисовка экранов ---------- */
|
|
|
|
|
static void _draw_main_screen(void)
|
|
|
|
|
{
|
|
|
|
|
const zh_main_screen_description_t *s = &main_screens[main_screen_number];
|
|
|
|
|
for (uint8_t i = 0; i < LCD_TOTAL_ROWS; ++i)
|
|
|
|
|
{
|
|
|
|
|
char buf[LCD_TOTAL_COLS + 1];
|
|
|
|
|
_format_main_row(buf, s->lines[i], s->data[i]);
|
|
|
|
|
_lcd_put_row(i, buf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _draw_load_screen(void)
|
|
|
|
|
{
|
|
|
|
|
const zh_boot_screen_description_t *s = &boot_screens[boot_screen_number];
|
|
|
|
|
for (uint8_t i = 0; i < LCD_TOTAL_ROWS; ++i)
|
|
|
|
|
{
|
|
|
|
|
char buf[LCD_TOTAL_COLS + 1];
|
|
|
|
|
_format_load_row(buf, s->lines[i]);
|
|
|
|
|
_lcd_put_row(i, buf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Отрисовка меню ---------- */
|
|
|
|
|
static void _draw_menu(zh_menu_node_t *current_node, uint8_t current_index, const zh_menu_status_t *status)
|
|
|
|
|
{
|
|
|
|
|
if (current_node != cached_node)
|
|
|
|
|
{
|
|
|
|
|
_invalidaterow_cache();
|
|
|
|
|
cached_node = current_node;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_refresh_dynamic_labels(current_node);
|
|
|
|
|
|
|
|
|
|
zh_menu_view_t view = {0};
|
|
|
|
|
zh_menu_get_view(&_menu_handle, &view);
|
|
|
|
|
|
|
|
|
|
char header[LCD_TOTAL_COLS + 1];
|
|
|
|
|
// memset(header, ' ', LCD_TOTAL_COLS);
|
|
|
|
|
// header[LCD_TOTAL_COLS] = '\0';
|
|
|
|
|
// size_t hlen = strlen(current_node->label);
|
|
|
|
|
// if (hlen > LCD_TOTAL_COLS)
|
|
|
|
|
// {
|
|
|
|
|
// hlen = LCD_TOTAL_COLS;
|
|
|
|
|
// }
|
|
|
|
|
// memcpy(header, current_node->label, hlen);
|
|
|
|
|
|
|
|
|
|
snprintf(header, sizeof(header), "%-*.*s", LCD_TOTAL_COLS, LCD_TOTAL_COLS, current_node->label);
|
|
|
|
|
|
|
|
|
|
_lcd_put_row(0, header);
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < LCD_TOTAL_ROWS - 1; ++i)
|
|
|
|
|
{
|
|
|
|
|
char buf[LCD_TOTAL_COLS + 1];
|
|
|
|
|
|
|
|
|
|
if (i < view.visible_count)
|
|
|
|
|
{
|
|
|
|
|
uint8_t idx = view.first_visible + i;
|
|
|
|
|
bool is_cursor = (idx == current_index);
|
|
|
|
|
bool is_edit = is_cursor && status->is_selected && current_node->submenu[idx].selectable;
|
|
|
|
|
bool arrow_up = (i == 0) && (view.first_visible > 0);
|
|
|
|
|
bool arrow_down = (i == (view.visible_count - 1)) && ((uint8_t)(view.first_visible + view.visible_count) < current_node->item_numbers);
|
|
|
|
|
_format_row(buf, current_node->submenu[idx].label, is_cursor, is_edit, arrow_up, arrow_down);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
memset(buf, ' ', LCD_TOTAL_COLS);
|
|
|
|
|
buf[LCD_TOTAL_COLS] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_lcd_put_row((uint8_t)(i + 1), buf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Boot: 5 сек splash → main ---------- */
|
|
|
|
|
static void _boot_task(void *pv)
|
|
|
|
|
{
|
|
|
|
|
(void)pv;
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(BOOT_SPLASH_MS));
|
|
|
|
|
_boot_done = true;
|
|
|
|
|
|
|
|
|
|
zh_menu_status_t s = {0};
|
|
|
|
|
zh_menu_get(&_menu_handle, &s);
|
|
|
|
|
if (!s.is_active)
|
|
|
|
|
{
|
|
|
|
|
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
|
|
|
|
|
zh_160x_lcd_clear(&_lcd_160x_handle); /* NEW: очистка перед первым main view */
|
|
|
|
|
_invalidaterow_cache();
|
|
|
|
|
_draw_main_screen();
|
|
|
|
|
xSemaphoreGive(_lcd_mutex);
|
|
|
|
|
}
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- app_main ---------- */
|
|
|
|
|
void app_main(void)
|
|
|
|
|
{
|
|
|
|
|
nvs_flash_init();
|
|
|
|
|
esp_err_t err = nvs_flash_init();
|
|
|
|
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
|
|
|
|
{
|
|
|
|
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
|
|
|
|
ESP_ERROR_CHECK(nvs_flash_init());
|
|
|
|
|
}
|
|
|
|
|
esp_event_loop_create_default();
|
|
|
|
|
_lcd_mutex = xSemaphoreCreateMutex();
|
|
|
|
|
_zh_load_settings();
|
|
|
|
|
_zh_pcf8574_init();
|
|
|
|
|
_zh_system_load();
|
|
|
|
|
_zh_aht_init();
|
|
|
|
|
_zh_encoder_init();
|
|
|
|
|
_zh_menu_init();
|
|
|
|
|
// _zh_wifi_init();
|
|
|
|
|
// _zh_webserver_init();
|
|
|
|
|
// const esp_partition_t *running = esp_ota_get_running_partition();
|
|
|
|
|
// esp_ota_img_states_t ota_state = {0};
|
|
|
|
|
// esp_ota_get_state_partition(running, &ota_state);
|
|
|
|
|
// if (ota_state == ESP_OTA_IMG_PENDING_VERIFY)
|
|
|
|
|
// {
|
|
|
|
|
// vTaskDelay(60000 / portTICK_PERIOD_MS);
|
|
|
|
|
// esp_ota_mark_app_valid_cancel_rollback();
|
|
|
|
|
// }
|
|
|
|
|
_boot_done = false;
|
|
|
|
|
_prev_selected = false;
|
|
|
|
|
_invalidaterow_cache();
|
|
|
|
|
_draw_load_screen();
|
|
|
|
|
xTaskCreate(_boot_task, "boot", 3072, NULL, 3, NULL);
|
|
|
|
|
_zh_wifi_init();
|
|
|
|
|
_zh_webserver_init();
|
|
|
|
|
_zh_system_load();
|
|
|
|
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
|
|
|
|
esp_ota_img_states_t ota_state = {0};
|
|
|
|
|
esp_ota_get_state_partition(running, &ota_state);
|
|
|
|
|
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY)
|
|
|
|
|
{
|
|
|
|
|
vTaskDelay(60000 / portTICK_PERIOD_MS);
|
|
|
|
|
esp_ota_mark_app_valid_cancel_rollback();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_load_settings(void)
|
|
|
|
@@ -369,22 +177,22 @@ static void _zh_load_settings(void)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_zh_nvs_setting_data_blob_t setting_data_blob = {0};
|
|
|
|
|
uint8_t size = sizeof(setting_data_blob);
|
|
|
|
|
if (nvs_get_blob(nvs, NVS_KEY_SETTINGS, &setting_data_blob, (size_t *)&size) != ESP_OK)
|
|
|
|
|
size_t size = sizeof(setting_data_blob);
|
|
|
|
|
if (nvs_get_blob(nvs, NVS_KEY_SETTINGS, &setting_data_blob, &size) != ESP_OK)
|
|
|
|
|
{
|
|
|
|
|
nvs_close(nvs);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
nvs_close(nvs);
|
|
|
|
|
air_condition_temp_on = setting_data_blob.air_condition_temp_on;
|
|
|
|
|
air_condition_temp_off = setting_data_blob.air_condition_temp_off;
|
|
|
|
|
exit_channel_power[0] = setting_data_blob.exit_channel_power[0];
|
|
|
|
|
exit_channel_power[1] = setting_data_blob.exit_channel_power[1];
|
|
|
|
|
exit_channel_power[2] = setting_data_blob.exit_channel_power[2];
|
|
|
|
|
exit_channel_power[3] = setting_data_blob.exit_channel_power[3];
|
|
|
|
|
bulb_brightness = setting_data_blob.bulb_brightness;
|
|
|
|
|
boot_screen_number = setting_data_blob.boot_screen_number;
|
|
|
|
|
main_screen_number = setting_data_blob.main_screen_number;
|
|
|
|
|
_air_condition_temp_on = setting_data_blob.air_condition_temp_on;
|
|
|
|
|
_air_condition_temp_off = setting_data_blob.air_condition_temp_off;
|
|
|
|
|
_exit_channel_power[0] = setting_data_blob.exit_channel_power[0];
|
|
|
|
|
_exit_channel_power[1] = setting_data_blob.exit_channel_power[1];
|
|
|
|
|
_exit_channel_power[2] = setting_data_blob.exit_channel_power[2];
|
|
|
|
|
_exit_channel_power[3] = setting_data_blob.exit_channel_power[3];
|
|
|
|
|
_bulb_brightness = setting_data_blob.bulb_brightness;
|
|
|
|
|
_boot_screen_number = VALUE_CLAMP(setting_data_blob.boot_screen_number, 0, ARRAY_SIZE(_boot_screens) - 1);
|
|
|
|
|
_main_screen_number = VALUE_CLAMP(setting_data_blob.main_screen_number, 0, ARRAY_SIZE(_main_screens) - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_save_settings(void)
|
|
|
|
@@ -395,15 +203,15 @@ static void _zh_save_settings(void)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_zh_nvs_setting_data_blob_t setting_data_blob = {0};
|
|
|
|
|
setting_data_blob.air_condition_temp_on = air_condition_temp_on;
|
|
|
|
|
setting_data_blob.air_condition_temp_off = air_condition_temp_off;
|
|
|
|
|
setting_data_blob.exit_channel_power[0] = exit_channel_power[0];
|
|
|
|
|
setting_data_blob.exit_channel_power[1] = exit_channel_power[1];
|
|
|
|
|
setting_data_blob.exit_channel_power[2] = exit_channel_power[2];
|
|
|
|
|
setting_data_blob.exit_channel_power[3] = exit_channel_power[3];
|
|
|
|
|
setting_data_blob.bulb_brightness = bulb_brightness;
|
|
|
|
|
setting_data_blob.boot_screen_number = boot_screen_number;
|
|
|
|
|
setting_data_blob.main_screen_number = main_screen_number;
|
|
|
|
|
setting_data_blob.air_condition_temp_on = _air_condition_temp_on;
|
|
|
|
|
setting_data_blob.air_condition_temp_off = _air_condition_temp_off;
|
|
|
|
|
setting_data_blob.exit_channel_power[0] = _exit_channel_power[0];
|
|
|
|
|
setting_data_blob.exit_channel_power[1] = _exit_channel_power[1];
|
|
|
|
|
setting_data_blob.exit_channel_power[2] = _exit_channel_power[2];
|
|
|
|
|
setting_data_blob.exit_channel_power[3] = _exit_channel_power[3];
|
|
|
|
|
setting_data_blob.bulb_brightness = _bulb_brightness;
|
|
|
|
|
setting_data_blob.boot_screen_number = _boot_screen_number;
|
|
|
|
|
setting_data_blob.main_screen_number = _main_screen_number;
|
|
|
|
|
nvs_set_blob(nvs, NVS_KEY_SETTINGS, &setting_data_blob, sizeof(setting_data_blob));
|
|
|
|
|
nvs_commit(nvs);
|
|
|
|
|
nvs_close(nvs);
|
|
|
|
@@ -419,8 +227,9 @@ static void _zh_wifi_init(void)
|
|
|
|
|
.ap = {
|
|
|
|
|
.ssid = WIFI_AP_SSID,
|
|
|
|
|
.password = WIFI_AP_PASS,
|
|
|
|
|
.max_connection = 4,
|
|
|
|
|
.max_connection = MAX_AP_CONNECTION,
|
|
|
|
|
.authmode = WIFI_AUTH_WPA2_PSK,
|
|
|
|
|
.channel = WIFI_AP_CHANNEL,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
esp_wifi_set_mode(WIFI_MODE_AP);
|
|
|
|
@@ -457,6 +266,7 @@ static void _zh_pcf8574_init(void)
|
|
|
|
|
i2c_master_bus_handle_t i2c_bus_handle = NULL;
|
|
|
|
|
i2c_master_bus_config_t i2c_bus_config = {
|
|
|
|
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
|
|
|
|
.i2c_port = -1,
|
|
|
|
|
.scl_io_num = EXPANDERS_I2C_SCL_GPIO,
|
|
|
|
|
.sda_io_num = EXPANDERS_I2C_SDA_GPIO,
|
|
|
|
|
.glitch_ignore_cnt = 7,
|
|
|
|
@@ -487,24 +297,54 @@ static void _zh_menu_init(void)
|
|
|
|
|
config.task_priority = MENU_TASK_PRIORITY;
|
|
|
|
|
config.stack_size = MENU_TASK_STACK_SIZE;
|
|
|
|
|
config.queue_size = MENU_QUEUE_SIZE;
|
|
|
|
|
config.root = &main_menu;
|
|
|
|
|
config.root = &_main_menu;
|
|
|
|
|
config.loop = false;
|
|
|
|
|
config.viewport_rows = LCD_TOTAL_ROWS - 1;
|
|
|
|
|
config.enter_cb = &zh_menu_enter_main_menu_callback;
|
|
|
|
|
config.exit_cb = &zh_menu_exit_main_menu_callback;
|
|
|
|
|
config.change_cb = &zh_menu_change_menu_callback;
|
|
|
|
|
config.enter_cb = &_zh_menu_enter_main_menu_callback;
|
|
|
|
|
config.exit_cb = &_zh_menu_exit_main_menu_callback;
|
|
|
|
|
config.change_cb = &_zh_menu_change_menu_callback;
|
|
|
|
|
zh_menu_init(&config, &_menu_handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_aht_init(void)
|
|
|
|
|
{
|
|
|
|
|
i2c_master_bus_handle_t i2c_bus_handle = NULL;
|
|
|
|
|
i2c_master_bus_config_t i2c_bus_config = {
|
|
|
|
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
|
|
|
|
.i2c_port = -1,
|
|
|
|
|
.scl_io_num = AHT_I2C_SCL_GPIO,
|
|
|
|
|
.sda_io_num = AHT_I2C_SDA_GPIO,
|
|
|
|
|
.glitch_ignore_cnt = 7,
|
|
|
|
|
.flags.enable_internal_pullup = true,
|
|
|
|
|
};
|
|
|
|
|
i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
|
|
|
|
|
zh_aht_init_config_t config = ZH_AHT_INIT_CONFIG_DEFAULT();
|
|
|
|
|
config.i2c_handle = i2c_bus_handle;
|
|
|
|
|
zh_aht_init(&config, &_aht_handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_system_load(void)
|
|
|
|
|
{
|
|
|
|
|
// TO DO
|
|
|
|
|
zh_160x_lcd_clear(&_lcd_160x_handle);
|
|
|
|
|
_zh_draw_boot_screen();
|
|
|
|
|
float humidity = 0.0;
|
|
|
|
|
float temperature = 0.0;
|
|
|
|
|
zh_aht_read(&_aht_handle, &humidity, &temperature);
|
|
|
|
|
snprintf(_cabin_temperature_status, sizeof(_cabin_temperature_status), "%dC", (int)lroundf(temperature));
|
|
|
|
|
snprintf(_cabin_humidity_status, sizeof(_cabin_humidity_status), "%d%%", (int)lroundf(humidity));
|
|
|
|
|
vTaskDelay(5000);
|
|
|
|
|
zh_160x_lcd_clear(&_lcd_160x_handle);
|
|
|
|
|
CLEAR_ROW_CACHE();
|
|
|
|
|
_zh_draw_main_screen();
|
|
|
|
|
xTaskCreatePinnedToCore(&_zh_stats_processing_task, "stats_processing_task", STATS_TASK_STACK_SIZE, NULL, STATS_TASK_PRIORITY, &_stats_task_handle, tskNO_AFFINITY);
|
|
|
|
|
xTaskCreatePinnedToCore(&_zh_aht_processing_task, "aht_processing_task", AHT_TASK_STACK_SIZE, NULL, AHT_TASK_PRIORITY, &_aht_task_handle, tskNO_AFFINITY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_pcf8574_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
|
|
|
|
{
|
|
|
|
|
// TO DO
|
|
|
|
|
}
|
|
|
|
|
/* ---------- Encoder event handler ---------- */
|
|
|
|
|
|
|
|
|
|
void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
|
|
|
|
{
|
|
|
|
|
(void)arg;
|
|
|
|
@@ -514,7 +354,7 @@ void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t e
|
|
|
|
|
{
|
|
|
|
|
case ZH_BUTTON_EVENT:
|
|
|
|
|
{
|
|
|
|
|
zh_encoder_button_event_on_isr_t *button_event = event_data;
|
|
|
|
|
zh_encoder_button_event_on_isr_t *button_event = (zh_encoder_button_event_on_isr_t *)event_data;
|
|
|
|
|
if (button_event->button_status == ZH_ENCODER_BUTTON_STATUS_RELEASED)
|
|
|
|
|
{
|
|
|
|
|
menu_status.is_active == false ? zh_menu_set(&_menu_handle, ZH_MENU_ENTER, NULL, 0) : menu_status.is_selected == false ? zh_menu_set(&_menu_handle, ZH_MENU_SELECT, NULL, 0)
|
|
|
|
@@ -524,7 +364,7 @@ void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t e
|
|
|
|
|
}
|
|
|
|
|
case ZH_ENCODER_EVENT:
|
|
|
|
|
{
|
|
|
|
|
zh_encoder_event_on_isr_t *encoder_event = event_data;
|
|
|
|
|
zh_encoder_event_on_isr_t *encoder_event = (zh_encoder_event_on_isr_t *)event_data;
|
|
|
|
|
if (menu_status.is_active == true)
|
|
|
|
|
{
|
|
|
|
|
if (menu_status.is_selected == true)
|
|
|
|
@@ -545,68 +385,97 @@ void _zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t e
|
|
|
|
|
|
|
|
|
|
static void _zh_stats_processing_task(void *pvParameter)
|
|
|
|
|
{
|
|
|
|
|
(void)pvParameter;
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
const zh_encoder_stats_t *encoder_stats = zh_encoder_get_stats();
|
|
|
|
|
const zh_pcf8574_stats_t *pcf8574_stats = zh_pcf8574_get_stats();
|
|
|
|
|
const zh_menu_stats_t *menu_stats = zh_menu_get_stats();
|
|
|
|
|
const zh_aht_stats_t *aht_stats = zh_aht_get_stats();
|
|
|
|
|
printf("Stats task minimum free stack size: %lu.\n", (uint32_t)uxTaskGetStackHighWaterMark(NULL));
|
|
|
|
|
printf("AHT task minimum free stack size: %lu.\n", (uint32_t)uxTaskGetStackHighWaterMark(_aht_task_handle));
|
|
|
|
|
printf("AHT i2c driver error: %lu.\n", aht_stats->i2c_driver_error);
|
|
|
|
|
printf("Encoder task minimum free stack size: %lu.\n", encoder_stats->min_stack_size);
|
|
|
|
|
printf("Encoder event post error: %lu.\n", encoder_stats->event_post_error);
|
|
|
|
|
printf("Encoder queue overflow error: %lu.\n", encoder_stats->queue_overflow_error);
|
|
|
|
|
printf("Expander task minimum free stack size: %lu.\n", pcf8574_stats->min_stack_size);
|
|
|
|
|
printf("Expander i2c driver error: %lu.\n", pcf8574_stats->i2c_driver_error);
|
|
|
|
|
printf("Expander event post error: %lu.\n", pcf8574_stats->event_post_error);
|
|
|
|
|
printf("Expander queue overflow error: %lu.\n", pcf8574_stats->queue_overflow_error);
|
|
|
|
|
printf("Expander vector error: %lu.\n", pcf8574_stats->vector_error);
|
|
|
|
|
printf("Menu task minimum free stack size: %lu.\n", menu_stats->min_stack_size);
|
|
|
|
|
printf("Menu queue overflow error: %lu.\n", menu_stats->queue_overflow_error);
|
|
|
|
|
printf("\n");
|
|
|
|
|
vTaskDelay(60000 / portTICK_PERIOD_MS);
|
|
|
|
|
}
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Menu callbacks ---------- */
|
|
|
|
|
void zh_menu_enter_main_menu_callback(void *arg)
|
|
|
|
|
static void _zh_aht_processing_task(void *pvParameter)
|
|
|
|
|
{
|
|
|
|
|
(void)arg;
|
|
|
|
|
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
|
|
|
|
|
cached_node = NULL;
|
|
|
|
|
_invalidaterow_cache();
|
|
|
|
|
xSemaphoreGive(_lcd_mutex);
|
|
|
|
|
(void)pvParameter;
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
float humidity = 0.0;
|
|
|
|
|
float temperature = 0.0;
|
|
|
|
|
zh_aht_read(&_aht_handle, &humidity, &temperature);
|
|
|
|
|
snprintf(_cabin_temperature_status, sizeof(_cabin_temperature_status), "%dC", (int)lroundf(temperature));
|
|
|
|
|
snprintf(_cabin_humidity_status, sizeof(_cabin_humidity_status), "%d%%", (int)lroundf(humidity));
|
|
|
|
|
zh_menu_status_t menu_status = {0};
|
|
|
|
|
zh_menu_get(&_menu_handle, &menu_status);
|
|
|
|
|
if (menu_status.is_active == false)
|
|
|
|
|
{
|
|
|
|
|
_zh_draw_main_screen();
|
|
|
|
|
}
|
|
|
|
|
// Здесь будет управление кондеем.
|
|
|
|
|
vTaskDelay(10000 / portTICK_PERIOD_MS);
|
|
|
|
|
}
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void zh_menu_exit_main_menu_callback(void *arg)
|
|
|
|
|
static void _zh_menu_enter_main_menu_callback(void *arg)
|
|
|
|
|
{
|
|
|
|
|
(void)arg;
|
|
|
|
|
_cached_node = NULL;
|
|
|
|
|
CLEAR_ROW_CACHE();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_menu_exit_main_menu_callback(void *arg)
|
|
|
|
|
{
|
|
|
|
|
(void)arg;
|
|
|
|
|
/* NEW: страховочное сохранение при выходе из меню */
|
|
|
|
|
_zh_save_settings();
|
|
|
|
|
_prev_selected = false;
|
|
|
|
|
|
|
|
|
|
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
|
|
|
|
|
zh_160x_lcd_clear(&_lcd_160x_handle);
|
|
|
|
|
_invalidaterow_cache();
|
|
|
|
|
if (_boot_done)
|
|
|
|
|
_draw_main_screen();
|
|
|
|
|
else
|
|
|
|
|
_draw_load_screen();
|
|
|
|
|
cached_node = NULL;
|
|
|
|
|
xSemaphoreGive(_lcd_mutex);
|
|
|
|
|
CLEAR_ROW_CACHE();
|
|
|
|
|
_zh_draw_main_screen();
|
|
|
|
|
_cached_node = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_index)
|
|
|
|
|
static void _zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_index)
|
|
|
|
|
{
|
|
|
|
|
last_node = current_node;
|
|
|
|
|
last_index = current_index;
|
|
|
|
|
|
|
|
|
|
_last_node = current_node;
|
|
|
|
|
_last_index = current_index;
|
|
|
|
|
zh_menu_status_t status = {0};
|
|
|
|
|
zh_menu_get(&_menu_handle, &status);
|
|
|
|
|
|
|
|
|
|
/* NEW: сохраняем при выходе из режима редактирования (BACK) */
|
|
|
|
|
if (_prev_selected && !status.is_selected)
|
|
|
|
|
if (_prev_selected == true && status.is_selected == false)
|
|
|
|
|
{
|
|
|
|
|
_zh_save_settings();
|
|
|
|
|
}
|
|
|
|
|
_prev_selected = status.is_selected;
|
|
|
|
|
|
|
|
|
|
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
|
|
|
|
|
_draw_menu(current_node, current_index, &status);
|
|
|
|
|
xSemaphoreGive(_lcd_mutex);
|
|
|
|
|
_zh_draw_main_menu(current_node, current_index, &status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------- Selectable: Temp / PW ---------- */
|
|
|
|
|
void zh_menu_selectable_item_callback(void *arg)
|
|
|
|
|
static void _zh_menu_selectable_item_callback(void *arg)
|
|
|
|
|
{
|
|
|
|
|
if (last_node == NULL || last_node->submenu == NULL)
|
|
|
|
|
if (_last_node == NULL || _last_node->submenu == NULL)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (last_index >= last_node->item_numbers)
|
|
|
|
|
if (_last_index >= _last_node->item_numbers)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
zh_menu_node_t *item = &last_node->submenu[last_index];
|
|
|
|
|
zh_menu_node_t *item = &_last_node->submenu[_last_index];
|
|
|
|
|
if (arg == NULL)
|
|
|
|
|
{
|
|
|
|
|
zh_menu_set(&_menu_handle, ZH_MENU_SHOW, NULL, 0);
|
|
|
|
@@ -615,34 +484,39 @@ void zh_menu_selectable_item_callback(void *arg)
|
|
|
|
|
zh_encoder_event_on_isr_t *encoder = (zh_encoder_event_on_isr_t *)arg;
|
|
|
|
|
int8_t dir = encoder->encoder_status == ZH_ENCODER_STATUS_INCREASED ? +1 : -1;
|
|
|
|
|
bool changed = false;
|
|
|
|
|
if (item->label == lbl_temp_on)
|
|
|
|
|
if (item->label == _label_air_condition_temp_on)
|
|
|
|
|
{
|
|
|
|
|
air_condition_temp_on = VALUE_CLAMP(air_condition_temp_on + dir, 15, 30);
|
|
|
|
|
_air_condition_temp_on = VALUE_CLAMP(_air_condition_temp_on + dir, 15, 30);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
else if (item->label == lbl_temp_off)
|
|
|
|
|
else if (item->label == _label_air_condition_temp_off)
|
|
|
|
|
{
|
|
|
|
|
air_condition_temp_off = VALUE_CLAMP(air_condition_temp_off + dir, 15, 30);
|
|
|
|
|
_air_condition_temp_off = VALUE_CLAMP(_air_condition_temp_off + dir, 15, 30);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
else if (item->label == lbl_pw_ch_1)
|
|
|
|
|
else if (item->label == _label_exit_channel_1_power)
|
|
|
|
|
{
|
|
|
|
|
exit_channel_power[0] = VALUE_CLAMP(exit_channel_power[0] + dir * 5, 0, 100);
|
|
|
|
|
_exit_channel_power[0] = VALUE_CLAMP(_exit_channel_power[0] + dir * 5, 0, 100);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
else if (item->label == lbl_pw_ch_2)
|
|
|
|
|
else if (item->label == _label_exit_channel_2_power)
|
|
|
|
|
{
|
|
|
|
|
exit_channel_power[1] = VALUE_CLAMP(exit_channel_power[1] + dir * 5, 0, 100);
|
|
|
|
|
_exit_channel_power[1] = VALUE_CLAMP(_exit_channel_power[1] + dir * 5, 0, 100);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
else if (item->label == lbl_pw_ch_3)
|
|
|
|
|
else if (item->label == _label_exit_channel_3_power)
|
|
|
|
|
{
|
|
|
|
|
exit_channel_power[2] = VALUE_CLAMP(exit_channel_power[2] + dir * 5, 0, 100);
|
|
|
|
|
_exit_channel_power[2] = VALUE_CLAMP(_exit_channel_power[2] + dir * 5, 0, 100);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
else if (item->label == lbl_pw_ch_4)
|
|
|
|
|
else if (item->label == _label_exit_channel_4_power)
|
|
|
|
|
{
|
|
|
|
|
exit_channel_power[3] = VALUE_CLAMP(exit_channel_power[3] + dir * 5, 0, 100);
|
|
|
|
|
_exit_channel_power[3] = VALUE_CLAMP(_exit_channel_power[3] + dir * 5, 0, 100);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
else if (item->label == _label_bulb_brightness)
|
|
|
|
|
{
|
|
|
|
|
_bulb_brightness = VALUE_CLAMP(_bulb_brightness + dir * 5, 0, 100);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
if (changed == true)
|
|
|
|
@@ -651,7 +525,7 @@ void zh_menu_selectable_item_callback(void *arg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void zh_menu_boot_screen_callback(void *arg)
|
|
|
|
|
static void _zh_menu_boot_screen_callback(void *arg)
|
|
|
|
|
{
|
|
|
|
|
if (arg == NULL)
|
|
|
|
|
{
|
|
|
|
@@ -660,23 +534,23 @@ void zh_menu_boot_screen_callback(void *arg)
|
|
|
|
|
}
|
|
|
|
|
zh_encoder_event_on_isr_t *encoder = (zh_encoder_event_on_isr_t *)arg;
|
|
|
|
|
int8_t dir = encoder->encoder_status == ZH_ENCODER_STATUS_INCREASED ? +1 : -1;
|
|
|
|
|
int next = boot_screen_number + dir;
|
|
|
|
|
int next = _boot_screen_number + dir;
|
|
|
|
|
if (next < 0)
|
|
|
|
|
{
|
|
|
|
|
next = ARRAY_SIZE(boot_screens) - 1;
|
|
|
|
|
next = ARRAY_SIZE(_boot_screens) - 1;
|
|
|
|
|
}
|
|
|
|
|
if (next >= ARRAY_SIZE(boot_screens))
|
|
|
|
|
if (next >= ARRAY_SIZE(_boot_screens))
|
|
|
|
|
{
|
|
|
|
|
next = 0;
|
|
|
|
|
}
|
|
|
|
|
if (next != boot_screen_number)
|
|
|
|
|
if (next != _boot_screen_number)
|
|
|
|
|
{
|
|
|
|
|
boot_screen_number = next;
|
|
|
|
|
_boot_screen_number = next;
|
|
|
|
|
zh_menu_set(&_menu_handle, ZH_MENU_SHOW, NULL, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void zh_menu_main_screen_callback(void *arg)
|
|
|
|
|
static void _zh_menu_main_screen_callback(void *arg)
|
|
|
|
|
{
|
|
|
|
|
if (arg == NULL)
|
|
|
|
|
{
|
|
|
|
@@ -685,24 +559,144 @@ void zh_menu_main_screen_callback(void *arg)
|
|
|
|
|
}
|
|
|
|
|
zh_encoder_event_on_isr_t *encoder = (zh_encoder_event_on_isr_t *)arg;
|
|
|
|
|
int8_t dir = encoder->encoder_status == ZH_ENCODER_STATUS_INCREASED ? +1 : -1;
|
|
|
|
|
int next = main_screen_number + dir;
|
|
|
|
|
int next = _main_screen_number + dir;
|
|
|
|
|
if (next < 0)
|
|
|
|
|
{
|
|
|
|
|
next = ARRAY_SIZE(main_screens) - 1;
|
|
|
|
|
next = ARRAY_SIZE(_main_screens) - 1;
|
|
|
|
|
}
|
|
|
|
|
if (next >= ARRAY_SIZE(main_screens))
|
|
|
|
|
if (next >= ARRAY_SIZE(_main_screens))
|
|
|
|
|
{
|
|
|
|
|
next = 0;
|
|
|
|
|
}
|
|
|
|
|
if (next != main_screen_number)
|
|
|
|
|
if (next != _main_screen_number)
|
|
|
|
|
{
|
|
|
|
|
main_screen_number = next;
|
|
|
|
|
_main_screen_number = next;
|
|
|
|
|
zh_menu_set(&_menu_handle, ZH_MENU_SHOW, NULL, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void zh_menu_back_command_callback(void *arg)
|
|
|
|
|
static void _zh_menu_back_command_callback(void *arg)
|
|
|
|
|
{
|
|
|
|
|
(void)arg;
|
|
|
|
|
zh_menu_set(&_menu_handle, ZH_MENU_BACK, NULL, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_draw_main_screen(void)
|
|
|
|
|
{
|
|
|
|
|
const _zh_main_screen_description_t *main_screen = &_main_screens[_main_screen_number];
|
|
|
|
|
for (uint8_t i = 0; i < LCD_TOTAL_ROWS; ++i)
|
|
|
|
|
{
|
|
|
|
|
char buf[LCD_TOTAL_COLS + 1];
|
|
|
|
|
snprintf(buf, sizeof(buf), "%-12.12s%-4s", main_screen->lines[i], main_screen->values[i]);
|
|
|
|
|
if (_row_valid[i] == false || memcmp(_row_cache[i], buf, LCD_TOTAL_COLS) != 0)
|
|
|
|
|
{
|
|
|
|
|
zh_160x_set_cursor(&_lcd_160x_handle, i, 0);
|
|
|
|
|
zh_160x_print_char(&_lcd_160x_handle, buf);
|
|
|
|
|
memcpy(_row_cache[i], buf, LCD_TOTAL_COLS);
|
|
|
|
|
_row_valid[i] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_draw_boot_screen(void)
|
|
|
|
|
{
|
|
|
|
|
const _zh_boot_screen_description_t *boot_screen = &_boot_screens[_boot_screen_number];
|
|
|
|
|
for (uint8_t i = 0; i < LCD_TOTAL_ROWS; ++i)
|
|
|
|
|
{
|
|
|
|
|
char buf[LCD_TOTAL_COLS + 1];
|
|
|
|
|
snprintf(buf, sizeof(buf), "%-*.*s", LCD_TOTAL_COLS, LCD_TOTAL_COLS, boot_screen->lines[i]);
|
|
|
|
|
if (_row_valid[i] == false || memcmp(_row_cache[i], buf, LCD_TOTAL_COLS) != 0)
|
|
|
|
|
{
|
|
|
|
|
zh_160x_set_cursor(&_lcd_160x_handle, i, 0);
|
|
|
|
|
zh_160x_print_char(&_lcd_160x_handle, buf);
|
|
|
|
|
memcpy(_row_cache[i], buf, LCD_TOTAL_COLS);
|
|
|
|
|
_row_valid[i] = true;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _zh_draw_main_menu(zh_menu_node_t *current_node, uint8_t current_index, const zh_menu_status_t *status)
|
|
|
|
|
{
|
|
|
|
|
if (current_node != _cached_node)
|
|
|
|
|
{
|
|
|
|
|
CLEAR_ROW_CACHE();
|
|
|
|
|
_cached_node = current_node;
|
|
|
|
|
}
|
|
|
|
|
for (uint8_t i = 0; i < current_node->item_numbers; ++i)
|
|
|
|
|
{
|
|
|
|
|
const char *label = current_node->submenu[i].label;
|
|
|
|
|
if (label == _label_air_condition_temp_on)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_air_condition_temp_on, sizeof(_label_air_condition_temp_on), LABEL_AIR_CONDITION_TEMP_ON "%2dC", (uint8_t)_air_condition_temp_on);
|
|
|
|
|
}
|
|
|
|
|
else if (label == _label_air_condition_temp_off)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_air_condition_temp_off, sizeof(_label_air_condition_temp_off), LABEL_AIR_CONDITION_TEMP_OFF "%2dC", (uint8_t)_air_condition_temp_off);
|
|
|
|
|
}
|
|
|
|
|
else if (label == _label_exit_channel_1_power)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_exit_channel_1_power, sizeof(_label_exit_channel_1_power), LABEL_EXIT_CHANNEL_1_POWER "%3d%%", (uint8_t)_exit_channel_power[0]);
|
|
|
|
|
}
|
|
|
|
|
else if (label == _label_exit_channel_2_power)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_exit_channel_2_power, sizeof(_label_exit_channel_2_power), LABEL_EXIT_CHANNEL_2_POWER "%3d%%", (uint8_t)_exit_channel_power[1]);
|
|
|
|
|
}
|
|
|
|
|
else if (label == _label_exit_channel_3_power)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_exit_channel_3_power, sizeof(_label_exit_channel_3_power), LABEL_EXIT_CHANNEL_3_POWER "%3d%%", (uint8_t)_exit_channel_power[2]);
|
|
|
|
|
}
|
|
|
|
|
else if (label == _label_exit_channel_4_power)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_exit_channel_4_power, sizeof(_label_exit_channel_4_power), LABEL_EXIT_CHANNEL_4_POWER "%3d%%", (uint8_t)_exit_channel_power[3]);
|
|
|
|
|
}
|
|
|
|
|
else if (label == _label_bulb_brightness)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_bulb_brightness, sizeof(_label_bulb_brightness), LABEL_BULB_BRIGHTNESS "%3d%%", (uint8_t)_bulb_brightness);
|
|
|
|
|
}
|
|
|
|
|
else if (label == _label_boot_screen)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_boot_screen, sizeof(_label_boot_screen), LABEL_BOOT_SCREEN "%s", _boot_screens[_boot_screen_number].name);
|
|
|
|
|
}
|
|
|
|
|
else if (label == _label_main_screen)
|
|
|
|
|
{
|
|
|
|
|
snprintf(_label_main_screen, sizeof(_label_main_screen), LABEL_MAIN_SCREEN "%s", _main_screens[_main_screen_number].name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
zh_menu_view_t view = {0};
|
|
|
|
|
zh_menu_get_view(&_menu_handle, &view);
|
|
|
|
|
char header[LCD_TOTAL_COLS + 1];
|
|
|
|
|
snprintf(header, sizeof(header), "%-*.*s", LCD_TOTAL_COLS, LCD_TOTAL_COLS, current_node->label);
|
|
|
|
|
if (_row_valid[0] == false || memcmp(_row_cache[0], header, LCD_TOTAL_COLS) != 0)
|
|
|
|
|
{
|
|
|
|
|
zh_160x_set_cursor(&_lcd_160x_handle, 0, 0);
|
|
|
|
|
zh_160x_print_char(&_lcd_160x_handle, header);
|
|
|
|
|
memcpy(_row_cache[0], header, LCD_TOTAL_COLS);
|
|
|
|
|
_row_valid[0] = true;
|
|
|
|
|
};
|
|
|
|
|
for (uint8_t i = 0; i < LCD_TOTAL_ROWS - 1; ++i)
|
|
|
|
|
{
|
|
|
|
|
char buf[LCD_TOTAL_COLS + 1];
|
|
|
|
|
if (i < view.visible_count)
|
|
|
|
|
{
|
|
|
|
|
uint8_t idx = view.first_visible + i;
|
|
|
|
|
bool is_cursor = (idx == current_index);
|
|
|
|
|
bool is_edit = is_cursor && status->is_selected && current_node->submenu[idx].selectable;
|
|
|
|
|
bool arrow_up = (i == 0) && (view.first_visible > 0);
|
|
|
|
|
bool arrow_down = (i == (view.visible_count - 1)) && ((uint8_t)(view.first_visible + view.visible_count) < current_node->item_numbers);
|
|
|
|
|
uint8_t text_cols = LCD_TOTAL_COLS - 1 - (arrow_up || arrow_down);
|
|
|
|
|
snprintf(buf, sizeof(buf), "%c%-*.*s%c", is_edit == true ? '*' : (is_cursor == true ? '>' : ' '), text_cols, text_cols, current_node->submenu[idx].label, arrow_up == true ? '^' : (arrow_down == true ? 'v' : ' '));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
memset(buf, ' ', LCD_TOTAL_COLS);
|
|
|
|
|
buf[LCD_TOTAL_COLS] = '\0';
|
|
|
|
|
}
|
|
|
|
|
if (_row_valid[i + 1] == false || memcmp(_row_cache[i + 1], buf, LCD_TOTAL_COLS) != 0)
|
|
|
|
|
{
|
|
|
|
|
zh_160x_set_cursor(&_lcd_160x_handle, i + 1, 0);
|
|
|
|
|
zh_160x_print_char(&_lcd_160x_handle, buf);
|
|
|
|
|
memcpy(_row_cache[i + 1], buf, LCD_TOTAL_COLS);
|
|
|
|
|
_row_valid[i + 1] = true;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|