/** * @file cli_core.c * @brief CLI core implementation: command table + handlers + line execution. * Transport-agnostic: all output goes through the current cli_out_fn * (default: USART1 DMA TX). The UART frontend lives in app_cli.c; * the BLE frame frontend (app_ble_proto.c) switches the output to * framed CLI_RSP responses around cli_exec_line(). */ #include "cli_core.h" #include "app_version.h" #include "bsp_usart.h" #include "app_gpio.h" #include "app_info.h" #include "app_ble_proto.h" #include "app_params.h" #include "app_bootset.h" #include "app_cli.h" #include "n32wb03x.h" /* SystemTrimValueGet (temp debug) */ #include #include #include #include "FreeRTOS.h" #include "task.h" #define CLI_TX_BUF_SIZE 96 #define CLI_MAX_ARGS 8 #define CLI_RESET_DELAY_MS 200u /* let the reply drain before NVIC_SystemReset */ typedef void (*CliCmdHandler_t)(int argc, char* argv[]); typedef struct { const char* name; const char* help; CliCmdHandler_t handler; } CliCmd_t; static char s_txBuf[CLI_TX_BUF_SIZE]; static cli_out_fn s_out = NULL; /* NULL = default dual output */ /* ------------------------------------------------------------------ */ /* Output */ /* ------------------------------------------------------------------ */ /** * @brief Default output: USART1 DMA TX. BLE-originated commands temporarily * switch the output to framed CLI_RSP (proto_cli_out) via * cli_set_output() around cli_exec_line(). * @note Strings must contain explicit "\r\n" (no auto CR like fputc). */ static void cli_out_uart(const char* str) { bsp_usart_write_dma((const uint8_t*)str, (uint16_t)strlen(str)); } void cli_set_output(cli_out_fn out) { s_out = out; } void cli_write(const char* str) { if (s_out != NULL) { s_out(str); } else { cli_out_uart(str); } } void cli_printf(const char* fmt, ...) { va_list args; va_start(args, fmt); vsnprintf(s_txBuf, CLI_TX_BUF_SIZE, fmt, args); va_end(args); s_txBuf[CLI_TX_BUF_SIZE - 1] = '\0'; cli_write(s_txBuf); } /* ------------------------------------------------------------------ */ /* Built-in commands */ /* ------------------------------------------------------------------ */ static void CmdHelp(int argc, char* argv[]); /** * @brief "version": show firmware and clock info. */ static void CmdVersion(int argc, char* argv[]) { (void)argc; (void)argv; cli_printf("firmware version: %s\r\n", APP_FW_VERSION); cli_write("mcm-ddc-ble: BLE(rdtss) + FreeRTOS + UART(DMA) + CLI\r\n"); cli_printf("SystemCoreClock = %lu Hz\r\n", (unsigned long)SystemCoreClock); } /** * @brief "sysinfo": show FreeRTOS runtime info (uptime, tasks, heap, * per-task state/priority/stack high water mark). */ static void CmdSysInfo(int argc, char* argv[]) { static char s_taskList[384]; /* vTaskList output buffer */ uint32_t ticks = (uint32_t)xTaskGetTickCount(); (void)argc; (void)argv; cli_printf("uptime: %lu ticks (%lu s)\r\n", (unsigned long)ticks, (unsigned long)(ticks / configTICK_RATE_HZ)); cli_printf("tasks: %u\r\n", (unsigned int)uxTaskGetNumberOfTasks()); cli_printf("heap: total %u B, free %u B, min ever free %u B\r\n", (unsigned int)configTOTAL_HEAP_SIZE, (unsigned int)xPortGetFreeHeapSize(), (unsigned int)xPortGetMinimumEverFreeHeapSize()); /* vTaskList lines already end with "\r\n"; stack column is in words */ cli_write("name state prio stack(w) num\r\n"); s_taskList[0] = '\0'; vTaskList(s_taskList); cli_write(s_taskList); } /** * @brief "led <1|2> ": control a board LED. */ static void CmdLed(int argc, char* argv[]) { GPIO_Module* port; uint16_t pin; if (argc < 3) { cli_write("usage: led <1|2> \r\n"); return; } if (argv[1][0] == '1' && argv[1][1] == '\0') { port = LED1_PORT; pin = LED1_PIN; } else if (argv[1][0] == '2' && argv[1][1] == '\0') { port = LED2_PORT; pin = LED2_PIN; } else { cli_write("usage: led <1|2> \r\n"); return; } if (strcmp(argv[2], "on") == 0) { LedOn(port, pin); } else if (strcmp(argv[2], "off") == 0) { LedOff(port, pin); } else if (strcmp(argv[2], "toggle") == 0) { LedBlink(port, pin); } else { cli_write("usage: led <1|2> \r\n"); return; } cli_printf("led %s %s done\r\n", argv[1], argv[2]); } /** * @brief "devinfo": show the same items the BLE INFO_QUERY(all) reports. */ static void CmdDevInfo(int argc, char* argv[]) { int16_t temp = app_info_chip_temp_c10(); uint16_t fan = app_fan_get_rpm(); (void)argc; (void)argv; cli_printf("firmware: %s (0x%08lX)\r\n", APP_FW_VERSION, (unsigned long)APP_FW_VERSION_NUM); cli_printf("cur bank: APP%u\r\n", (unsigned int)app_info_cur_bank()); if (temp == 0x7FFF) { cli_write("chip temp: ADC read timeout\r\n"); } else { trim_stored_t *p_trim = SystemTrimValueGet(); cli_printf("chip temp: %d.%d C (adc=%u trim=%lu)\r\n", (int)(temp / 10), (int)(temp % 10), (unsigned int)app_info_last_temp_raw(), (p_trim != NULL) ? (unsigned long)p_trim->rc_adc_ts_25c : 0ul); } cli_printf("vdd: %u mV\r\n", (unsigned int)app_info_vdd_mv()); if (fan == APP_FAN_RPM_INVALID) { cli_write("fan rpm: n/a (no tachometer)\r\n"); } else { cli_printf("fan rpm: %u\r\n", (unsigned int)fan); } cli_printf("uptime: %lu s\r\n", (unsigned long)((uint32_t)xTaskGetTickCount() / configTICK_RATE_HZ)); cli_printf("free heap: %u B\r\n", (unsigned int)xPortGetFreeHeapSize()); } /** * @brief "blelog [on|off]": BLE traffic log switch (connect/disconnect * events + hex dump of every RX/TX protocol frame on the UART). */ static void CmdBleLog(int argc, char* argv[]) { if (argc >= 2) { if (strcmp(argv[1], "on") == 0) { app_ble_proto_log_set(1); } else if (strcmp(argv[1], "off") == 0) { app_ble_proto_log_set(0); } else { cli_write("usage: blelog [on|off]\r\n"); return; } } cli_printf("ble log: %s\r\n", app_ble_proto_log_get() ? "on" : "off"); } /** * @brief "reset": system reset (reboot into the active bank). */ static void CmdReset(int argc, char* argv[]) { (void)argc; (void)argv; cli_write("resetting...\r\n"); vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS)); NVIC_SystemReset(); } /** * @brief "factory": restore the APP_DATA params to factory defaults, * save them to flash, then reboot. The bootsetting record is not * touched (it selects the boot bank, it is not user configuration). */ static void CmdFactory(int argc, char* argv[]) { (void)argc; (void)argv; if (app_params_restore_defaults() == 0) { cli_write("factory defaults restored, resetting...\r\n"); } else { cli_write("factory: flash write failed, resetting anyway...\r\n"); } vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS)); NVIC_SystemReset(); } /** * @brief "uartrst": re-initialize USART1 at the default baud rate and * flush the RX queue (recovery from a wedged/misconfigured UART). */ static void CmdUartRst(int argc, char* argv[]) { (void)argc; (void)argv; bsp_usart_set_baud(BSP_USART_BAUDRATE); cli_printf("usart reset: %lu 8N1, rx queue flushed\r\n", (unsigned long)BSP_USART_BAUDRATE); } /** * @brief "uartinfo": show the USART1 configuration. */ static void CmdUartInfo(int argc, char* argv[]) { (void)argc; (void)argv; cli_write("usart1: TX=PB6 RX=PB7 (AF4)\r\n"); cli_printf("baud: %lu, 8 data bits, no parity, 1 stop bit\r\n", (unsigned long)BSP_USART_BAUDRATE); cli_write("flow control: none; tx: DMA CH1 (polled); rx: RXDNE irq queue\r\n"); } /** * @brief "ota": switch the UART frontend into OTA binary frame mode * (0xCA OTA frames on the raw stream, framed RSP on TX). * Handshake: the "[ota] binary mode ON" marker line is the go-ahead - * the host must wait for it before sending frames. Exits: OTA_ABORT * frame (immediate), 3 s idle timeout (fallback), or device reset * after a successful OTA_END. The BLE OTA channel (...e0005) is * always available and does not need this command. */ static void CmdOta(int argc, char* argv[]) { (void)argc; (void)argv; cli_write("[ota] binary mode ON - send 0xCA OTA frames now\r\n" "(ble_protocol.md section 6.6; OTA_ABORT or 3s idle exits)\r\n"); app_cli_ota_mode_enter(); } /* Command table; add new commands here */ static const CliCmd_t s_cmds[] = { {"help", "help [cmd]: list commands / show one command's help", CmdHelp}, {"version", "show firmware version", CmdVersion}, {"sysinfo", "show FreeRTOS runtime info (uptime/tasks/heap/task list)", CmdSysInfo}, {"devinfo", "show device info (temp/vdd/fan, same as BLE INFO_QUERY all)", CmdDevInfo}, {"blelog", "blelog [on|off]: BLE traffic log (events + frame hex dump)", CmdBleLog}, {"led", "led <1|2> : control board LED", CmdLed}, {"appget", "appget [json]: show app params (led/pwm)", AppParams_CmdAppget}, {"appset", "appset | appset json {\"led\":500}", AppParams_CmdAppset}, {"bsdump", "bsdump: show bootsetting record + crc check", AppBootset_CmdBsdump}, {"bsset", "bsset : write bootsetting field (careful!)", AppBootset_CmdBsset}, {"appsw", "appsw [1|2]: switch boot bank (crc verified) + reboot", AppBootset_CmdAppswitch}, {"ota", "ota: UART enters OTA binary frame mode (see help ota)", CmdOta}, {"reset", "reset: system reset (reboot)", CmdReset}, {"factory", "factory: restore params to defaults and reboot", CmdFactory}, {"uartrst", "uartrst: re-init USART1 (460800 8N1), flush rx queue", CmdUartRst}, {"uartinfo", "uartinfo: show USART1 pins/baud/format", CmdUartInfo}, }; #define CLI_CMD_COUNT (sizeof(s_cmds) / sizeof(s_cmds[0])) /** * @brief "help [cmd]": without an argument, list all commands with their * one-line help (the full table exceeds 512B and relies on the * chunked BLE read of the CLI characteristic); with an argument, * show that command's detailed help only. */ static void CmdHelp(int argc, char* argv[]) { uint32_t i; if (argc >= 2) { for (i = 0; i < CLI_CMD_COUNT; i++) { if (strcmp(argv[1], s_cmds[i].name) == 0) { cli_printf(" %-8s %s\r\n", s_cmds[i].name, s_cmds[i].help); return; } } cli_printf("unknown command: %s (try 'help')\r\n", argv[1]); return; } cli_write("commands:\r\n"); for (i = 0; i < CLI_CMD_COUNT; i++) { cli_printf(" %-8s %s\r\n", s_cmds[i].name, s_cmds[i].help); } cli_write("keys: TAB = complete, UP/DOWN = history\r\n"); } uint32_t cli_cmd_count(void) { return CLI_CMD_COUNT; } const char* cli_cmd_name(uint32_t idx) { return (idx < CLI_CMD_COUNT) ? s_cmds[idx].name : NULL; } /* ------------------------------------------------------------------ */ /* Line parsing / dispatch */ /* ------------------------------------------------------------------ */ int cli_exec_line(char* line) { char* argv[CLI_MAX_ARGS]; int argc = 0; char* p = line; uint32_t i; /* Split on spaces/tabs */ while (*p != '\0' && argc < CLI_MAX_ARGS) { while (*p == ' ' || *p == '\t') { p++; } if (*p == '\0') { break; } argv[argc++] = p; while (*p != '\0' && *p != ' ' && *p != '\t') { p++; } if (*p != '\0') { *p++ = '\0'; } } if (argc == 0) { return 0; } for (i = 0; i < CLI_CMD_COUNT; i++) { if (strcmp(argv[0], s_cmds[i].name) == 0) { s_cmds[i].handler(argc, argv); return 0; } } cli_printf("unknown command: %s (try 'help')\r\n", argv[0]); return 1; }