Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b57a9eac15 | ||
|
|
2a7b9e17bc |
No files matched your search
+160
-194
@@ -1,17 +1,18 @@
|
||||
#include "zh_sim_race_button_box.h"
|
||||
|
||||
|
||||
#define ENCODER_NUMBER 0x01
|
||||
#define LCD_VIEWPORT_ROWS 3
|
||||
#define LCD_COLS 16
|
||||
#define LCD_TOTAL_ROWS 4
|
||||
// Хэндлы - функции - переменные
|
||||
// #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 */
|
||||
// #define NVS_NAMESPACE "sim_race_box" /* NEW */
|
||||
// #define NVS_KEY_SETTINGS "settings" /* NEW */
|
||||
|
||||
static const char *TAG = "sim_race_box";
|
||||
|
||||
@@ -25,20 +26,20 @@ int16_t main_debug_data[VIEW_CELLS] = {45, -42, 152, 12, 0};
|
||||
int16_t main_blank_data[VIEW_CELLS] = {0, 0, 0, 0, 0};
|
||||
|
||||
/* ---------- Дескрипторы экранов ---------- */
|
||||
typedef struct
|
||||
{
|
||||
const char *name;
|
||||
const char *const labels[VIEW_ROWS];
|
||||
int16_t *data;
|
||||
} main_screen_desc_t;
|
||||
// typedef struct
|
||||
// {
|
||||
// const char *name;
|
||||
// const char *const labels[VIEW_ROWS];
|
||||
// int16_t *data;
|
||||
// } main_screen_desc_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *name;
|
||||
const char *const lines[VIEW_ROWS];
|
||||
} load_screen_desc_t;
|
||||
// typedef struct
|
||||
// {
|
||||
// const char *name;
|
||||
// const char *const lines[VIEW_ROWS];
|
||||
// } load_screen_desc_t;
|
||||
|
||||
static const main_screen_desc_t main_screens[MAIN_SCREEN_COUNT] = {
|
||||
static const zh_main_screen_description_t main_screens[MAIN_SCREEN_COUNT] = {
|
||||
{"Race", {"SPD", "RPM", "GER", "LAP"}, main_race_data},
|
||||
{"Engine", {"OIL", "WTR", "BAT", "FUL"}, main_engine_data},
|
||||
{"Times", {"LAP", "BST", "LST", "DLT"}, main_times_data},
|
||||
@@ -46,7 +47,7 @@ static const main_screen_desc_t main_screens[MAIN_SCREEN_COUNT] = {
|
||||
{"Blank", {"---", "---", "---", "---"}, main_blank_data},
|
||||
};
|
||||
|
||||
static const load_screen_desc_t load_screens[LOAD_SCREEN_COUNT] = {
|
||||
static const zh_boot_screen_description_t load_screens[LOAD_SCREEN_COUNT] = {
|
||||
{"Boot", {"Booting...", "RAM: OK", "I2C: OK", "Starting..."}},
|
||||
{"Version", {"FW v1.0", "Build 2025-10", "ESP-IDF v5.4", "zh_menu v1.2"}},
|
||||
{"Diag", {"I2C: OK", "LCD: OK", "ENC: OK", "All: OK"}},
|
||||
@@ -70,116 +71,82 @@ char lbl_pw_ch_2[MENU_LABEL_LEN] = "PW CH 2 100%";
|
||||
char lbl_pw_ch_3[MENU_LABEL_LEN] = "PW CH 3 100%";
|
||||
char lbl_pw_ch_4[MENU_LABEL_LEN] = "PW CH 4 100%";
|
||||
|
||||
int16_t g_temp_on = 15;
|
||||
int16_t g_temp_off = 35;
|
||||
uint8_t g_pw_ch[4] = {100, 100, 100, 100};
|
||||
|
||||
/* ---------- 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 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_COLS];
|
||||
static bool _row_valid[LCD_TOTAL_ROWS] = {false, false, false, false};
|
||||
|
||||
/* ---------- Мьютекс LCD ---------- */
|
||||
static SemaphoreHandle_t _lcd_mutex = NULL;
|
||||
|
||||
void zh_encoder_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data);
|
||||
static void zh_load_settings(void);
|
||||
static void zh_save_settings(void);
|
||||
|
||||
/* ============================================================
|
||||
* NEW: NVS blob — все сохраняемые поля в одной структуре
|
||||
* ============================================================ */
|
||||
typedef struct
|
||||
{
|
||||
int16_t temp_on;
|
||||
int16_t temp_off;
|
||||
uint8_t pw_ch[4];
|
||||
uint8_t boot_screen;
|
||||
uint8_t main_screen;
|
||||
} settings_blob_t;
|
||||
void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||
|
||||
/* ============================================================
|
||||
* NEW: загрузка настроек из NVS.
|
||||
* Безопасно к отсутствию записи, к несовпадению размера blob
|
||||
* и к устаревшим индексам экранов (выходящим за границы).
|
||||
* ============================================================ */
|
||||
static void _load_settings(void)
|
||||
/* ---------- Состояние меню ---------- */
|
||||
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 void zh_load_settings(void)
|
||||
{
|
||||
nvs_handle_t nvs;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs);
|
||||
if (err != ESP_OK)
|
||||
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs) != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "No saved settings (%s), using defaults", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
settings_blob_t blob;
|
||||
size_t size = sizeof(blob);
|
||||
err = nvs_get_blob(nvs, NVS_KEY_SETTINGS, &blob, &size);
|
||||
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)
|
||||
{
|
||||
nvs_close(nvs);
|
||||
|
||||
if (err != ESP_OK || size != sizeof(blob))
|
||||
{
|
||||
ESP_LOGW(TAG, "Settings blob missing or size mismatch (%s)", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Защита от устаревших индексов после обновления прошивки */
|
||||
if (blob.temp_on >= 0 && blob.temp_on <= 100)
|
||||
g_temp_on = blob.temp_on;
|
||||
if (blob.temp_off >= 0 && blob.temp_off <= 100)
|
||||
g_temp_off = blob.temp_off;
|
||||
memcpy(g_pw_ch, blob.pw_ch, sizeof(g_pw_ch));
|
||||
|
||||
if (blob.boot_screen < LOAD_SCREEN_COUNT)
|
||||
_boot_screen = blob.boot_screen;
|
||||
if (blob.main_screen < MAIN_SCREEN_COUNT)
|
||||
_main_screen = blob.main_screen;
|
||||
|
||||
ESP_LOGI(TAG, "Settings loaded: temp_on=%d temp_off=%d pw=[%u,%u,%u,%u] boot=%d main=%d",
|
||||
g_temp_on, g_temp_off, g_pw_ch[0], g_pw_ch[1], g_pw_ch[2], g_pw_ch[3],
|
||||
_boot_screen, _main_screen);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* NEW: сохранение настроек в NVS (единый blob, атомарно).
|
||||
* ============================================================ */
|
||||
static void _save_settings(void)
|
||||
{
|
||||
nvs_handle_t nvs;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "nvs_open RW failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
settings_blob_t blob = {
|
||||
.temp_on = g_temp_on,
|
||||
.temp_off = g_temp_off,
|
||||
.boot_screen = (uint8_t)_boot_screen,
|
||||
.main_screen = (uint8_t)_main_screen,
|
||||
};
|
||||
memcpy(blob.pw_ch, g_pw_ch, sizeof(g_pw_ch));
|
||||
|
||||
err = nvs_set_blob(nvs, NVS_KEY_SETTINGS, &blob, sizeof(blob));
|
||||
if (err == ESP_OK)
|
||||
err = nvs_commit(nvs);
|
||||
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;
|
||||
}
|
||||
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE(TAG, "Failed to save settings: %s", esp_err_to_name(err));
|
||||
else
|
||||
ESP_LOGI(TAG, "Settings saved");
|
||||
static void zh_save_settings(void)
|
||||
{
|
||||
nvs_handle_t nvs = 0;
|
||||
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs) != ESP_OK)
|
||||
{
|
||||
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;
|
||||
nvs_set_blob(nvs, NVS_KEY_SETTINGS, &setting_data_blob, sizeof(setting_data_blob));
|
||||
nvs_commit(nvs);
|
||||
nvs_close(nvs);
|
||||
}
|
||||
|
||||
/* ---------- Утилиты ---------- */
|
||||
@@ -193,20 +160,20 @@ 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 _invalidate_row_cache(void)
|
||||
static void _invalidaterow_cache(void)
|
||||
{
|
||||
memset(_row_valid, 0, sizeof(_row_valid));
|
||||
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_COLS);
|
||||
buf[LCD_COLS] = '\0';
|
||||
memset(buf, ' ', LCD_TOTAL_COLS);
|
||||
buf[LCD_TOTAL_COLS] = '\0';
|
||||
buf[0] = edit ? '*' : (selected ? '>' : ' ');
|
||||
|
||||
uint8_t text_cols = (arrow_up || arrow_down) ? (LCD_COLS - 2) : (LCD_COLS - 1);
|
||||
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;
|
||||
@@ -214,73 +181,77 @@ static void _format_row(char *buf, const char *label, bool selected, bool edit,
|
||||
|
||||
if (arrow_up)
|
||||
{
|
||||
buf[LCD_COLS - 1] = '^';
|
||||
buf[LCD_TOTAL_COLS - 1] = '^';
|
||||
}
|
||||
else if (arrow_down)
|
||||
{
|
||||
buf[LCD_COLS - 1] = 'v';
|
||||
buf[LCD_TOTAL_COLS - 1] = 'v';
|
||||
}
|
||||
}
|
||||
|
||||
static void _format_main_row(char *buf, const char *label, int16_t value)
|
||||
{
|
||||
memset(buf, ' ', LCD_COLS);
|
||||
buf[LCD_COLS] = '\0';
|
||||
snprintf(buf, LCD_COLS + 1, "%3s:%5d", label, (int)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)
|
||||
{
|
||||
memset(buf, ' ', LCD_COLS);
|
||||
buf[LCD_COLS] = '\0';
|
||||
size_t len = strlen(line);
|
||||
if (len > LCD_COLS)
|
||||
len = LCD_COLS;
|
||||
memcpy(buf, line, len);
|
||||
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_COLS) == 0)
|
||||
if (row_valid[row] && memcmp(row_cache[row], text, LCD_TOTAL_COLS) == 0)
|
||||
return;
|
||||
|
||||
zh_160x_set_cursor(&lcd_handle, row, 0);
|
||||
zh_160x_print_char(&lcd_handle, text);
|
||||
memcpy(_row_cache[row], text, LCD_COLS);
|
||||
_row_valid[row] = true;
|
||||
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)g_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)g_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)g_pw_ch[0]);
|
||||
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)g_pw_ch[1]);
|
||||
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)g_pw_ch[2]);
|
||||
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)g_pw_ch[3]);
|
||||
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)
|
||||
{
|
||||
@@ -296,34 +267,33 @@ static void _refresh_dynamic_labels(zh_menu_node_t *node)
|
||||
/* ---------- Отрисовка экранов ---------- */
|
||||
static void _draw_main_screen(void)
|
||||
{
|
||||
const main_screen_desc_t *s = &main_screens[_main_screen];
|
||||
for (uint8_t i = 0; i < VIEW_ROWS; ++i)
|
||||
const zh_main_screen_description_t *s = &main_screens[_main_screen];
|
||||
for (uint8_t i = 0; i < LCD_TOTAL_ROWS; ++i)
|
||||
{
|
||||
char buf[LCD_COLS + 1];
|
||||
_format_main_row(buf, s->labels[i], s->data[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 load_screen_desc_t *s = &load_screens[_boot_screen];
|
||||
for (uint8_t i = 0; i < VIEW_ROWS; ++i)
|
||||
const zh_boot_screen_description_t *s = &load_screens[_boot_screen];
|
||||
for (uint8_t i = 0; i < LCD_TOTAL_ROWS; ++i)
|
||||
{
|
||||
char buf[LCD_COLS + 1];
|
||||
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)
|
||||
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)
|
||||
if (current_node != cached_node)
|
||||
{
|
||||
_invalidate_row_cache();
|
||||
_cached_node = current_node;
|
||||
_invalidaterow_cache();
|
||||
cached_node = current_node;
|
||||
}
|
||||
|
||||
_refresh_dynamic_labels(current_node);
|
||||
@@ -331,35 +301,37 @@ static void _draw_menu(zh_menu_node_t *current_node, uint8_t current_index,
|
||||
zh_menu_view_t view = {0};
|
||||
zh_menu_get_view(&menu_handle, &view);
|
||||
|
||||
char header[LCD_COLS + 1];
|
||||
memset(header, ' ', LCD_COLS);
|
||||
header[LCD_COLS] = '\0';
|
||||
size_t hlen = strlen(current_node->label);
|
||||
if (hlen > LCD_COLS)
|
||||
hlen = LCD_COLS;
|
||||
memcpy(header, current_node->label, hlen);
|
||||
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_VIEWPORT_ROWS; ++i)
|
||||
for (uint8_t i = 0; i < LCD_TOTAL_ROWS - 1; ++i)
|
||||
{
|
||||
char buf[LCD_COLS + 1];
|
||||
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 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);
|
||||
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_COLS);
|
||||
buf[LCD_COLS] = '\0';
|
||||
memset(buf, ' ', LCD_TOTAL_COLS);
|
||||
buf[LCD_TOTAL_COLS] = '\0';
|
||||
}
|
||||
|
||||
_lcd_put_row((uint8_t)(i + 1), buf);
|
||||
@@ -379,7 +351,7 @@ static void _boot_task(void *pv)
|
||||
{
|
||||
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
|
||||
zh_160x_lcd_clear(&lcd_handle); /* NEW: очистка перед первым main view */
|
||||
_invalidate_row_cache();
|
||||
_invalidaterow_cache();
|
||||
_draw_main_screen();
|
||||
xSemaphoreGive(_lcd_mutex);
|
||||
}
|
||||
@@ -404,11 +376,10 @@ void app_main(void)
|
||||
}
|
||||
ESP_ERROR_CHECK(nvs_err);
|
||||
|
||||
_load_settings(); /* до отрисовки splash и до чтения _boot_screen */
|
||||
zh_load_settings();
|
||||
|
||||
esp_event_loop_create_default();
|
||||
esp_event_handler_instance_register(ZH_ENCODER, ESP_EVENT_ANY_ID,
|
||||
&zh_encoder_event_handler, NULL, NULL);
|
||||
esp_event_handler_instance_register(ZH_ENCODER, ESP_EVENT_ANY_ID, &zh_encoder_event_handler, NULL, NULL);
|
||||
|
||||
/* ---------- Encoder ---------- */
|
||||
zh_encoder_init_config_t encoder_config = ZH_ENCODER_INIT_CONFIG_DEFAULT();
|
||||
@@ -455,7 +426,7 @@ void app_main(void)
|
||||
menu_config.queue_size = 5;
|
||||
menu_config.root = &main_menu;
|
||||
menu_config.loop = false;
|
||||
menu_config.viewport_rows = LCD_VIEWPORT_ROWS;
|
||||
menu_config.viewport_rows = LCD_TOTAL_ROWS - 1;
|
||||
menu_config.enter_cb = &zh_menu_enter_main_menu_callback;
|
||||
menu_config.exit_cb = &zh_menu_exit_main_menu_callback;
|
||||
menu_config.change_cb = &zh_menu_change_menu_callback;
|
||||
@@ -464,14 +435,13 @@ void app_main(void)
|
||||
/* ---------- Splash: сохранённый load-экран ---------- */
|
||||
_boot_done = false;
|
||||
_prev_selected = false;
|
||||
_invalidate_row_cache();
|
||||
_invalidaterow_cache();
|
||||
_draw_load_screen();
|
||||
xTaskCreate(_boot_task, "boot", 3072, NULL, 3, NULL);
|
||||
}
|
||||
|
||||
/* ---------- Encoder event handler ---------- */
|
||||
void zh_encoder_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
{
|
||||
(void)arg;
|
||||
zh_menu_status_t menu_status = {0};
|
||||
@@ -483,8 +453,7 @@ void zh_encoder_event_handler(void *arg, esp_event_base_t event_base,
|
||||
zh_encoder_button_event_on_isr_t *button_event = event_data;
|
||||
if (button_event->button_status == true)
|
||||
{
|
||||
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)
|
||||
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)
|
||||
: zh_menu_set(&menu_handle, ZH_MENU_BACK, NULL, 0);
|
||||
}
|
||||
break;
|
||||
@@ -496,14 +465,11 @@ void zh_encoder_event_handler(void *arg, esp_event_base_t event_base,
|
||||
{
|
||||
if (menu_status.is_selected == true)
|
||||
{
|
||||
zh_menu_set(&menu_handle, ZH_MENU_SELECT, encoder_event,
|
||||
sizeof(zh_encoder_event_on_isr_t));
|
||||
zh_menu_set(&menu_handle, ZH_MENU_SELECT, encoder_event, sizeof(zh_encoder_event_on_isr_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
encoder_event->encoder_status == true
|
||||
? zh_menu_set(&menu_handle, ZH_MENU_UP, NULL, 0)
|
||||
: zh_menu_set(&menu_handle, ZH_MENU_DOWN, NULL, 0);
|
||||
encoder_event->encoder_status == true ? zh_menu_set(&menu_handle, ZH_MENU_UP, NULL, 0) : zh_menu_set(&menu_handle, ZH_MENU_DOWN, NULL, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -518,8 +484,8 @@ void zh_menu_enter_main_menu_callback(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
|
||||
_cached_node = NULL;
|
||||
_invalidate_row_cache();
|
||||
cached_node = NULL;
|
||||
_invalidaterow_cache();
|
||||
xSemaphoreGive(_lcd_mutex);
|
||||
}
|
||||
|
||||
@@ -527,24 +493,24 @@ void zh_menu_exit_main_menu_callback(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
/* NEW: страховочное сохранение при выходе из меню */
|
||||
_save_settings();
|
||||
zh_save_settings();
|
||||
_prev_selected = false;
|
||||
|
||||
xSemaphoreTake(_lcd_mutex, portMAX_DELAY);
|
||||
zh_160x_lcd_clear(&lcd_handle);
|
||||
_invalidate_row_cache();
|
||||
_invalidaterow_cache();
|
||||
if (_boot_done)
|
||||
_draw_main_screen();
|
||||
else
|
||||
_draw_load_screen();
|
||||
_cached_node = NULL;
|
||||
cached_node = NULL;
|
||||
xSemaphoreGive(_lcd_mutex);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -552,7 +518,7 @@ void zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_
|
||||
/* NEW: сохраняем при выходе из режима редактирования (BACK) */
|
||||
if (_prev_selected && !status.is_selected)
|
||||
{
|
||||
_save_settings();
|
||||
zh_save_settings();
|
||||
}
|
||||
_prev_selected = status.is_selected;
|
||||
|
||||
@@ -564,12 +530,12 @@ void zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_
|
||||
/* ---------- Selectable: Temp / PW ---------- */
|
||||
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)
|
||||
{
|
||||
@@ -583,32 +549,32 @@ void zh_menu_selectable_item_callback(void *arg)
|
||||
|
||||
if (item->label == lbl_temp_on)
|
||||
{
|
||||
g_temp_on = _clamp_i16(g_temp_on + dir, 10, 50);
|
||||
air_condition_temp_on = _clamp_i16(air_condition_temp_on + dir, 15, 30);
|
||||
changed = true;
|
||||
}
|
||||
else if (item->label == lbl_temp_off)
|
||||
{
|
||||
g_temp_off = _clamp_i16(g_temp_off + dir, 10, 50);
|
||||
air_condition_temp_off = _clamp_i16(air_condition_temp_off + dir, 15, 30);
|
||||
changed = true;
|
||||
}
|
||||
else if (item->label == lbl_pw_ch_1)
|
||||
{
|
||||
g_pw_ch[0] = _clamp_u8(g_pw_ch[0] + dir * 5, 0, 100);
|
||||
exit_channel_power[0] = _clamp_u8(exit_channel_power[0] + dir * 5, 0, 100);
|
||||
changed = true;
|
||||
}
|
||||
else if (item->label == lbl_pw_ch_2)
|
||||
{
|
||||
g_pw_ch[1] = _clamp_u8(g_pw_ch[1] + dir * 5, 0, 100);
|
||||
exit_channel_power[1] = _clamp_u8(exit_channel_power[1] + dir * 5, 0, 100);
|
||||
changed = true;
|
||||
}
|
||||
else if (item->label == lbl_pw_ch_3)
|
||||
{
|
||||
g_pw_ch[2] = _clamp_u8(g_pw_ch[2] + dir * 5, 0, 100);
|
||||
exit_channel_power[2] = _clamp_u8(exit_channel_power[2] + dir * 5, 0, 100);
|
||||
changed = true;
|
||||
}
|
||||
else if (item->label == lbl_pw_ch_4)
|
||||
{
|
||||
g_pw_ch[3] = _clamp_u8(g_pw_ch[3] + dir * 5, 0, 100);
|
||||
exit_channel_power[3] = _clamp_u8(exit_channel_power[3] + dir * 5, 0, 100);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,21 @@
|
||||
#include "zh_encoder.h"
|
||||
#include "zh_ota_server.h"
|
||||
|
||||
#define ENCODER_NUMBER 0x01
|
||||
#define MENU_VIEWPORT_ROWS 3
|
||||
#define ENCODER_NUMBER 1
|
||||
|
||||
#define LCD_TOTAL_ROWS 4
|
||||
#define LCD_TOTAL_COLS 16
|
||||
// #define LCD_VIEWPORT_ROWS (LCD_TOTAL_ROWS - 1)
|
||||
|
||||
#define MENU_LABEL_LEN 17
|
||||
|
||||
/* ---------- Размеры view ---------- */
|
||||
#define VIEW_CELLS 5 /* ячеек в массиве данных (main) */
|
||||
#define VIEW_ROWS 4 /* строк отрисовывается на LCD */
|
||||
|
||||
#define NVS_NAMESPACE "sim_race_box" /* NEW */
|
||||
#define NVS_KEY_SETTINGS "settings" /* NEW */
|
||||
|
||||
/* ---------- Пул основных экранов (idle) — 5 массивов ---------- */
|
||||
extern int16_t main_race_data[VIEW_CELLS];
|
||||
extern int16_t main_engine_data[VIEW_CELLS];
|
||||
@@ -33,9 +40,9 @@ extern char lbl_boot_screen[MENU_LABEL_LEN]; /* "B: <имя load-экрана>"
|
||||
extern char lbl_main_screen[MENU_LABEL_LEN]; /* "M: <имя main-экрана>" */
|
||||
|
||||
/* ---------- Переменные значений ---------- */
|
||||
extern int16_t g_temp_on;
|
||||
extern int16_t g_temp_off;
|
||||
extern uint8_t g_pw_ch[4];
|
||||
// extern int16_t g_temp_on;
|
||||
// extern int16_t g_temp_off;
|
||||
// extern uint8_t g_pw_ch[4];
|
||||
|
||||
/* ---------- Callbacks ---------- */
|
||||
void zh_menu_enter_main_menu_callback(void *arg);
|
||||
@@ -52,6 +59,29 @@ extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t air_condition_temp_on;
|
||||
uint8_t air_condition_temp_off;
|
||||
uint8_t exit_channel_power[4];
|
||||
uint8_t bulb_brightness;
|
||||
uint8_t boot_screen_number;
|
||||
uint8_t main_screen_number;
|
||||
} zh_nvs_setting_data_blob_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *name;
|
||||
const char *const lines[LCD_TOTAL_ROWS];
|
||||
int16_t *data;
|
||||
} zh_main_screen_description_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *name;
|
||||
const char *const lines[LCD_TOTAL_ROWS];
|
||||
} zh_boot_screen_description_t;
|
||||
|
||||
zh_menu_node_t main_menu = {
|
||||
.label = "Settings",
|
||||
.submenu = (zh_menu_node_t[]){
|
||||
|
||||
Reference in new issue
Block a user