diff --git a/src/main/cli/cli.c b/src/main/cli/cli.c index 335f3fe506..500fb78ac6 100644 --- a/src/main/cli/cli.c +++ b/src/main/cli/cli.c @@ -538,6 +538,10 @@ static void printValuePointer(const clivalue_t *var, const void *valuePointer, b } else { cliPrintf("OFF"); } + break; + case MODE_STRING: + cliPrintf("%s", (char *)valuePointer); + break; } if (valueIsCorrupted) { @@ -756,7 +760,6 @@ static void cliSetVar(const clivalue_t *var, const uint32_t value) } *(uint32_t *)ptr = workValue; break; - } } else { switch (var->type & VALUE_TYPE_MASK) { @@ -3954,6 +3957,19 @@ static uint8_t getWordLength(char *bufBegin, char *bufEnd) return bufEnd - bufBegin; } +uint16_t cliGetSettingIndex(char *name, uint8_t length) +{ + for (uint32_t i = 0; i < valueTableEntryCount; i++) { + const char *settingName = valueTable[i].name; + + // ensure exact match when setting to prevent setting variables with shorter names + if (strncasecmp(name, settingName, strlen(settingName)) == 0 && length == strlen(settingName)) { + return i; + } + } + return valueTableEntryCount; +} + STATIC_UNIT_TESTED void cliSet(char *cmdline) { const uint32_t len = strlen(cmdline); @@ -3977,140 +3993,157 @@ STATIC_UNIT_TESTED void cliSet(char *cmdline) eqptr++; eqptr = skipSpace(eqptr); - for (uint32_t i = 0; i < valueTableEntryCount; i++) { - const clivalue_t *val = &valueTable[i]; - - // ensure exact match when setting to prevent setting variables with shorter names - if (strncasecmp(cmdline, val->name, strlen(val->name)) == 0 && variableNameLength == strlen(val->name)) { - - bool valueChanged = false; - int16_t value = 0; - switch (val->type & VALUE_MODE_MASK) { - case MODE_DIRECT: { - if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) { - uint32_t value = strtol(eqptr, NULL, 10); - - if (value <= val->config.u32Max) { - cliSetVar(val, value); - valueChanged = true; - } - } else { - int value = atoi(eqptr); - - int min; - int max; - getMinMax(val, &min, &max); - - if (value >= min && value <= max) { - cliSetVar(val, value); - valueChanged = true; - } - } - } - - break; - case MODE_LOOKUP: - case MODE_BITSET: { - int tableIndex; - if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) { - tableIndex = TABLE_OFF_ON; - } else { - tableIndex = val->config.lookup.tableIndex; - } - const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex]; - bool matched = false; - for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) { - matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0; - - if (matched) { - value = tableValueIndex; - - cliSetVar(val, value); - valueChanged = true; - } - } - } - - break; - - case MODE_ARRAY: { - const uint8_t arrayLength = val->config.array.length; - char *valPtr = eqptr; - - int i = 0; - while (i < arrayLength && valPtr != NULL) { - // skip spaces - valPtr = skipSpace(valPtr); - - // process substring starting at valPtr - // note: no need to copy substrings for atoi() - // it stops at the first character that cannot be converted... - switch (val->type & VALUE_TYPE_MASK) { - default: - case VAR_UINT8: - { - // fetch data pointer - uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i; - // store value - *data = (uint8_t)atoi((const char*) valPtr); - } - - break; - case VAR_INT8: - { - // fetch data pointer - int8_t *data = (int8_t *)cliGetValuePointer(val) + i; - // store value - *data = (int8_t)atoi((const char*) valPtr); - } - - break; - case VAR_UINT16: - { - // fetch data pointer - uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i; - // store value - *data = (uint16_t)atoi((const char*) valPtr); - } - - break; - case VAR_INT16: - { - // fetch data pointer - int16_t *data = (int16_t *)cliGetValuePointer(val) + i; - // store value - *data = (int16_t)atoi((const char*) valPtr); - } - - break; - } - - // find next comma (or end of string) - valPtr = strchr(valPtr, ',') + 1; - - i++; - } - } - - // mark as changed - valueChanged = true; - - break; - - } - - if (valueChanged) { - cliPrintf("%s set to ", val->name); - cliPrintVar(val, 0); - } else { - cliPrintErrorLinef("INVALID VALUE"); - cliPrintVarRange(val); - } - - return; - } + const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength); + if (index >= valueTableEntryCount) { + cliPrintErrorLinef("INVALID NAME"); + return; } - cliPrintErrorLinef("INVALID NAME"); + const clivalue_t *val = &valueTable[index]; + + bool valueChanged = false; + int16_t value = 0; + switch (val->type & VALUE_MODE_MASK) { + case MODE_DIRECT: { + if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) { + uint32_t value = strtol(eqptr, NULL, 10); + + if (value <= val->config.u32Max) { + cliSetVar(val, value); + valueChanged = true; + } + } else { + int value = atoi(eqptr); + + int min; + int max; + getMinMax(val, &min, &max); + + if (value >= min && value <= max) { + cliSetVar(val, value); + valueChanged = true; + } + } + } + + break; + case MODE_LOOKUP: + case MODE_BITSET: { + int tableIndex; + if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) { + tableIndex = TABLE_OFF_ON; + } else { + tableIndex = val->config.lookup.tableIndex; + } + const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex]; + bool matched = false; + for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) { + matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0; + + if (matched) { + value = tableValueIndex; + + cliSetVar(val, value); + valueChanged = true; + } + } + } + + break; + + case MODE_ARRAY: { + const uint8_t arrayLength = val->config.array.length; + char *valPtr = eqptr; + + int i = 0; + while (i < arrayLength && valPtr != NULL) { + // skip spaces + valPtr = skipSpace(valPtr); + + // process substring starting at valPtr + // note: no need to copy substrings for atoi() + // it stops at the first character that cannot be converted... + switch (val->type & VALUE_TYPE_MASK) { + default: + case VAR_UINT8: + { + // fetch data pointer + uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i; + // store value + *data = (uint8_t)atoi((const char*) valPtr); + } + + break; + case VAR_INT8: + { + // fetch data pointer + int8_t *data = (int8_t *)cliGetValuePointer(val) + i; + // store value + *data = (int8_t)atoi((const char*) valPtr); + } + + break; + case VAR_UINT16: + { + // fetch data pointer + uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i; + // store value + *data = (uint16_t)atoi((const char*) valPtr); + } + + break; + case VAR_INT16: + { + // fetch data pointer + int16_t *data = (int16_t *)cliGetValuePointer(val) + i; + // store value + *data = (int16_t)atoi((const char*) valPtr); + } + + break; + } + + // find next comma (or end of string) + valPtr = strchr(valPtr, ',') + 1; + + i++; + } + } + + // mark as changed + valueChanged = true; + + break; + case MODE_STRING: { + char *valPtr = eqptr; + valPtr = skipSpace(valPtr); + + const unsigned int len = strlen(valPtr); + const uint8_t min = val->config.string.minlength; + const uint8_t max = val->config.string.maxlength; + const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 || + strlen((char *)cliGetValuePointer(val)) == 0 || + strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0); + + if (updatable && len > 0 && len <= max) { + memset((char *)cliGetValuePointer(val), 0, max); + if (len >= min && strncmp(valPtr, emptyName, len)) { + strncpy((char *)cliGetValuePointer(val), valPtr, len); + } + valueChanged = true; + } + } + break; + } + + if (valueChanged) { + cliPrintf("%s set to ", val->name); + cliPrintVar(val, 0); + } else { + cliPrintErrorLinef("INVALID VALUE"); + cliPrintVarRange(val); + } + + return; } else { // no equals, check for matching variables. cliGet(cmdline); diff --git a/src/main/cli/settings.c b/src/main/cli/settings.c index da1d85e570..7381b83a5d 100644 --- a/src/main/cli/settings.c +++ b/src/main/cli/settings.c @@ -88,6 +88,7 @@ #include "pg/sdio.h" #include "pg/rcdevice.h" #include "pg/stats.h" +#include "pg/board.h" #include "rx/rx.h" #include "rx/cc2500_frsky_common.h" @@ -1419,8 +1420,13 @@ const clivalue_t valueTable[] = { #ifdef USE_PERSISTENT_STATS { "stats", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_STATS_CONFIG, offsetof(statsConfig_t, stats_enabled) }, { "stats_total_flights", VAR_UINT32 | MASTER_VALUE, .config.u32Max = UINT32_MAX, PG_STATS_CONFIG, offsetof(statsConfig_t, stats_total_flights) }, + { "stats_total_time_s", VAR_UINT32 | MASTER_VALUE, .config.u32Max = UINT32_MAX, PG_STATS_CONFIG, offsetof(statsConfig_t, stats_total_time_s) }, { "stats_total_dist_m", VAR_UINT32 | MASTER_VALUE, .config.u32Max = UINT32_MAX, PG_STATS_CONFIG, offsetof(statsConfig_t, stats_total_dist_m) }, +#endif + { "name", VAR_UINT8 | MASTER_VALUE | MODE_STRING, .config.string = { 1, MAX_NAME_LENGTH, STRING_FLAGS_NONE }, PG_PILOT_CONFIG, offsetof(pilotConfig_t, name) }, +#ifdef USE_OSD + { "display_name", VAR_UINT8 | MASTER_VALUE | MODE_STRING, .config.string = { 1, MAX_NAME_LENGTH, STRING_FLAGS_NONE }, PG_PILOT_CONFIG, offsetof(pilotConfig_t, displayName) }, #endif }; diff --git a/src/main/cli/settings.h b/src/main/cli/settings.h index 49a97b5a5f..9cb290d1db 100644 --- a/src/main/cli/settings.h +++ b/src/main/cli/settings.h @@ -160,17 +160,18 @@ typedef enum { PROFILE_RATE_VALUE = (2 << VALUE_SECTION_OFFSET), HARDWARE_VALUE = (3 << VALUE_SECTION_OFFSET), // Part of the master section, but used for the hardware definition - // value mode, bits 5-6 + // value mode, bits 5-7 MODE_DIRECT = (0 << VALUE_MODE_OFFSET), MODE_LOOKUP = (1 << VALUE_MODE_OFFSET), MODE_ARRAY = (2 << VALUE_MODE_OFFSET), - MODE_BITSET = (3 << VALUE_MODE_OFFSET) + MODE_BITSET = (3 << VALUE_MODE_OFFSET), + MODE_STRING = (4 << VALUE_MODE_OFFSET), } cliValueFlag_e; #define VALUE_TYPE_MASK (0x07) #define VALUE_SECTION_MASK (0x18) -#define VALUE_MODE_MASK (0x60) +#define VALUE_MODE_MASK (0xE0) typedef struct cliMinMaxConfig_s { const int16_t min; @@ -190,18 +191,28 @@ typedef struct cliArrayLengthConfig_s { const uint8_t length; } cliArrayLengthConfig_t; +typedef struct cliStringLengthConfig_s { + const uint8_t minlength; + const uint8_t maxlength; + const uint8_t flags; +} cliStringLengthConfig_t; + +#define STRING_FLAGS_NONE (0) +#define STRING_FLAGS_WRITEONCE (1 << 0) + typedef union { cliLookupTableConfig_t lookup; // used for MODE_LOOKUP excl. VAR_UINT32 cliMinMaxConfig_t minmax; // used for MODE_DIRECT with signed parameters cliMinMaxUnsignedConfig_t minmaxUnsigned; // used for MODE_DIRECT with unsigned parameters cliArrayLengthConfig_t array; // used for MODE_ARRAY + cliStringLengthConfig_t string; // used for MODE_STRING uint8_t bitpos; // used for MODE_BITSET - uint32_t u32Max; // used for MODE_DIRECT with VAR_UINT32 + uint32_t u32Max; // used for MODE_DIRECT with VAR_UINT32 } cliValueConfig_t; typedef struct clivalue_s { const char *name; - const uint8_t type; // see cliValueFlag_e + const uint8_t type; // see cliValueFlag_e const cliValueConfig_t config; pgn_t pgn; diff --git a/src/test/Makefile b/src/test/Makefile index 751a8e1da3..3dbee62b4b 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -20,6 +20,8 @@ TARGET_DIR = $(USER_DIR)/target include $(ROOT)/make/system-id.mk include $(ROOT)/make/targets_list.mk +VPATH := $(VPATH):$(USER_DIR):$(TEST_DIR) + # specify which files that are included in the test in addition to the unittest file. # variables available: # _SRC @@ -658,4 +660,3 @@ target_list: @echo $(foreach target,$(ALT_TARGETS),$(target)\>$(call get_base_target,$(target))) @echo ========== ALT/BASE FULL MAPPING ========== @echo $(foreach target,$(VALID_TARGETS),$(target)\>$(call get_base_target,$(target))) - diff --git a/src/test/unit/cli_unittest.cc b/src/test/unit/cli_unittest.cc index 318ae05aab..7f760ae4f6 100644 --- a/src/test/unit/cli_unittest.cc +++ b/src/test/unit/cli_unittest.cc @@ -17,7 +17,6 @@ #include #include -#include #include @@ -55,9 +54,12 @@ extern "C" { void cliSet(char *cmdline); void cliGet(char *cmdline); - + int cliGetSettingIndex(char *name, uint8_t length); + const clivalue_t valueTable[] = { - { "array_unit_test", VAR_INT8 | MODE_ARRAY | MASTER_VALUE, .config.array.length = 3, PG_RESERVED_FOR_TESTING_1, 0 } + { "array_unit_test", VAR_INT8 | MODE_ARRAY | MASTER_VALUE, .config.array.length = 3, PG_RESERVED_FOR_TESTING_1, 0 }, + { "str_unit_test", VAR_UINT8 | MODE_STRING | MASTER_VALUE, .config.string = { 0, 16, 0 }, PG_RESERVED_FOR_TESTING_1, 0 }, + { "wos_unit_test", VAR_UINT8 | MODE_STRING | MASTER_VALUE, .config.string = { 0, 16, STRING_FLAGS_WRITEONCE }, PG_RESERVED_FOR_TESTING_1, 0 }, }; const uint16_t valueTableEntryCount = ARRAYLEN(valueTable); const lookupTableEntry_t lookupTables[] = {}; @@ -87,21 +89,20 @@ extern "C" { #include "unittest_macros.h" #include "gtest/gtest.h" -TEST(CLIUnittest, TestCliSet) + +TEST(CLIUnittest, TestCliSetArray) { + char *str = (char *)"array_unit_test = 123, -3 , 1"; + cliSet(str); - cliSet((char *)"array_unit_test = 123, -3 , 1"); + const uint16_t index = cliGetSettingIndex(str, 15); + EXPECT_LT(index, valueTableEntryCount); - const clivalue_t cval = { - .name = "array_unit_test", - .type = MODE_ARRAY | MASTER_VALUE | VAR_INT8, - .pgn = PG_RESERVED_FOR_TESTING_1, - .offset = 0 - }; + const clivalue_t val = valueTable[index]; printf("\n===============================\n"); - int8_t *data = (int8_t *)cliGetValuePointer(&cval); - for(int i=0; i<3; i++){ + int8_t *data = (int8_t *)cliGetValuePointer(&val); + for(int i=0; i < val.config.array.length; i++){ printf("data[%d] = %d\n", i, data[i]); } printf("\n===============================\n"); @@ -116,6 +117,82 @@ TEST(CLIUnittest, TestCliSet) //EXPECT_EQ(false, false); } +TEST(CLIUnittest, TestCliSetStringNoFlags) +{ + char *str = (char *)"str_unit_test = SAMPLE"; + cliSet(str); + + const uint16_t index = cliGetSettingIndex(str, 13); + EXPECT_LT(index, valueTableEntryCount); + + const clivalue_t val = valueTable[index]; + + printf("\n===============================\n"); + uint8_t *data = (uint8_t *)cliGetValuePointer(&val); + for(int i = 0; i < val.config.string.maxlength && data[i] != 0; i++){ + printf("data[%d] = %d (%c)\n", i, data[i], data[i]); + } + printf("\n===============================\n"); + + + EXPECT_EQ('S', data[0]); + EXPECT_EQ('A', data[1]); + EXPECT_EQ('M', data[2]); + EXPECT_EQ('P', data[3]); + EXPECT_EQ('L', data[4]); + EXPECT_EQ('E', data[5]); + EXPECT_EQ(0, data[6]); +} + +TEST(CLIUnittest, TestCliSetStringWriteOnce) +{ + char *str1 = (char *)"wos_unit_test = SAMPLE"; + char *str2 = (char *)"wos_unit_test = ELPMAS"; + cliSet(str1); + + const uint16_t index = cliGetSettingIndex(str1, 13); + EXPECT_LT(index, valueTableEntryCount); + + const clivalue_t val = valueTable[index]; + + printf("\n===============================\n"); + uint8_t *data = (uint8_t *)cliGetValuePointer(&val); + for(int i = 0; i < val.config.string.maxlength && data[i] != 0; i++){ + printf("data[%d] = %d (%c)\n", i, data[i], data[i]); + } + printf("\n===============================\n"); + + EXPECT_EQ('S', data[0]); + EXPECT_EQ('A', data[1]); + EXPECT_EQ('M', data[2]); + EXPECT_EQ('P', data[3]); + EXPECT_EQ('L', data[4]); + EXPECT_EQ('E', data[5]); + EXPECT_EQ(0, data[6]); + + cliSet(str2); + + EXPECT_EQ('S', data[0]); + EXPECT_EQ('A', data[1]); + EXPECT_EQ('M', data[2]); + EXPECT_EQ('P', data[3]); + EXPECT_EQ('L', data[4]); + EXPECT_EQ('E', data[5]); + EXPECT_EQ(0, data[6]); + + cliSet(str1); + + EXPECT_EQ('S', data[0]); + EXPECT_EQ('A', data[1]); + EXPECT_EQ('M', data[2]); + EXPECT_EQ('P', data[3]); + EXPECT_EQ('L', data[4]); + EXPECT_EQ('E', data[5]); + EXPECT_EQ(0, data[6]); + + printf("\n"); +} + // STUBS extern "C" {