/** * @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 "app_display.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 "hang": wedge the CLI task in a busy loop so the idle hook stops * feeding the IWDG - watchdog fire test (device resets in ~4s and * the next banner shows "Last reset: IWDG watchdog!"). */ static void CmdHang(int argc, char* argv[]) { (void)argc; (void)argv; cli_write("hanging now - watchdog should reset in ~4s ...\r\n"); vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS)); for (;;) { /* busy: idle task starves, IWDG unfed */ } } /** * @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: DMA CH2 ring (3KB, idle irq)\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(); } /** * @brief Parse a "pxN" pin name (e.g. "pb0", "PA3", "PB13") into a GPIO port * and pin mask. @return 1 on success, 0 on bad name/out-of-range pin. */ static int pinset_parse(const char* name, GPIO_Module** port, uint16_t* mask) { const char* p; int pin = 0; if (name[0] != 'p' && name[0] != 'P') { return 0; } if (name[1] == 'a' || name[1] == 'A') { *port = GPIOA; } else if (name[1] == 'b' || name[1] == 'B') { *port = GPIOB; } else { return 0; } p = &name[2]; if (*p < '0' || *p > '9') { return 0; } while (*p >= '0' && *p <= '9') { pin = pin * 10 + (*p - '0'); if (pin > 15) { return 0; } p++; } if (*p != '\0') { return 0; } if (*port == GPIOA && pin > 6) /* PA7 not bonded on this chip */ { return 0; } if (*port == GPIOB && pin > 13) /* PB14/PB15 not bonded */ { return 0; } *mask = (uint16_t)(1u << pin); return 1; } /** * @brief "pinset [0|1]": set a GPIO output high/low, or read its level. * e.g. "pinset pb0 1", "pinset pa0 0", "pinset pb0" (read level). */ static void CmdPinSet(int argc, char* argv[]) { GPIO_Module* port = NULL; uint16_t mask = 0; GPIO_InitType s; if (argc < 2) { cli_write("usage: pinset [0|1] (e.g. pinset pb0 1)\r\n"); return; } if (!pinset_parse(argv[1], &port, &mask)) { cli_printf("bad pin: %s (use pa0..pa6 / pb0..pb13)\r\n", argv[1]); return; } if (port == GPIOA) { RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE); } else { RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE); } if (argc >= 3) { /* reserved-pin guard: warn before reconfiguring SWD / console pins */ if ((port == GPIOA && (mask & (GPIO_PIN_4 | GPIO_PIN_5)) != 0u) || (port == GPIOB && (mask & (GPIO_PIN_6 | GPIO_PIN_7)) != 0u)) { cli_write("warning: SWD/console pin - reconfigure may break the link\r\n"); } GPIO_InitStruct(&s); s.Pin = mask; s.GPIO_Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitPeripheral(port, &s); if (argv[2][0] == '1' && argv[2][1] == '\0') { GPIO_SetBits(port, mask); cli_printf("%s = 1\r\n", argv[1]); } else if (argv[2][0] == '0' && argv[2][1] == '\0') { GPIO_ResetBits(port, mask); cli_printf("%s = 0\r\n", argv[1]); } else { cli_write("usage: pinset [0|1] (0=low, 1=high)\r\n"); } } else { /* read: the input data register reflects the actual pin level */ cli_printf("%s = %u\r\n", argv[1], (unsigned)(GPIO_ReadInputDataBit(port, mask) ? 1u : 0u)); } } /** * @brief Parse a decimal floating-point number ("36.5", "-1.2", "88"). * @return 1 on success, 0 on bad input. */ static int cli_parse_float(const char* s, float* out) { float v = 0.0f; float frac = 0.1f; int neg = 0; int any = 0; if (*s == '-') { neg = 1; s++; } else if (*s == '+') { s++; } while (*s >= '0' && *s <= '9') { v = v * 10.0f + (float)(*s - '0'); s++; any = 1; } if (*s == '.') { s++; while (*s >= '0' && *s <= '9') { v += frac * (float)(*s - '0'); frac *= 0.1f; s++; any = 1; } } if (!any || *s != '\0') { return 0; } *out = neg ? -v : v; return 1; } /** * @brief Parse a hexadecimal integer ("ff", "0xFF", "10"). * @return 1 on success, 0 on bad input. */ /** * @brief "disp [f]": set the temperature display. * value 0.0..999.9 is shown as [hundreds][tens][ones][F][tenths]; * "f" turns on the DIG6-A degF indicator. */ static void CmdDisp(int argc, char* argv[]) { uint8_t type = DISP_TYPE_CELSIUS; float value; int i; if (argc < 2) { cli_write("usage: disp [f] (e.g. disp 36.5, disp 36.5 f)\r\n"); return; } for (i = 2; i < argc; i++) { if (strcmp(argv[i], "f") == 0) { type = DISP_TYPE_FAHRENHEIT; } else { cli_printf("bad mode: %s (use f)\r\n", argv[i]); return; } } if (!cli_parse_float(argv[1], &value)) { cli_printf("bad value: %s\r\n", argv[1]); return; } display_set(value, type, DISP_BASE_DECIMAL); /* echo the (clamped) value actually shown */ { float shown = value; int scaled, ip, fp; if (shown < 0.0f) { shown = 0.0f; } if (shown > 999.9f) { shown = 999.9f; } scaled = (int)(shown * 10.0f + 0.5f); ip = scaled / 10; fp = scaled - ip * 10; cli_printf("display = %d.%d %c\r\n", ip, fp, (type == DISP_TYPE_FAHRENHEIT) ? 'F' : 'C'); } } /** * @brief Parse a display pin selector "pin1".."pin7" (or bare "1".."7"). * @return 1 on success, 0 on bad input. */ static int cli_parse_disp_pin(const char* s, uint8_t* pin_no) { if (s[0] == 'p' || s[0] == 'P') { if ((s[1] != 'i' && s[1] != 'I') || (s[2] != 'n' && s[2] != 'N')) { return 0; } s += 3; } if (s[0] < '1' || s[0] > '7' || s[1] != '\0') { return 0; } *pin_no = (uint8_t)(s[0] - '0'); return 1; } /** * @brief Format a GPIO port + pin mask as a pin name ("PA6"/"PB0"/"PB11"). */ static void cli_pin_str(GPIO_Module* port, uint16_t pin, char* out) { char pc = (port == GPIOA) ? 'A' : 'B'; uint8_t n = 0u; while ((pin & 1u) == 0u) { pin >>= 1; n++; } if (n >= 10u) { out[0] = 'P'; out[1] = pc; out[2] = '1'; out[3] = (char)('0' + (n - 10u)); out[4] = '\0'; } else { out[0] = 'P'; out[1] = pc; out[2] = (char)('0' + n); out[3] = '\0'; } } /** * @brief "dispauto [on|off]": enable/disable the automatic display scan. * Turning it off releases all 7 display pins to high-impedance so * "dispset" can drive them manually. */ static void CmdDispAuto(int argc, char* argv[]) { if (argc >= 2) { if (strcmp(argv[1], "on") == 0) { display_auto_set(1u); } else if (strcmp(argv[1], "off") == 0) { display_auto_set(0u); } else { cli_write("usage: dispauto [on|off]\r\n"); return; } } cli_printf("auto display: %s\r\n", display_auto_get() ? "on" : "off"); } /** * @brief Drive one display pin (1..7) to 0 / 1 / high-Z. * @return 1 on success, 0 on bad value, -1 on bad pin number. */ static int cli_disp_set_pin(uint8_t pin_no, char val) { GPIO_Module* port; uint16_t pin; GPIO_InitType s; if (!display_pin_map(pin_no, &port, &pin)) { return -1; } /* Make sure the port clock is on before touching the pin (defensive; * display_task also enables both clocks at boot). */ if (port == GPIOA) { RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE); } else { RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE); } GPIO_InitStruct(&s); s.Pin = pin; if (val == '1') { s.GPIO_Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitPeripheral(port, &s); GPIO_SetBits(port, pin); } else if (val == '0') { s.GPIO_Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitPeripheral(port, &s); GPIO_ResetBits(port, pin); } else if (val == 'z' || val == 'Z') { s.GPIO_Mode = GPIO_MODE_INPUT; s.GPIO_Pull = GPIO_NO_PULL; GPIO_InitPeripheral(port, &s); } else { return 0; } return 1; } /** * @brief "dispset [0|1|z]": drive display pin(s) manually. * pin1..pin7 map to PB0/PB1/PA0/PA1/PB11/PB13/PA6; "all" hits all 7. * "z" = high-Z. No value reads one pin's level. Use after "dispauto off". */ static void CmdDispSet(int argc, char* argv[]) { if (argc < 2) { cli_write("usage: dispset [0|1|z]\r\n"); return; } /* "all": apply the value to all 7 display pins */ if (strcmp(argv[1], "all") == 0 || strcmp(argv[1], "ALL") == 0) { uint8_t p; if (argc < 3 || argv[2][1] != '\0') { cli_write("usage: dispset all [0|1|z]\r\n"); return; } if (display_auto_get()) { cli_write("warning: auto display is ON - use 'dispauto off' first\r\n"); } for (p = 1u; p <= 7u; p++) { if (cli_disp_set_pin(p, argv[2][0]) != 1) { cli_write("usage: dispset all [0|1|z]\r\n"); return; } } cli_printf("all 7 display pins = %s\r\n", (argv[2][0] == 'z' || argv[2][0] == 'Z') ? "high-Z" : argv[2]); return; } /* single pin */ { GPIO_Module* port; uint16_t pin; uint8_t pin_no; char name[8]; if (!cli_parse_disp_pin(argv[1], &pin_no) || !display_pin_map(pin_no, &port, &pin)) { cli_printf("bad pin: %s (use pin1..pin7 or all)\r\n", argv[1]); return; } cli_pin_str(port, pin, name); if (argc >= 3) { if (display_auto_get()) { cli_write("warning: auto display is ON - use 'dispauto off' first\r\n"); } if (argv[2][1] == '\0' && cli_disp_set_pin(pin_no, argv[2][0]) == 1) { if (argv[2][0] == 'z' || argv[2][0] == 'Z') { cli_printf("pin%u (%s) = high-Z\r\n", (unsigned)pin_no, name); } else { cli_printf("pin%u (%s) = %c\r\n", (unsigned)pin_no, name, argv[2][0]); } } else { cli_write("usage: dispset [0|1|z]\r\n"); } } else { cli_printf("pin%u (%s) = %u\r\n", (unsigned)pin_no, name, (unsigned)(GPIO_ReadInputDataBit(port, pin) ? 1u : 0u)); } } } /** * @brief "disptest": automated display self-test. First a quick digit/position * check (0..9, multi-digit, max 999.9, degF toggle), then a full sweep * of the temperature range 0.1..999.0 in both Celsius and Fahrenheit. * Restores auto-scan + 0.0 when done. */ static void CmdDispTest(int argc, char* argv[]) { static const float s_digits[10] = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f }; uint8_t prev_auto = display_auto_get(); uint16_t i; uint8_t unit; (void)argc; (void)argv; /* the test drives values through display_set(), so auto must be on */ display_auto_set(1u); /* --- 1. quick digit / position check --- */ cli_write("disptest: 0.0..9.0 -> 12.3/456.7/999.9 -> 36.5F/C\r\n"); for (i = 0u; i < 10u; i++) { display_set(s_digits[i], DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); cli_printf(" %u.0\r\n", (unsigned)i); vTaskDelay(pdMS_TO_TICKS(500)); } display_set(12.3f, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); cli_write(" 12.3\r\n"); vTaskDelay(pdMS_TO_TICKS(1000)); display_set(456.7f, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); cli_write(" 456.7\r\n"); vTaskDelay(pdMS_TO_TICKS(1000)); display_set(999.9f, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); cli_write(" 999.9 (max)\r\n"); vTaskDelay(pdMS_TO_TICKS(1000)); display_set(36.5f, DISP_TYPE_FAHRENHEIT, DISP_BASE_DECIMAL); cli_write(" 36.5 F (degF indicator on)\r\n"); vTaskDelay(pdMS_TO_TICKS(1200)); display_set(36.5f, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); cli_write(" 36.5 C (degF indicator off)\r\n"); vTaskDelay(pdMS_TO_TICKS(1200)); /* --- 2. full temperature sweep, Celsius then Fahrenheit --- */ for (unit = 0u; unit < 2u; unit++) { uint8_t type = (unit == 0u) ? DISP_TYPE_CELSIUS : DISP_TYPE_FAHRENHEIT; cli_printf("disptest sweep %s: 0.1..9.9 -> 10..99 -> 100..999\r\n", (unit == 0u) ? "C" : "F"); for (i = 1u; i <= 99u; i++) /* 0.1 .. 9.9 step 0.1 */ { display_set((float)i * 0.1f, type, DISP_BASE_DECIMAL); vTaskDelay(pdMS_TO_TICKS(100)); } for (i = 10u; i <= 99u; i++) /* 10 .. 99 step 1 */ { display_set((float)i, type, DISP_BASE_DECIMAL); vTaskDelay(pdMS_TO_TICKS(100)); } for (i = 100u; i <= 990u; i += 10u) /* 100 .. 990 step 10 */ { display_set((float)i, type, DISP_BASE_DECIMAL); vTaskDelay(pdMS_TO_TICKS(100)); } display_set(999.0f, type, DISP_BASE_DECIMAL); /* 999.0 */ vTaskDelay(pdMS_TO_TICKS(300)); } display_set(0.0f, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); display_auto_set(prev_auto); cli_write("disptest done\r\n"); } /* 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}, {"pinset", "pinset [0|1]: set/read GPIO level (pa0-6/pb0-13)", CmdPinSet}, {"disp", "disp [f]: set temperature display (0-999.9, 1 dec)", CmdDisp}, {"dispauto", "dispauto [on|off]: enable/disable auto display scan", CmdDispAuto}, {"dispset", "dispset [0|1|z]: drive display pin(s)", CmdDispSet}, {"disptest", "disptest: display self-test + full 0.1-999.0 sweep (C/F)", CmdDispTest}, {"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}, {"hang", "hang: wedge the CLI task (IWDG watchdog fire test)", CmdHang}, }; #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("mode: APP\r\n"); 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; }