Improve watchdog monitoring and CLI

This commit is contained in:
2025-12-19 11:01:33 +02:00
parent 89ded8b119
commit e9933da1a4
11 changed files with 970 additions and 285 deletions

View File

@@ -11,6 +11,7 @@
#include "dcdc_controller.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
@@ -18,8 +19,12 @@
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "ws2812_status.h"
#include "watch_config.h"
#include "esp_system.h"
#include "ina226_monitor.h"
#include "uart_mux.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/soc.h"
#define CLI_LINE_MAX_LEN 128
#define CLI_QUEUE_LEN 8
@@ -45,6 +50,10 @@ static bool s_host_ready;
static void usb_cli_task(void *arg);
static void usb_cli_process_line(char *line);
static void usb_cli_prompt(void);
static void usb_cli_enter_bootloader(void);
static void usb_cli_handle_config(char *args);
static void usb_cli_print_config(void);
static void usb_cli_handle_reset(void);
static void usb_cli_write_raw(const char *data, size_t len)
{
@@ -112,10 +121,16 @@ static void usb_cli_print_status(void)
usb_cli_printf("\r\nСтан каналів:\r\n");
for (size_t i = 0; i < dcdc_channel_count(); ++i) {
dcdc_channel_t ch = (dcdc_channel_t)i;
usb_cli_printf(" - CH%u: %s (GPIO %d)\r\n",
uart_mux_channel_stats_t stats = {0};
if (uart_mux_ready()) {
uart_mux_get_channel_stats(i, &stats);
}
usb_cli_printf(" - CH%u: %s (GPIO %d) | miss=%u restart=%u\r\n",
(unsigned)i,
dcdc_get_state(ch) ? "ON" : "OFF",
(int)dcdc_get_gpio(ch));
(int)dcdc_get_gpio(ch),
(unsigned)stats.missed_heartbeats,
(unsigned)stats.restart_count);
}
}
@@ -124,6 +139,18 @@ static void usb_cli_prompt(void)
usb_cli_write_raw(PROMPT, strlen(PROMPT));
}
static void usb_cli_enter_bootloader(void)
{
usb_cli_printf("\r\nПерехід у режим прошивки esptool...\r\n"
"Після перезавантаження з’явиться ROM-порт USB CDC/Serial.\r\n"
"Запустіть esptool.py або idf.py flash та прошийте пристрій. "
"Для виходу з bootloader виконайте 'esptool.py --after hard_reset reset' або перезавантажте живлення.\r\n");
vTaskDelay(pdMS_TO_TICKS(100));
tinyusb_driver_uninstall();
REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
esp_restart();
}
static void usb_cli_handle_switch(const char *cmd, char *args)
{
dcdc_channel_t channel;
@@ -246,14 +273,22 @@ static void usb_cli_process_line(char *line)
if (strcasecmp(cmd, "help") == 0) {
usb_cli_printf(
"\r\nДоступні команди:\r\n"
" help - показати довідку\r\n"
" status - стан всіх каналів\r\n"
" enable <n> - увімкнути канал n\r\n"
" disable <n> - вимкнути канал n\r\n"
" toggle <n> - перемкнути канал n\r\n"
" sense [n] - показати напругу/струм/потужність (опц. канал)\r\n"
" uart send <n> <msg> - відправити повідомлення Pi n\r\n"
" uart read <n> [len] - прочитати дані з Pi n\r\n");
" help - показати цю довідку\r\n"
" status - показати стан усіх каналів DCDC\r\n"
" enable <n> - увімкнути канал n (0..4)\r\n"
" disable <n> - вимкнути канал n\r\n"
" toggle <n> - перемкнути канал n\r\n"
" sense - виміряти напругу/струм/потужність INA226\r\n"
" uart send <n> <msg> - надіслати текстове повідомлення в Raspberry Pi n\r\n"
" uart read <n> [len] - прочитати до [len] байт відповіді від Raspberry Pi n\r\n"
" config show - показати збережені таймінги heartbeat/DCDC\r\n"
" config set hb_period <сек> - змінити інтервал відправки heartbeat\r\n"
" config set dcdc_off <сек> - задати тривалість вимкнення DCDC при рестарті\r\n"
" config set hb_start <сек> - налаштувати затримку перед стартом опитування після boot\r\n"
" config set hb_monitor <0|1> - увімкнути/вимкнути контроль відповіді heartbeat\r\n"
" config set hb_miss <шт> - кількість пропусків відповіді до рестарту каналу\r\n"
" reset - м'яко перезавантажити ESP32-S3\r\n"
" bootloader - перезавантажити ESP32-S3 у ROM bootloader для esptool\r\n");
} else if (strcasecmp(cmd, "status") == 0) {
usb_cli_print_status();
} else if (strcasecmp(cmd, "enable") == 0 ||
@@ -264,11 +299,109 @@ static void usb_cli_process_line(char *line)
usb_cli_handle_sense(save_ptr);
} else if (strcasecmp(cmd, "uart") == 0) {
usb_cli_handle_uart(save_ptr);
} else if (strcasecmp(cmd, "config") == 0) {
usb_cli_handle_config(save_ptr);
} else if (strcasecmp(cmd, "reset") == 0) {
usb_cli_handle_reset();
} else if (strcasecmp(cmd, "bootloader") == 0) {
usb_cli_enter_bootloader();
} else {
usb_cli_printf("\r\nНевідома команда '%s'\r\n", cmd);
}
}
static void usb_cli_print_config(void)
{
const watch_config_t *cfg = watch_config_get();
usb_cli_printf(
"\r\nПоточні налаштування:\r\n"
" hb_period: %u с\r\n"
" dcdc_off: %u с\r\n"
" hb_start_delay: %u с\r\n"
" hb_monitor: %s\r\n"
" hb_miss_limit: %u зап.\r\n",
(unsigned)cfg->heartbeat_period_sec,
(unsigned)cfg->dcdc_restart_off_sec,
(unsigned)cfg->heartbeat_start_delay_sec,
cfg->heartbeat_monitor_enabled ? "on" : "off",
(unsigned)cfg->heartbeat_miss_limit);
}
static void usb_cli_handle_config(char *args)
{
if (!args || *args == '\0') {
usb_cli_print_config();
return;
}
char *ctx = NULL;
char *action = strtok_r(args, " ", &ctx);
if (!action || strcasecmp(action, "show") == 0) {
usb_cli_print_config();
return;
}
if (strcasecmp(action, "set") != 0) {
usb_cli_printf("\r\nВикористання: config show | config set <hb_period|dcdc_off|hb_start|hb_monitor|hb_miss> <знач>\r\n");
return;
}
char *param = strtok_r(NULL, " ", &ctx);
char *value_str = strtok_r(NULL, " ", &ctx);
if (!param || !value_str) {
usb_cli_printf("\r\nВкажіть параметр і значення в секундах\r\n");
return;
}
uint32_t value = (uint32_t)strtoul(value_str, NULL, 10);
watch_config_t new_cfg = *watch_config_get();
if (strcasecmp(param, "hb_period") == 0) {
if (value == 0) {
usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
return;
}
new_cfg.heartbeat_period_sec = value;
} else if (strcasecmp(param, "dcdc_off") == 0) {
if (value == 0) {
usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
return;
}
new_cfg.dcdc_restart_off_sec = value;
} else if (strcasecmp(param, "hb_start") == 0 ||
strcasecmp(param, "hb_start_delay") == 0) {
if (value == 0) {
usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
return;
}
new_cfg.heartbeat_start_delay_sec = value;
} else if (strcasecmp(param, "hb_monitor") == 0) {
if (value != 0 && value != 1) {
usb_cli_printf("\r\nhb_monitor приймає 0 або 1\r\n");
return;
}
new_cfg.heartbeat_monitor_enabled = (value != 0);
} else if (strcasecmp(param, "hb_miss") == 0 ||
strcasecmp(param, "hb_miss_limit") == 0) {
if (value == 0) {
usb_cli_printf("\r\nhb_miss має бути більше нуля\r\n");
return;
}
new_cfg.heartbeat_miss_limit = value;
} else {
usb_cli_printf("\r\nНевідомий параметр '%s'\r\n", param);
return;
}
esp_err_t err = watch_config_save(&new_cfg);
if (err == ESP_OK) {
usb_cli_printf("\r\nПараметр оновлено\r\n");
usb_cli_print_config();
} else {
usb_cli_printf("\r\nПомилка збереження конфігурації: %s\r\n", esp_err_to_name(err));
}
}
static void usb_cli_task(void *arg)
{
usb_cli_msg_t msg;
@@ -285,14 +418,17 @@ static void usb_cli_task(void *arg)
usb_cli_process_line(line);
line_len = 0;
}
usb_cli_write_raw("\r\n", 2);
usb_cli_prompt();
} else if (ch == 0x7F || ch == '\b') {
if (line_len > 0) {
line_len--;
usb_cli_write_raw("\b \b", 3);
}
} else if (isprint((unsigned char)ch)) {
if (line_len < CLI_LINE_MAX_LEN - 1) {
line[line_len++] = ch;
usb_cli_write_raw(&ch, 1);
} else {
usb_cli_printf("\r\nРядок занадто довгий\r\n");
line_len = 0;
@@ -386,3 +522,9 @@ esp_err_t usb_cdc_cli_init(void)
ESP_LOGI(TAG, "USB CDC CLI ініціалізовано");
return ESP_OK;
}
static void usb_cli_handle_reset(void)
{
usb_cli_printf("\r\nПерезавантаження пристрою...\r\n");
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
}