From 1074a8f03437c8be91ef579fdcaaee17c6bbeb8d Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Fri, 11 Sep 2026 13:43:08 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Parser=20f=C3=BCr=20die=20UART-Testkons?= =?UTF-8?q?ole?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sys/nl_console_cmd.c | 120 +++++++++++++++++++++++++++++++++++++++ src/sys/nl_console_cmd.h | 34 +++++++++++ test/CMakeLists.txt | 1 + test/test_console_cmd.c | 98 ++++++++++++++++++++++++++++++++ 4 files changed, 253 insertions(+) create mode 100644 src/sys/nl_console_cmd.c create mode 100644 src/sys/nl_console_cmd.h create mode 100644 test/test_console_cmd.c diff --git a/src/sys/nl_console_cmd.c b/src/sys/nl_console_cmd.c new file mode 100644 index 0000000..5819895 --- /dev/null +++ b/src/sys/nl_console_cmd.c @@ -0,0 +1,120 @@ +#include "sys/nl_console_cmd.h" + +#include +#include +#include + +#define MAX_TOK 6 + +const char nl_console_help[] = + "on [ms] | off [ms] | bri <1-100> [ms] | rgb [ms] | ct [ms]\r\n" + "pob last|on|off | fade | blink [n] | pulse on|off\r\n" + "state | factory | reboot | help\r\n"; + +static bool num(const char *s, int32_t lo, int32_t hi, int32_t *out) +{ + char *end; + long v = strtol(s, &end, 10); + if (*s == '\0' || *end != '\0' || v < lo || v > hi) + return false; + *out = (int32_t)v; + return true; +} + +/* Parses tok[first..n-1] into arg[]: `req` required values in [lo,hi], optionally one + * trailing transition in [0,60000]. */ +static bool args(nl_cmd_t *c, char **tok, int n, int req, int32_t lo, int32_t hi, bool opt_ms) +{ + int given = n - 1; + if (given < req || given > req + (opt_ms ? 1 : 0)) + return false; + for (int i = 0; i < req; i++) + if (!num(tok[1 + i], lo, hi, &c->arg[i])) + return false; + if (given > req && !num(tok[1 + req], 0, 60000, &c->arg[req])) + return false; + c->argc = (uint8_t)given; + return true; +} + +static bool word(const char *s, const char *const *words, int count, int32_t *out) +{ + for (int i = 0; i < count; i++) + if (strcmp(s, words[i]) == 0) { + *out = i; + return true; + } + return false; +} + +nl_cmd_t nl_console_parse(const char *line) +{ + static const char *const POB[] = {"last", "on", "off"}; + static const char *const OFFON[] = {"off", "on"}; + nl_cmd_t c; + char buf[64]; + char *tok[MAX_TOK]; + char *save = NULL; + int n = 0; + bool ok; + + memset(&c, 0, sizeof c); + strncpy(buf, line, sizeof buf - 1); + buf[sizeof buf - 1] = '\0'; + for (char *t = strtok_r(buf, " \t\r\n", &save); t; t = strtok_r(NULL, " \t\r\n", &save)) { + if (n == MAX_TOK) { + c.type = NL_CMD_ERROR; + return c; + } + tok[n++] = t; + } + if (n == 0) + return c; /* NL_CMD_NONE */ + + if (!strcmp(tok[0], "on")) { + c.type = NL_CMD_ON; + ok = args(&c, tok, n, 0, 0, 0, true); + } else if (!strcmp(tok[0], "off")) { + c.type = NL_CMD_OFF; + ok = args(&c, tok, n, 0, 0, 0, true); + } else if (!strcmp(tok[0], "bri")) { + c.type = NL_CMD_BRI; + ok = args(&c, tok, n, 1, 1, 100, true); + } else if (!strcmp(tok[0], "rgb")) { + c.type = NL_CMD_RGB; + ok = args(&c, tok, n, 3, 0, 255, true); + } else if (!strcmp(tok[0], "ct")) { + c.type = NL_CMD_CT; + ok = args(&c, tok, n, 1, 1000, 20000, true); + } else if (!strcmp(tok[0], "fade")) { + c.type = NL_CMD_FADE; + ok = args(&c, tok, n, 2, 0, 10000, false); + } else if (!strcmp(tok[0], "pob")) { + c.type = NL_CMD_POB; + ok = n == 2 && word(tok[1], POB, 3, &c.arg[0]); + c.argc = 1; + } else if (!strcmp(tok[0], "pulse")) { + c.type = NL_CMD_PULSE; + ok = n == 2 && word(tok[1], OFFON, 2, &c.arg[0]); + c.argc = 1; + } else if (!strcmp(tok[0], "blink")) { + c.type = NL_CMD_BLINK; + c.arg[0] = 3; + c.argc = 1; + ok = n == 1 || (n == 2 && num(tok[1], 1, 10, &c.arg[0])); + } else { + static const struct { const char *name; nl_cmd_type_t type; } SIMPLE[] = { + {"state", NL_CMD_STATE}, {"factory", NL_CMD_FACTORY}, {"reboot", NL_CMD_REBOOT}, {"help", NL_CMD_HELP}}; + ok = false; + for (size_t i = 0; i < sizeof SIMPLE / sizeof SIMPLE[0]; i++) + if (!strcmp(tok[0], SIMPLE[i].name)) { + c.type = SIMPLE[i].type; + ok = n == 1; + } + } + if (!ok) { + memset(&c, 0, sizeof c); + c.type = NL_CMD_ERROR; + } + return c; +} diff --git a/src/sys/nl_console_cmd.h b/src/sys/nl_console_cmd.h new file mode 100644 index 0000000..a400451 --- /dev/null +++ b/src/sys/nl_console_cmd.h @@ -0,0 +1,34 @@ +#ifndef NL_CONSOLE_CMD_H +#define NL_CONSOLE_CMD_H + +#include + +typedef enum { + NL_CMD_NONE = 0, + NL_CMD_ON, + NL_CMD_OFF, + NL_CMD_BRI, + NL_CMD_RGB, + NL_CMD_CT, + NL_CMD_POB, + NL_CMD_FADE, + NL_CMD_STATE, + NL_CMD_BLINK, + NL_CMD_PULSE, + NL_CMD_FACTORY, + NL_CMD_REBOOT, + NL_CMD_HELP, + NL_CMD_ERROR +} nl_cmd_type_t; + +typedef struct { + nl_cmd_type_t type; + int32_t arg[4]; + uint8_t argc; +} nl_cmd_t; + +nl_cmd_t nl_console_parse(const char *line); + +extern const char nl_console_help[]; + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e2416fa..4aa35d1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -34,3 +34,4 @@ nl_test(test_settings test_settings.c fake_flash.c ${NL_SRC}/config/nl_settings. nl_test(test_state_log test_state_log.c fake_flash.c ${NL_SRC}/config/nl_state_log.c ${NL_SRC}/light/nl_boot_state.c ${NL_SRC}/util/nl_crc.c) nl_test(test_reset_counter test_reset_counter.c fake_flash.c ${NL_SRC}/config/nl_reset_counter.c) +nl_test(test_console_cmd test_console_cmd.c ${NL_SRC}/sys/nl_console_cmd.c) diff --git a/test/test_console_cmd.c b/test/test_console_cmd.c new file mode 100644 index 0000000..b8d1faf --- /dev/null +++ b/test/test_console_cmd.c @@ -0,0 +1,98 @@ +#include "unity.h" +#include "sys/nl_console_cmd.h" + +void setUp(void) {} +void tearDown(void) {} + +static void test_empty_and_unknown(void) +{ + TEST_ASSERT_EQUAL(NL_CMD_NONE, nl_console_parse("").type); + TEST_ASSERT_EQUAL(NL_CMD_NONE, nl_console_parse(" \r\n").type); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("dance").type); +} + +static void test_on_off(void) +{ + nl_cmd_t c = nl_console_parse("on"); + TEST_ASSERT_EQUAL(NL_CMD_ON, c.type); + TEST_ASSERT_EQUAL_UINT8(0, c.argc); + c = nl_console_parse("off 1500\r"); + TEST_ASSERT_EQUAL(NL_CMD_OFF, c.type); + TEST_ASSERT_EQUAL_UINT8(1, c.argc); + TEST_ASSERT_EQUAL_INT32(1500, c.arg[0]); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("on 60001").type); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("on abc").type); +} + +static void test_bri(void) +{ + nl_cmd_t c = nl_console_parse("bri 50 200"); + TEST_ASSERT_EQUAL(NL_CMD_BRI, c.type); + TEST_ASSERT_EQUAL_UINT8(2, c.argc); + TEST_ASSERT_EQUAL_INT32(50, c.arg[0]); + TEST_ASSERT_EQUAL_INT32(200, c.arg[1]); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("bri 0").type); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("bri").type); +} + +static void test_rgb(void) +{ + nl_cmd_t c = nl_console_parse("rgb 255 0 128 1000"); + TEST_ASSERT_EQUAL(NL_CMD_RGB, c.type); + TEST_ASSERT_EQUAL_UINT8(4, c.argc); + TEST_ASSERT_EQUAL_INT32(128, c.arg[2]); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("rgb 255 0").type); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("rgb 256 0 0").type); +} + +static void test_ct(void) +{ + nl_cmd_t c = nl_console_parse("ct 2700"); + TEST_ASSERT_EQUAL(NL_CMD_CT, c.type); + TEST_ASSERT_EQUAL_INT32(2700, c.arg[0]); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("ct 500").type); +} + +static void test_pob_fade_blink_pulse(void) +{ + TEST_ASSERT_EQUAL_INT32(0, nl_console_parse("pob last").arg[0]); + TEST_ASSERT_EQUAL_INT32(1, nl_console_parse("pob on").arg[0]); + TEST_ASSERT_EQUAL_INT32(2, nl_console_parse("pob off").arg[0]); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("pob maybe").type); + + nl_cmd_t c = nl_console_parse("fade 300 800"); + TEST_ASSERT_EQUAL(NL_CMD_FADE, c.type); + TEST_ASSERT_EQUAL_INT32(800, c.arg[1]); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("fade 300").type); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("fade 300 10001").type); + + c = nl_console_parse("blink"); + TEST_ASSERT_EQUAL(NL_CMD_BLINK, c.type); + TEST_ASSERT_EQUAL_INT32(3, c.arg[0]); + TEST_ASSERT_EQUAL_INT32(5, nl_console_parse("blink 5").arg[0]); + + TEST_ASSERT_EQUAL_INT32(1, nl_console_parse("pulse on").arg[0]); + TEST_ASSERT_EQUAL_INT32(0, nl_console_parse("pulse off").arg[0]); +} + +static void test_simple_commands(void) +{ + TEST_ASSERT_EQUAL(NL_CMD_STATE, nl_console_parse("state").type); + TEST_ASSERT_EQUAL(NL_CMD_FACTORY, nl_console_parse("factory").type); + TEST_ASSERT_EQUAL(NL_CMD_REBOOT, nl_console_parse("reboot").type); + TEST_ASSERT_EQUAL(NL_CMD_HELP, nl_console_parse("help").type); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("state now").type); +} + +int main(void) +{ + UNITY_BEGIN(); + RUN_TEST(test_empty_and_unknown); + RUN_TEST(test_on_off); + RUN_TEST(test_bri); + RUN_TEST(test_rgb); + RUN_TEST(test_ct); + RUN_TEST(test_pob_fade_blink_pulse); + RUN_TEST(test_simple_commands); + return UNITY_END(); +}