mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-19 22:35:23 +03:00
Merge pull request #3523 from fishpepper/cli_array
improved array handling in cli
This commit is contained in:
commit
afe2d26ded
3 changed files with 356 additions and 31 deletions
|
@ -299,13 +299,34 @@ static void cliPrintLinef(const char *format, ...)
|
|||
va_end(va);
|
||||
}
|
||||
|
||||
|
||||
static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
|
||||
{
|
||||
if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
|
||||
for (int i = 0; i < var->config.array.length; i++) {
|
||||
uint8_t value = ((uint8_t *)valuePointer)[i];
|
||||
switch (var->type & VALUE_TYPE_MASK) {
|
||||
default:
|
||||
case VAR_UINT8:
|
||||
// uint8_t array
|
||||
cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
|
||||
break;
|
||||
|
||||
case VAR_INT8:
|
||||
// int8_t array
|
||||
cliPrintf("%d", ((int8_t *)valuePointer)[i]);
|
||||
break;
|
||||
|
||||
case VAR_UINT16:
|
||||
// uin16_t array
|
||||
cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
|
||||
break;
|
||||
|
||||
case VAR_INT16:
|
||||
// int16_t array
|
||||
cliPrintf("%d", ((int16_t *)valuePointer)[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
cliPrintf("%d", value);
|
||||
if (i < var->config.array.length - 1) {
|
||||
cliPrint(",");
|
||||
}
|
||||
|
@ -376,7 +397,7 @@ static uint16_t getValueOffset(const clivalue_t *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void *getValuePointer(const clivalue_t *value)
|
||||
STATIC_UNIT_TESTED void *getValuePointer(const clivalue_t *value)
|
||||
{
|
||||
const pgRegistry_t* rec = pgFind(value->pgn);
|
||||
return CONST_CAST(void *, rec->address + getValueOffset(value));
|
||||
|
@ -2637,7 +2658,7 @@ static void cliDefaults(char *cmdline)
|
|||
cliReboot();
|
||||
}
|
||||
|
||||
static void cliGet(char *cmdline)
|
||||
STATIC_UNIT_TESTED void cliGet(char *cmdline)
|
||||
{
|
||||
const clivalue_t *val;
|
||||
int matchedCommands = 0;
|
||||
|
@ -2681,7 +2702,7 @@ static uint8_t getWordLength(char *bufBegin, char *bufEnd)
|
|||
return bufEnd - bufBegin;
|
||||
}
|
||||
|
||||
static void cliSet(char *cmdline)
|
||||
STATIC_UNIT_TESTED void cliSet(char *cmdline)
|
||||
{
|
||||
const uint32_t len = strlen(cmdline);
|
||||
char *eqptr;
|
||||
|
@ -2706,16 +2727,17 @@ static void cliSet(char *cmdline)
|
|||
|
||||
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, valueTable[i].name, strlen(valueTable[i].name)) == 0 && variableNameLength == strlen(valueTable[i].name)) {
|
||||
if (strncasecmp(cmdline, val->name, strlen(val->name)) == 0 && variableNameLength == strlen(val->name)) {
|
||||
|
||||
bool valueChanged = false;
|
||||
int16_t value = 0;
|
||||
switch (valueTable[i].type & VALUE_MODE_MASK) {
|
||||
switch (val->type & VALUE_MODE_MASK) {
|
||||
case MODE_DIRECT: {
|
||||
int16_t value = atoi(eqptr);
|
||||
|
||||
if (value >= valueTable[i].config.minmax.min && value <= valueTable[i].config.minmax.max) {
|
||||
if (value >= val->config.minmax.min && value <= val->config.minmax.max) {
|
||||
cliSetVar(val, value);
|
||||
valueChanged = true;
|
||||
}
|
||||
|
@ -2723,7 +2745,7 @@ static void cliSet(char *cmdline)
|
|||
|
||||
break;
|
||||
case MODE_LOOKUP: {
|
||||
const lookupTableEntry_t *tableEntry = &lookupTables[valueTable[i].config.lookup.tableIndex];
|
||||
const lookupTableEntry_t *tableEntry = &lookupTables[val->config.lookup.tableIndex];
|
||||
bool matched = false;
|
||||
for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
|
||||
matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
|
||||
|
@ -2739,41 +2761,67 @@ static void cliSet(char *cmdline)
|
|||
|
||||
break;
|
||||
case MODE_ARRAY: {
|
||||
const uint8_t arrayLength = valueTable[i].config.array.length;
|
||||
const uint8_t arrayLength = val->config.array.length;
|
||||
char *valPtr = eqptr;
|
||||
uint8_t array[256];
|
||||
char curVal[4];
|
||||
|
||||
for (int i = 0; i < arrayLength; i++) {
|
||||
// skip spaces
|
||||
valPtr = skipSpace(valPtr);
|
||||
char *valEnd = strstr(valPtr, ",");
|
||||
if ((valEnd != NULL) && (i < arrayLength - 1)) {
|
||||
uint8_t varLength = getWordLength(valPtr, valEnd);
|
||||
if (varLength <= 3) {
|
||||
strncpy(curVal, valPtr, getWordLength(valPtr, valEnd));
|
||||
curVal[varLength] = '\0';
|
||||
array[i] = (uint8_t)atoi((const char *)curVal);
|
||||
valPtr = valEnd + 1;
|
||||
} else {
|
||||
// find next comma (or end of string)
|
||||
char *valEndPtr = strchr(valPtr, ',');
|
||||
|
||||
// comma found or last item?
|
||||
if ((valEndPtr != NULL) || (i == arrayLength - 1)){
|
||||
// process substring [valPtr, valEndPtr[
|
||||
// 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 *)getValuePointer(val) + i;
|
||||
// store value
|
||||
*data = (uint8_t)atoi((const char*) valPtr);
|
||||
}
|
||||
break;
|
||||
|
||||
case VAR_INT8: {
|
||||
// fetch data pointer
|
||||
int8_t *data = (int8_t *)getValuePointer(val) + i;
|
||||
// store value
|
||||
*data = (int8_t)atoi((const char*) valPtr);
|
||||
}
|
||||
break;
|
||||
|
||||
case VAR_UINT16: {
|
||||
// fetch data pointer
|
||||
uint16_t *data = (uint16_t *)getValuePointer(val) + i;
|
||||
// store value
|
||||
*data = (uint16_t)atoi((const char*) valPtr);
|
||||
}
|
||||
break;
|
||||
|
||||
case VAR_INT16: {
|
||||
// fetch data pointer
|
||||
int16_t *data = (int16_t *)getValuePointer(val) + i;
|
||||
// store value
|
||||
*data = (int16_t)atoi((const char*) valPtr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if ((valEnd == NULL) && (i == arrayLength - 1)) {
|
||||
array[i] = atoi(valPtr);
|
||||
|
||||
uint8_t *ptr = getValuePointer(val);
|
||||
memcpy(ptr, array, arrayLength);
|
||||
// mark as changed
|
||||
valueChanged = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prepare to parse next item
|
||||
valPtr = valEndPtr + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (valueChanged) {
|
||||
cliPrintf("%s set to ", valueTable[i].name);
|
||||
cliPrintf("%s set to ", val->name);
|
||||
cliPrintVar(val, 0);
|
||||
} else {
|
||||
cliPrintLine("Invalid value");
|
||||
|
|
|
@ -52,6 +52,16 @@ blackbox_encoding_unittest_SRC := \
|
|||
$(USER_DIR)/common/printf.c \
|
||||
$(USER_DIR)/common/typeconversion.c
|
||||
|
||||
cli_unittest_SRC := \
|
||||
$(USER_DIR)/fc/cli.c \
|
||||
$(USER_DIR)/config/feature.c \
|
||||
$(USER_DIR)/config/parameter_group.c \
|
||||
$(USER_DIR)/common/typeconversion.c
|
||||
|
||||
cli_unittest_DEFINES := \
|
||||
USE_CLI \
|
||||
SystemCoreClock=1000000 \
|
||||
OSD
|
||||
|
||||
cms_unittest_SRC := \
|
||||
$(USER_DIR)/cms/cms.c \
|
||||
|
|
267
src/test/unit/cli_unittest.cc
Normal file
267
src/test/unit/cli_unittest.cc
Normal file
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* This file is part of Cleanflight.
|
||||
*
|
||||
* Cleanflight is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cleanflight is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
extern "C" {
|
||||
#include "platform.h"
|
||||
#include "target.h"
|
||||
#include "fc/runtime_config.h"
|
||||
#include "fc/fc_msp.h"
|
||||
#include "config/parameter_group.h"
|
||||
#include "config/feature.h"
|
||||
#include "config/parameter_group_ids.h"
|
||||
#include "sensors/battery.h"
|
||||
#include "drivers/buf_writer.h"
|
||||
#include "flight/mixer.h"
|
||||
#include "flight/servos.h"
|
||||
#include "flight/pid.h"
|
||||
#include "io/ledstrip.h"
|
||||
#include "io/serial.h"
|
||||
#include "io/osd.h"
|
||||
#include "fc/settings.h"
|
||||
#include "rx/rx.h"
|
||||
#include "io/beeper.h"
|
||||
#include "fc/rc_adjustments.h"
|
||||
#include "scheduler/scheduler.h"
|
||||
#include "fc/runtime_config.h"
|
||||
#include "build/version.h"
|
||||
#include "fc/config.h"
|
||||
#include "drivers/buf_writer.h"
|
||||
#include "fc/cli.h"
|
||||
|
||||
void cliSet(char *cmdline);
|
||||
void cliGet(char *cmdline);
|
||||
void *getValuePointer(const clivalue_t *value);
|
||||
|
||||
const clivalue_t valueTable[] = {
|
||||
{ "array_unit_test", VAR_INT8 | MODE_ARRAY | MASTER_VALUE, .config.array.length = 3, PG_RESERVED_FOR_TESTING_1, 0 }
|
||||
};
|
||||
const uint16_t valueTableEntryCount = ARRAYLEN(valueTable);
|
||||
const lookupTableEntry_t lookupTables[] = {};
|
||||
|
||||
|
||||
PG_REGISTER(osdConfig_t, osdConfig, PG_OSD_CONFIG, 0);
|
||||
PG_REGISTER(batteryConfig_t, batteryConfig, PG_BATTERY_CONFIG, 0);
|
||||
PG_REGISTER(ledStripConfig_t, ledStripConfig, PG_LED_STRIP_CONFIG, 0);
|
||||
PG_REGISTER(systemConfig_t, systemConfig, PG_SYSTEM_CONFIG, 0);
|
||||
PG_REGISTER_ARRAY(adjustmentRange_t, MAX_ADJUSTMENT_RANGE_COUNT, adjustmentRanges, PG_ADJUSTMENT_RANGE_CONFIG, 0);
|
||||
PG_REGISTER_ARRAY(modeActivationCondition_t, MAX_MODE_ACTIVATION_CONDITION_COUNT, modeActivationConditions, PG_MODE_ACTIVATION_PROFILE, 0);
|
||||
PG_REGISTER(mixerConfig_t, mixerConfig, PG_MIXER_CONFIG, 0);
|
||||
PG_REGISTER_ARRAY(motorMixer_t, MAX_SUPPORTED_MOTORS, customMotorMixer, PG_MOTOR_MIXER, 0);
|
||||
PG_REGISTER_ARRAY(servoParam_t, MAX_SUPPORTED_SERVOS, servoParams, PG_SERVO_PARAMS, 0);
|
||||
PG_REGISTER_ARRAY(servoMixer_t, MAX_SERVO_RULES, customServoMixers, PG_SERVO_MIXER, 0);
|
||||
PG_REGISTER(featureConfig_t, featureConfig, PG_FEATURE_CONFIG, 0);
|
||||
PG_REGISTER(beeperConfig_t, beeperConfig, PG_BEEPER_CONFIG, 0);
|
||||
PG_REGISTER(rxConfig_t, rxConfig, PG_RX_CONFIG, 0);
|
||||
PG_REGISTER(serialConfig_t, serialConfig, PG_SERIAL_CONFIG, 0);
|
||||
PG_REGISTER_ARRAY(rxChannelRangeConfig_t, NON_AUX_CHANNEL_COUNT, rxChannelRangeConfigs, PG_RX_CHANNEL_RANGE_CONFIG, 0);
|
||||
PG_REGISTER_ARRAY(rxFailsafeChannelConfig_t, MAX_SUPPORTED_RC_CHANNEL_COUNT, rxFailsafeChannelConfigs, PG_RX_FAILSAFE_CHANNEL_CONFIG, 0);
|
||||
PG_REGISTER(pidConfig_t, pidConfig, PG_PID_CONFIG, 0);
|
||||
|
||||
PG_REGISTER_WITH_RESET_FN(int8_t, unitTestData, PG_RESERVED_FOR_TESTING_1, 0);
|
||||
}
|
||||
|
||||
#include "unittest_macros.h"
|
||||
#include "gtest/gtest.h"
|
||||
TEST(CLIUnittest, TestCliSet)
|
||||
{
|
||||
|
||||
cliSet((char *)"array_unit_test = 123, -3 , 1");
|
||||
|
||||
const clivalue_t cval = {
|
||||
.name = "array_unit_test",
|
||||
.type = MODE_ARRAY | MASTER_VALUE | VAR_INT8,
|
||||
.pgn = PG_RESERVED_FOR_TESTING_1,
|
||||
.offset = 0
|
||||
};
|
||||
|
||||
printf("\n===============================\n");
|
||||
int8_t *data = (int8_t *)getValuePointer(&cval);
|
||||
for(int i=0; i<3; i++){
|
||||
printf("data[%d] = %d\n", i, data[i]);
|
||||
}
|
||||
printf("\n===============================\n");
|
||||
|
||||
|
||||
EXPECT_EQ(123, data[0]);
|
||||
EXPECT_EQ( -3, data[1]);
|
||||
EXPECT_EQ( 1, data[2]);
|
||||
|
||||
|
||||
//cliGet((char *)"osd_item_vbat");
|
||||
//EXPECT_EQ(false, false);
|
||||
}
|
||||
|
||||
// STUBS
|
||||
extern "C" {
|
||||
|
||||
float motor_disarmed[MAX_SUPPORTED_MOTORS];
|
||||
|
||||
uint16_t batteryWarningVoltage;
|
||||
uint8_t useHottAlarmSoundPeriod (void) { return 0; }
|
||||
const uint32_t baudRates[] = {0, 9600, 19200, 38400, 57600, 115200, 230400, 250000, 400000}; // see baudRate_e
|
||||
|
||||
uint32_t micros(void) {return 0;}
|
||||
|
||||
int32_t getAmperage(void) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
uint16_t getBatteryVoltage(void) {
|
||||
return 42;
|
||||
}
|
||||
|
||||
batteryState_e getBatteryState(void) {
|
||||
return BATTERY_OK;
|
||||
}
|
||||
|
||||
uint8_t calculateBatteryPercentageRemaining(void) {
|
||||
return 67;
|
||||
}
|
||||
|
||||
uint8_t getMotorCount() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
|
||||
void setPrintfSerialPort(struct serialPort_s) {}
|
||||
|
||||
void tfp_printf(const char * expectedFormat, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, expectedFormat);
|
||||
vprintf(expectedFormat, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
||||
void tfp_format(void *, void (*) (void *, char), const char * expectedFormat, va_list va) {
|
||||
vprintf(expectedFormat, va);
|
||||
}
|
||||
|
||||
static const box_t boxes[] = { { 0, "DUMMYBOX", 0 } };
|
||||
const box_t *findBoxByPermanentId(uint8_t) { return &boxes[0]; }
|
||||
const box_t *findBoxByBoxId(boxId_e) { return &boxes[0]; }
|
||||
|
||||
int8_t unitTestDataArray[3];
|
||||
|
||||
void pgResetFn_unitTestData(int8_t *ptr) {
|
||||
ptr = &unitTestDataArray[0];
|
||||
}
|
||||
|
||||
uint32_t getBeeperOffMask(void) { return 0; }
|
||||
uint32_t getPreferredBeeperOffMask(void) { return 0; }
|
||||
|
||||
void beeper(beeperMode_e) {}
|
||||
void beeperSilence(void) {}
|
||||
void beeperConfirmationBeeps(uint8_t) {}
|
||||
void beeperWarningBeeps(uint8_t) {}
|
||||
void beeperUpdate(timeUs_t) {}
|
||||
uint32_t getArmingBeepTimeMicros(void) {return 0;}
|
||||
beeperMode_e beeperModeForTableIndex(int) {return BEEPER_SILENCE;}
|
||||
const char *beeperNameForTableIndex(int) {return NULL;}
|
||||
int beeperTableEntryCount(void) {return 0;}
|
||||
bool isBeeperOn(void) {return false;}
|
||||
void beeperOffSetAll(uint8_t) {}
|
||||
void setBeeperOffMask(uint32_t) {}
|
||||
void setPreferredBeeperOffMask(uint32_t) {}
|
||||
|
||||
void beeperOffSet(uint32_t) {}
|
||||
void beeperOffClear(uint32_t) {}
|
||||
void beeperOffClearAll(void) {}
|
||||
bool parseColor(int, const char *) {return false; }
|
||||
void resetEEPROM(void) {}
|
||||
void bufWriterFlush(bufWriter_t *) {}
|
||||
void mixerResetDisarmedMotors(void) {}
|
||||
void gpsEnablePassthrough(struct serialPort_s *) {}
|
||||
bool parseLedStripConfig(int, const char *){return false; }
|
||||
const char rcChannelLetters[] = "AERT12345678abcdefgh";
|
||||
|
||||
void parseRcChannels(const char *, rxConfig_t *){}
|
||||
void mixerLoadMix(int, motorMixer_t *) {}
|
||||
bool setModeColor(ledModeIndex_e, int, int) { return false; }
|
||||
float convertExternalToMotor(uint16_t ){ return 1.0; }
|
||||
uint8_t getCurrentPidProfileIndex(void){ return 1; }
|
||||
uint8_t getCurrentControlRateProfileIndex(void){ return 1; }
|
||||
void changeControlRateProfile(uint8_t) {}
|
||||
void resetAllRxChannelRangeConfigurations(rxChannelRangeConfig_t *) {}
|
||||
void writeEEPROM() {}
|
||||
serialPortConfig_t *serialFindPortConfiguration(serialPortIdentifier_e) {return NULL; }
|
||||
baudRate_e lookupBaudRateIndex(uint32_t){return BAUD_9600; }
|
||||
serialPortUsage_t *findSerialPortUsageByIdentifier(serialPortIdentifier_e){ return NULL; }
|
||||
serialPort_t *openSerialPort(serialPortIdentifier_e, serialPortFunction_e, serialReceiveCallbackPtr, uint32_t, portMode_t, portOptions_t) { return NULL; }
|
||||
void serialSetBaudRate(serialPort_t *, uint32_t) {}
|
||||
void serialSetMode(serialPort_t *, portMode_t) {}
|
||||
void serialPassthrough(serialPort_t *, serialPort_t *, serialConsumer *, serialConsumer *) {}
|
||||
uint32_t millis(void) { return 0; }
|
||||
uint8_t getBatteryCellCount(void) { return 1; }
|
||||
void servoMixerLoadMix(int) {}
|
||||
const char * getBatteryStateString(void){ return "_getBatteryStateString_"; }
|
||||
|
||||
uint32_t stackTotalSize(void) { return 0x4000; }
|
||||
uint32_t stackHighMem(void) { return 0x80000000; }
|
||||
uint16_t getEEPROMConfigSize(void) { return 1024; }
|
||||
|
||||
uint8_t __config_start = 0x00;
|
||||
uint8_t __config_end = 0x10;
|
||||
uint16_t averageSystemLoadPercent = 0;
|
||||
|
||||
timeDelta_t getTaskDeltaTime(cfTaskId_e){ return 0; }
|
||||
armingDisableFlags_e getArmingDisableFlags(void) { return ARMING_DISABLED_NO_GYRO; }
|
||||
|
||||
const char *armingDisableFlagNames[]= {
|
||||
"DUMMYDISABLEFLAGNAME"
|
||||
};
|
||||
|
||||
void getTaskInfo(cfTaskId_e, cfTaskInfo_t *) {}
|
||||
void getCheckFuncInfo(cfCheckFuncInfo_t *) {}
|
||||
|
||||
const char * const targetName = "UNITTEST";
|
||||
const char* const buildDate = "Jan 01 2017";
|
||||
const char * const buildTime = "00:00:00";
|
||||
const char * const shortGitRevision = "MASTER";
|
||||
|
||||
uint32_t serialRxBytesWaiting(const serialPort_t *) {return 0;}
|
||||
uint8_t serialRead(serialPort_t *){return 0;}
|
||||
|
||||
void bufWriterAppend(bufWriter_t *, uint8_t ch){ printf("%c", ch); }
|
||||
void serialWriteBufShim(void *, const uint8_t *, int) {}
|
||||
bufWriter_t *bufWriterInit(uint8_t *, int, bufWrite_t, void *) {return NULL;}
|
||||
void schedulerSetCalulateTaskStatistics(bool) {}
|
||||
void setArmingDisabled(armingDisableFlags_e) {}
|
||||
|
||||
void waitForSerialPortToFinishTransmitting(serialPort_t *) {}
|
||||
void stopPwmAllMotors(void) {}
|
||||
void systemResetToBootloader(void) {}
|
||||
void resetConfigs(void) {}
|
||||
void systemReset(void) {}
|
||||
|
||||
void changePidProfile(uint8_t) {}
|
||||
bool serialIsPortAvailable(serialPortIdentifier_e) { return false; }
|
||||
void generateLedConfig(ledConfig_t *, char *, size_t) {}
|
||||
bool isSerialTransmitBufferEmpty(const serialPort_t *) {return true; }
|
||||
void serialWrite(serialPort_t *, uint8_t ch) { printf("%c", ch);}
|
||||
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue