2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
30 #include "blackbox/blackbox.h"
32 #include "build/assert.h"
33 #include "build/build_config.h"
34 #include "build/version.h"
36 #include "common/axis.h"
37 #include "common/color.h"
38 #include "common/maths.h"
39 #include "common/printf.h"
40 #include "common/string_light.h"
41 #include "common/memory.h"
42 #include "common/time.h"
43 #include "common/typeconversion.h"
44 #include "common/fp_pid.h"
45 #include "programming/global_variables.h"
46 #include "programming/pid.h"
48 #include "config/config_eeprom.h"
49 #include "config/feature.h"
50 #include "config/parameter_group.h"
51 #include "config/parameter_group_ids.h"
53 #include "drivers/accgyro/accgyro.h"
54 #include "drivers/pwm_mapping.h"
55 #include "drivers/buf_writer.h"
56 #include "drivers/bus_i2c.h"
57 #include "drivers/compass/compass.h"
58 #include "drivers/flash.h"
59 #include "drivers/io.h"
60 #include "drivers/io_impl.h"
61 #include "drivers/osd_symbols.h"
62 #include "drivers/persistent.h"
63 #include "drivers/sdcard/sdcard.h"
64 #include "drivers/sensor.h"
65 #include "drivers/serial.h"
66 #include "drivers/stack_check.h"
67 #include "drivers/system.h"
68 #include "drivers/time.h"
69 #include "drivers/usb_msc.h"
70 #include "drivers/vtx_common.h"
72 #include "fc/fc_core.h"
74 #include "fc/config.h"
75 #include "fc/controlrate_profile.h"
76 #include "fc/rc_adjustments.h"
77 #include "fc/rc_controls.h"
78 #include "fc/rc_modes.h"
79 #include "fc/runtime_config.h"
80 #include "fc/settings.h"
82 #include "flight/failsafe.h"
83 #include "flight/imu.h"
84 #include "flight/mixer.h"
85 #include "flight/pid.h"
86 #include "flight/servos.h"
88 #include "io/asyncfatfs/asyncfatfs.h"
89 #include "io/beeper.h"
90 #include "io/flashfs.h"
92 #include "io/ledstrip.h"
94 #include "io/serial.h"
96 #include "fc/fc_msp_box.h"
98 #include "navigation/navigation.h"
99 #include "navigation/navigation_private.h"
102 #include "rx/spektrum.h"
103 #include "rx/srxl2.h"
105 #include "scheduler/scheduler.h"
107 #include "sensors/acceleration.h"
108 #include "sensors/barometer.h"
109 #include "sensors/battery.h"
110 #include "sensors/boardalignment.h"
111 #include "sensors/compass.h"
112 #include "sensors/diagnostics.h"
113 #include "sensors/gyro.h"
114 #include "sensors/pitotmeter.h"
115 #include "sensors/rangefinder.h"
116 #include "sensors/opflow.h"
117 #include "sensors/sensors.h"
118 #include "sensors/temperature.h"
119 #ifdef USE_ESC_SENSOR
120 #include "sensors/esc_sensor.h"
123 #include "telemetry/telemetry.h"
124 #include "build/debug.h"
126 extern timeDelta_t cycleTime
; // FIXME dependency on mw.c
127 extern uint8_t detectedSensors
[SENSOR_INDEX_COUNT
];
129 static serialPort_t
*cliPort
;
131 static bufWriter_t
*cliWriter
;
132 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + 128];
134 static char cliBuffer
[64];
135 static uint32_t bufferIndex
= 0;
136 static uint16_t cliDelayMs
= 0;
138 #if defined(USE_ASSERT)
139 static void cliAssert(char *cmdline
);
143 static bool commandBatchActive
= false;
144 static bool commandBatchError
= false;
147 // sync this with features_e
148 static const char * const featureNames
[] = {
149 "THR_VBAT_COMP", "VBAT", "TX_PROF_SEL", "BAT_PROF_AUTOSWITCH", "MOTOR_STOP",
150 "", "SOFTSERIAL", "GPS", "RPM_FILTERS",
151 "", "TELEMETRY", "CURRENT_METER", "REVERSIBLE_MOTORS", "",
152 "", "RSSI_ADC", "LED_STRIP", "DASHBOARD", "",
153 "BLACKBOX", "", "TRANSPONDER", "AIRMODE",
154 "SUPEREXPO", "VTX", "", "", "", "PWM_OUTPUT_ENABLE",
155 "OSD", "FW_LAUNCH", "FW_AUTOTRIM", NULL
159 static const char * const blackboxIncludeFlagNames
[] = {
177 /* Sensor names (used in lookup tables for *_hardware settings and in status command output) */
178 // sync with gyroSensor_e
179 static const char * const gyroNames
[] = { "NONE", "AUTO", "MPU6000", "MPU6500", "MPU9250", "BMI160", "ICM20689", "BMI088", "ICM42605", "BMI270","LSM6DXX", "FAKE"};
181 // sync this with sensors_e
182 static const char * const sensorTypeNames
[] = {
183 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "PITOT", "OPFLOW", "GPS", "GPS+MAG", NULL
186 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER | SENSOR_PITOT | SENSOR_OPFLOW)
188 static const char * const hardwareSensorStatusNames
[] = {
189 "NONE", "OK", "UNAVAILABLE", "FAILING"
192 static const char * const *sensorHardwareNames
[] = {
205 #ifdef USE_RANGEFINDER
206 table_rangefinder_hardware
,
211 table_pitot_hardware
,
216 table_opflow_hardware
,
222 static void cliPrint(const char *str
)
225 bufWriterAppend(cliWriter
, *str
++);
229 static void cliPrintLinefeed(void)
237 static void cliPrintLine(const char *str
)
243 static void cliPrintError(const char *str
)
245 cliPrint("### ERROR: ");
248 if (commandBatchActive
) {
249 commandBatchError
= true;
254 static void cliPrintErrorLine(const char *str
)
256 cliPrint("### ERROR: ");
259 if (commandBatchActive
) {
260 commandBatchError
= true;
265 #ifdef CLI_MINIMAL_VERBOSITY
266 #define cliPrintHashLine(str)
268 static void cliPrintHashLine(const char *str
)
275 static void cliPutp(void *p
, char ch
)
277 bufWriterAppend(p
, ch
);
281 DUMP_MASTER
= (1 << 0),
282 DUMP_PROFILE
= (1 << 1),
283 DUMP_BATTERY_PROFILE
= (1 << 2),
284 DUMP_RATES
= (1 << 3),
287 SHOW_DEFAULTS
= (1 << 6),
288 HIDE_UNUSED
= (1 << 7)
291 static void cliPrintfva(const char *format
, va_list va
)
293 tfp_format(cliWriter
, cliPutp
, format
, va
);
294 bufWriterFlush(cliWriter
);
297 static void cliPrintLinefva(const char *format
, va_list va
)
299 tfp_format(cliWriter
, cliPutp
, format
, va
);
300 bufWriterFlush(cliWriter
);
304 static bool cliDumpPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
306 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
308 va_start(va
, format
);
309 cliPrintLinefva(format
, va
);
317 static void cliWrite(uint8_t ch
)
319 bufWriterAppend(cliWriter
, ch
);
322 static bool cliDefaultPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
324 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
328 va_start(va
, format
);
329 cliPrintLinefva(format
, va
);
337 static void cliPrintf(const char *format
, ...)
340 va_start(va
, format
);
341 cliPrintfva(format
, va
);
346 static void cliPrintLinef(const char *format
, ...)
349 va_start(va
, format
);
350 cliPrintLinefva(format
, va
);
354 static void cliPrintErrorVa(const char *format
, va_list va
)
356 cliPrint("### ERROR: ");
357 cliPrintfva(format
, va
);
361 if (commandBatchActive
) {
362 commandBatchError
= true;
367 static void cliPrintErrorLinef(const char *format
, ...)
370 va_start(va
, format
);
371 cliPrintErrorVa(format
, va
);
375 static void printValuePointer(const setting_t
*var
, const void *valuePointer
, uint32_t full
)
378 char buf
[SETTING_MAX_NAME_LENGTH
];
380 switch (SETTING_TYPE(var
)) {
382 value
= *(uint8_t *)valuePointer
;
386 value
= *(int8_t *)valuePointer
;
390 value
= *(uint16_t *)valuePointer
;
394 value
= *(int16_t *)valuePointer
;
398 value
= *(uint32_t *)valuePointer
;
402 cliPrintf("%s", ftoa(*(float *)valuePointer
, buf
));
404 if (SETTING_MODE(var
) == MODE_DIRECT
) {
405 cliPrintf(" %s", ftoa((float)settingGetMin(var
), buf
));
406 cliPrintf(" %s", ftoa((float)settingGetMax(var
), buf
));
409 return; // return from case for float only
412 cliPrintf("%s", (const char *)valuePointer
);
416 switch (SETTING_MODE(var
)) {
418 if (SETTING_TYPE(var
) == VAR_UINT32
)
419 cliPrintf("%u", value
);
421 cliPrintf("%d", value
);
423 if (SETTING_MODE(var
) == MODE_DIRECT
) {
424 cliPrintf(" %d %u", settingGetMin(var
), settingGetMax(var
));
430 const char *name
= settingLookupValueName(var
, value
);
434 settingGetName(var
, buf
);
435 cliPrintErrorLinef("VALUE %d OUT OF RANGE FOR %s", (int)value
, buf
);
442 static bool valuePtrEqualsDefault(const setting_t
*value
, const void *ptr
, const void *ptrDefault
)
445 switch (SETTING_TYPE(value
)) {
447 result
= *(uint8_t *)ptr
== *(uint8_t *)ptrDefault
;
451 result
= *(int8_t *)ptr
== *(int8_t *)ptrDefault
;
455 result
= *(uint16_t *)ptr
== *(uint16_t *)ptrDefault
;
459 result
= *(int16_t *)ptr
== *(int16_t *)ptrDefault
;
463 result
= *(uint32_t *)ptr
== *(uint32_t *)ptrDefault
;
467 result
= *(float *)ptr
== *(float *)ptrDefault
;
471 result
= strncmp(ptr
, ptrDefault
, settingGetStringMaxLength(value
) + 1) == 0;
477 static void dumpPgValue(const setting_t
*value
, uint8_t dumpMask
)
479 char name
[SETTING_MAX_NAME_LENGTH
];
480 const char *format
= "set %s = ";
481 const char *defaultFormat
= "#set %s = ";
482 // During a dump, the PGs have been backed up to their "copy"
483 // regions and the actual values have been reset to its
484 // defaults. This means that settingGetValuePointer() will
485 // return the default value while settingGetCopyValuePointer()
486 // will return the actual value.
487 const void *valuePointer
= settingGetCopyValuePointer(value
);
488 const void *defaultValuePointer
= settingGetValuePointer(value
);
489 const bool equalsDefault
= valuePtrEqualsDefault(value
, valuePointer
, defaultValuePointer
);
490 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
491 settingGetName(value
, name
);
492 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
493 cliPrintf(defaultFormat
, name
);
494 // if the craftname has a leading space, then enclose the name in quotes
495 if (strcmp(name
, "name") == 0 && ((const char *)valuePointer
)[0] == ' ') {
496 cliPrintf("\"%s\"", (const char *)valuePointer
);
498 printValuePointer(value
, valuePointer
, 0);
502 cliPrintf(format
, name
);
503 printValuePointer(value
, valuePointer
, 0);
508 static void dumpAllValues(uint16_t valueSection
, uint8_t dumpMask
)
510 for (unsigned i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
511 const setting_t
*value
= settingGet(i
);
512 bufWriterFlush(cliWriter
);
513 if (SETTING_SECTION(value
) == valueSection
) {
514 dumpPgValue(value
, dumpMask
);
519 static void cliPrintVar(const setting_t
*var
, uint32_t full
)
521 const void *ptr
= settingGetValuePointer(var
);
523 printValuePointer(var
, ptr
, full
);
526 static void cliPrintVarRange(const setting_t
*var
)
528 switch (SETTING_MODE(var
)) {
530 if (SETTING_TYPE(var
) == VAR_STRING
) {
531 cliPrintLinef("Max. length: %u", settingGetStringMaxLength(var
));
534 cliPrintLinef("Allowed range: %d - %u", settingGetMin(var
), settingGetMax(var
));
538 const lookupTableEntry_t
*tableEntry
= settingLookupTable(var
);
539 cliPrint("Allowed values:");
540 for (uint32_t i
= 0; i
< tableEntry
->valueCount
; i
++) {
543 cliPrintf(" %s", tableEntry
->values
[i
]);
557 static void cliSetIntFloatVar(const setting_t
*var
, const int_float_value_t value
)
559 void *ptr
= settingGetValuePointer(var
);
561 switch (SETTING_TYPE(var
)) {
564 *(int8_t *)ptr
= value
.int_value
;
569 *(int16_t *)ptr
= value
.int_value
;
573 *(uint32_t *)ptr
= value
.uint_value
;
577 *(float *)ptr
= (float)value
.float_value
;
581 // Handled by cliSet directly
586 static void cliPrompt(void)
589 bufWriterFlush(cliWriter
);
592 static void cliShowParseError(void)
594 cliPrintErrorLinef("Parse error");
597 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
599 cliPrintErrorLinef("%s must be between %d and %d", name
, min
, max
);
602 static const char *nextArg(const char *currentArg
)
604 const char *ptr
= strchr(currentArg
, ' ');
605 while (ptr
&& *ptr
== ' ') {
612 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
614 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
617 int val
= fastA2I(ptr
);
618 val
= CHANNEL_VALUE_TO_STEP(val
);
619 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
621 range
->startStep
= val
;
623 range
->endStep
= val
;
625 (*validArgumentCount
)++;
633 // Check if a string's length is zero
634 static bool isEmpty(const char *string
)
636 return (string
== NULL
|| *string
== '\0') ? true : false;
639 #if defined(USE_ASSERT)
640 static void cliAssert(char *cmdline
)
644 if (assertFailureLine
) {
645 if (assertFailureFile
) {
646 cliPrintErrorLinef("Assertion failed at line %d, file %s", assertFailureLine
, assertFailureFile
);
649 cliPrintErrorLinef("Assertion failed at line %d", assertFailureLine
);
652 if (commandBatchActive
) {
653 commandBatchError
= true;
658 cliPrintLine("No assert() failed");
663 static void printAux(uint8_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
)
665 const char *format
= "aux %u %u %u %u %u";
666 // print out aux channel settings
667 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
668 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
669 bool equalsDefault
= false;
670 if (defaultModeActivationConditions
) {
671 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
672 equalsDefault
= mac
->modeId
== macDefault
->modeId
673 && mac
->auxChannelIndex
== macDefault
->auxChannelIndex
674 && mac
->range
.startStep
== macDefault
->range
.startStep
675 && mac
->range
.endStep
== macDefault
->range
.endStep
;
676 const box_t
*box
= findBoxByActiveBoxId(macDefault
->modeId
);
677 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
680 macDefault
->auxChannelIndex
,
681 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
682 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
)
685 const box_t
*box
= findBoxByActiveBoxId(mac
->modeId
);
686 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
689 mac
->auxChannelIndex
,
690 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
691 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
)
696 static void cliAux(char *cmdline
)
701 if (isEmpty(cmdline
)) {
702 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
);
706 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
707 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
708 uint8_t validArgumentCount
= 0;
713 const box_t
*box
= findBoxByPermanentId(val
);
715 mac
->modeId
= box
->boxId
;
716 validArgumentCount
++;
723 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
724 mac
->auxChannelIndex
= val
;
725 validArgumentCount
++;
728 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
730 if (validArgumentCount
!= 4) {
731 memset(mac
, 0, sizeof(modeActivationCondition_t
));
734 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
739 static void printSerial(uint8_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
)
741 const char *format
= "serial %d %d %ld %ld %ld %ld";
742 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
743 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
746 bool equalsDefault
= false;
747 if (serialConfigDefault
) {
748 equalsDefault
= serialConfig
->portConfigs
[i
].identifier
== serialConfigDefault
->portConfigs
[i
].identifier
749 && serialConfig
->portConfigs
[i
].functionMask
== serialConfigDefault
->portConfigs
[i
].functionMask
750 && serialConfig
->portConfigs
[i
].msp_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
751 && serialConfig
->portConfigs
[i
].gps_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
752 && serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
753 && serialConfig
->portConfigs
[i
].peripheral_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].peripheral_baudrateIndex
;
754 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
755 serialConfigDefault
->portConfigs
[i
].identifier
,
756 serialConfigDefault
->portConfigs
[i
].functionMask
,
757 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
758 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
759 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
760 baudRates
[serialConfigDefault
->portConfigs
[i
].peripheral_baudrateIndex
]
763 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
764 serialConfig
->portConfigs
[i
].identifier
,
765 serialConfig
->portConfigs
[i
].functionMask
,
766 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
767 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
768 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
769 baudRates
[serialConfig
->portConfigs
[i
].peripheral_baudrateIndex
]
774 static void cliSerial(char *cmdline
)
776 if (isEmpty(cmdline
)) {
777 printSerial(DUMP_MASTER
, serialConfig(), NULL
);
780 serialPortConfig_t portConfig
;
782 serialPortConfig_t
*currentConfig
;
784 uint8_t validArgumentCount
= 0;
786 const char *ptr
= cmdline
;
788 int val
= fastA2I(ptr
++);
789 currentConfig
= serialFindPortConfiguration(val
);
790 if (!currentConfig
) {
792 cliPrintErrorLinef("Invalid port ID %d", val
);
795 memcpy(&portConfig
, currentConfig
, sizeof(portConfig
));
796 validArgumentCount
++;
805 portConfig
.functionMask
|= (1 << val
);
811 portConfig
.functionMask
&= 0xFFFFFFFF ^ (1 << val
);
816 portConfig
.functionMask
= val
& 0xFFFFFFFF;
819 validArgumentCount
++;
822 for (int i
= 0; i
< 4; i
++) {
830 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
831 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
837 baudRateIndex
= constrain(baudRateIndex
, BAUD_MIN
, BAUD_MAX
);
838 portConfig
.msp_baudrateIndex
= baudRateIndex
;
841 baudRateIndex
= constrain(baudRateIndex
, BAUD_MIN
, BAUD_MAX
);
842 portConfig
.gps_baudrateIndex
= baudRateIndex
;
845 baudRateIndex
= constrain(baudRateIndex
, BAUD_MIN
, BAUD_MAX
);
846 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
849 baudRateIndex
= constrain(baudRateIndex
, BAUD_MIN
, BAUD_MAX
);
850 portConfig
.peripheral_baudrateIndex
= baudRateIndex
;
854 validArgumentCount
++;
857 if (validArgumentCount
< 2) {
862 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
865 #ifdef USE_SERIAL_PASSTHROUGH
866 static void cliSerialPassthrough(char *cmdline
)
870 if (isEmpty(cmdline
)) {
878 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
881 while (tok
!= NULL
) {
890 if (strstr(tok
, "rx") || strstr(tok
, "RX"))
892 if (strstr(tok
, "tx") || strstr(tok
, "TX"))
897 tok
= strtok_r(NULL
, " ", &saveptr
);
900 serialPort_t
*passThroughPort
;
901 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
902 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
904 tfp_printf("Port %d is closed, must specify baud.\r\n", id
);
910 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
, NULL
,
912 SERIAL_NOT_INVERTED
);
913 if (!passThroughPort
) {
914 tfp_printf("Port %d could not be opened.\r\n", id
);
917 tfp_printf("Port %d opened, baud = %u.\r\n", id
, (unsigned)baud
);
919 passThroughPort
= passThroughPortUsage
->serialPort
;
920 // If the user supplied a mode, override the port's mode, otherwise
921 // leave the mode unchanged. serialPassthrough() handles one-way ports.
922 tfp_printf("Port %d already open.\r\n", id
);
923 if (mode
&& passThroughPort
->mode
!= mode
) {
924 tfp_printf("Adjusting mode from %d to %d.\r\n",
925 passThroughPort
->mode
, mode
);
926 serialSetMode(passThroughPort
, mode
);
928 // If this port has a rx callback associated we need to remove it now.
929 // Otherwise no data will be pushed in the serial port buffer!
930 if (passThroughPort
->rxCallback
) {
931 tfp_printf("Removing rxCallback\r\n");
932 passThroughPort
->rxCallback
= 0;
936 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id
);
938 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
942 static void printAdjustmentRange(uint8_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
)
944 const char *format
= "adjrange %u %u %u %u %u %u %u";
945 // print out adjustment ranges channel settings
946 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
947 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
948 bool equalsDefault
= false;
949 if (defaultAdjustmentRanges
) {
950 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
951 equalsDefault
= ar
->auxChannelIndex
== arDefault
->auxChannelIndex
952 && ar
->range
.startStep
== arDefault
->range
.startStep
953 && ar
->range
.endStep
== arDefault
->range
.endStep
954 && ar
->adjustmentFunction
== arDefault
->adjustmentFunction
955 && ar
->auxSwitchChannelIndex
== arDefault
->auxSwitchChannelIndex
956 && ar
->adjustmentIndex
== arDefault
->adjustmentIndex
;
957 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
959 arDefault
->adjustmentIndex
,
960 arDefault
->auxChannelIndex
,
961 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
962 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
963 arDefault
->adjustmentFunction
,
964 arDefault
->auxSwitchChannelIndex
967 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
971 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
972 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
973 ar
->adjustmentFunction
,
974 ar
->auxSwitchChannelIndex
979 static void cliAdjustmentRange(char *cmdline
)
984 if (isEmpty(cmdline
)) {
985 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
);
989 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
990 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
991 uint8_t validArgumentCount
= 0;
996 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
997 ar
->adjustmentIndex
= val
;
998 validArgumentCount
++;
1004 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1005 ar
->auxChannelIndex
= val
;
1006 validArgumentCount
++;
1010 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1015 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1016 ar
->adjustmentFunction
= val
;
1017 validArgumentCount
++;
1023 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1024 ar
->auxSwitchChannelIndex
= val
;
1025 validArgumentCount
++;
1029 if (validArgumentCount
!= 6) {
1030 memset(ar
, 0, sizeof(adjustmentRange_t
));
1031 cliShowParseError();
1034 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1039 static void printMotorMix(uint8_t dumpMask
, const motorMixer_t
*primaryMotorMixer
, const motorMixer_t
*defaultprimaryMotorMixer
)
1041 const char *format
= "mmix %d %s %s %s %s";
1042 char buf0
[FTOA_BUFFER_SIZE
];
1043 char buf1
[FTOA_BUFFER_SIZE
];
1044 char buf2
[FTOA_BUFFER_SIZE
];
1045 char buf3
[FTOA_BUFFER_SIZE
];
1046 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1047 if (primaryMotorMixer
[i
].throttle
== 0.0f
)
1049 const float thr
= primaryMotorMixer
[i
].throttle
;
1050 const float roll
= primaryMotorMixer
[i
].roll
;
1051 const float pitch
= primaryMotorMixer
[i
].pitch
;
1052 const float yaw
= primaryMotorMixer
[i
].yaw
;
1053 bool equalsDefault
= false;
1054 if (defaultprimaryMotorMixer
) {
1055 const float thrDefault
= defaultprimaryMotorMixer
[i
].throttle
;
1056 const float rollDefault
= defaultprimaryMotorMixer
[i
].roll
;
1057 const float pitchDefault
= defaultprimaryMotorMixer
[i
].pitch
;
1058 const float yawDefault
= defaultprimaryMotorMixer
[i
].yaw
;
1059 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1061 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1063 ftoa(thrDefault
, buf0
),
1064 ftoa(rollDefault
, buf1
),
1065 ftoa(pitchDefault
, buf2
),
1066 ftoa(yawDefault
, buf3
));
1068 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1077 static void cliMotorMix(char *cmdline
)
1082 if (isEmpty(cmdline
)) {
1083 printMotorMix(DUMP_MASTER
, primaryMotorMixer(0), NULL
);
1084 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1085 // erase custom mixer
1086 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1087 primaryMotorMixerMutable(i
)->throttle
= 0.0f
;
1091 uint32_t i
= fastA2I(ptr
); // get motor number
1092 if (i
< MAX_SUPPORTED_MOTORS
) {
1095 primaryMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1100 primaryMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1105 primaryMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1110 primaryMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1114 cliShowParseError();
1116 printMotorMix(DUMP_MASTER
, primaryMotorMixer(0), NULL
);
1119 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
1124 static void printRxRange(uint8_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
)
1126 const char *format
= "rxrange %u %u %u";
1127 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1128 bool equalsDefault
= false;
1129 if (defaultChannelRangeConfigs
) {
1130 equalsDefault
= channelRangeConfigs
[i
].min
== defaultChannelRangeConfigs
[i
].min
1131 && channelRangeConfigs
[i
].max
== defaultChannelRangeConfigs
[i
].max
;
1132 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1134 defaultChannelRangeConfigs
[i
].min
,
1135 defaultChannelRangeConfigs
[i
].max
1138 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1140 channelRangeConfigs
[i
].min
,
1141 channelRangeConfigs
[i
].max
1146 static void cliRxRange(char *cmdline
)
1148 int i
, validArgumentCount
= 0;
1151 if (isEmpty(cmdline
)) {
1152 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
);
1153 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1154 resetAllRxChannelRangeConfigurations();
1158 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1159 int rangeMin
, rangeMax
;
1163 rangeMin
= fastA2I(ptr
);
1164 validArgumentCount
++;
1169 rangeMax
= fastA2I(ptr
);
1170 validArgumentCount
++;
1173 if (validArgumentCount
!= 2) {
1174 cliShowParseError();
1175 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1176 cliShowParseError();
1178 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1179 channelRangeConfig
->min
= rangeMin
;
1180 channelRangeConfig
->max
= rangeMax
;
1183 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT
- 1);
1188 #ifdef USE_TEMPERATURE_SENSOR
1189 static void printTempSensor(uint8_t dumpMask
, const tempSensorConfig_t
*tempSensorConfigs
, const tempSensorConfig_t
*defaultTempSensorConfigs
)
1191 const char *format
= "temp_sensor %u %u %s %d %d %u %s";
1192 for (uint8_t i
= 0; i
< MAX_TEMP_SENSORS
; i
++) {
1193 bool equalsDefault
= false;
1194 char label
[5], hex_address
[17];
1195 strncpy(label
, tempSensorConfigs
[i
].label
, TEMPERATURE_LABEL_LEN
);
1197 tempSensorAddressToString(tempSensorConfigs
[i
].address
, hex_address
);
1198 if (defaultTempSensorConfigs
) {
1199 equalsDefault
= tempSensorConfigs
[i
].type
== defaultTempSensorConfigs
[i
].type
1200 && tempSensorConfigs
[i
].address
== defaultTempSensorConfigs
[i
].address
1201 && tempSensorConfigs
[i
].osdSymbol
== defaultTempSensorConfigs
[i
].osdSymbol
1202 && !memcmp(tempSensorConfigs
[i
].label
, defaultTempSensorConfigs
[i
].label
, TEMPERATURE_LABEL_LEN
)
1203 && tempSensorConfigs
[i
].alarm_min
== defaultTempSensorConfigs
[i
].alarm_min
1204 && tempSensorConfigs
[i
].alarm_max
== defaultTempSensorConfigs
[i
].alarm_max
;
1205 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1207 defaultTempSensorConfigs
[i
].type
,
1209 defaultTempSensorConfigs
[i
].alarm_min
,
1210 defaultTempSensorConfigs
[i
].alarm_max
,
1215 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1217 tempSensorConfigs
[i
].type
,
1219 tempSensorConfigs
[i
].alarm_min
,
1220 tempSensorConfigs
[i
].alarm_max
,
1221 tempSensorConfigs
[i
].osdSymbol
,
1227 static void cliTempSensor(char *cmdline
)
1229 if (isEmpty(cmdline
)) {
1230 printTempSensor(DUMP_MASTER
, tempSensorConfig(0), NULL
);
1231 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1232 resetTempSensorConfig();
1235 const char *ptr
= cmdline
, *label
;
1236 int16_t type
=0, alarm_min
=0, alarm_max
=0;
1237 bool addressValid
= false;
1240 uint8_t validArgumentCount
= 0;
1242 if (i
>= 0 && i
< MAX_TEMP_SENSORS
) {
1246 type
= fastA2I(ptr
);
1247 validArgumentCount
++;
1252 addressValid
= tempSensorStringToAddress(ptr
, &address
);
1253 validArgumentCount
++;
1258 alarm_min
= fastA2I(ptr
);
1259 validArgumentCount
++;
1264 alarm_max
= fastA2I(ptr
);
1265 validArgumentCount
++;
1270 osdSymbol
= fastA2I(ptr
);
1271 validArgumentCount
++;
1274 label
= nextArg(ptr
);
1276 ++validArgumentCount
;
1280 if (validArgumentCount
< 4) {
1281 cliShowParseError();
1282 } else if (type
< 0 || type
> TEMP_SENSOR_DS18B20
|| alarm_min
< -550 || alarm_min
> 1250 || alarm_max
< -550 || alarm_max
> 1250 || osdSymbol
< 0 || osdSymbol
> TEMP_SENSOR_SYM_COUNT
|| strlen(label
) > TEMPERATURE_LABEL_LEN
|| !addressValid
) {
1283 cliShowParseError();
1285 tempSensorConfig_t
*sensorConfig
= tempSensorConfigMutable(i
);
1286 sensorConfig
->type
= type
;
1287 sensorConfig
->address
= address
;
1288 sensorConfig
->alarm_min
= alarm_min
;
1289 sensorConfig
->alarm_max
= alarm_max
;
1290 sensorConfig
->osdSymbol
= osdSymbol
;
1291 for (uint8_t index
= 0; index
< TEMPERATURE_LABEL_LEN
; ++index
) {
1292 sensorConfig
->label
[index
] = toupper(label
[index
]);
1293 if (label
[index
] == '\0') break;
1297 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS
- 1);
1303 #if defined(USE_SAFE_HOME)
1304 static void printSafeHomes(uint8_t dumpMask
, const navSafeHome_t
*navSafeHome
, const navSafeHome_t
*defaultSafeHome
)
1306 const char *format
= "safehome %u %u %d %d"; // uint8_t enabled, int32_t lat; int32_t lon
1307 for (uint8_t i
= 0; i
< MAX_SAFE_HOMES
; i
++) {
1308 bool equalsDefault
= false;
1309 if (defaultSafeHome
) {
1310 equalsDefault
= navSafeHome
[i
].enabled
== defaultSafeHome
[i
].enabled
1311 && navSafeHome
[i
].lat
== defaultSafeHome
[i
].lat
1312 && navSafeHome
[i
].lon
== defaultSafeHome
[i
].lon
;
1313 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1314 defaultSafeHome
[i
].enabled
, defaultSafeHome
[i
].lat
, defaultSafeHome
[i
].lon
);
1316 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1317 navSafeHome
[i
].enabled
, navSafeHome
[i
].lat
, navSafeHome
[i
].lon
);
1321 static void cliSafeHomes(char *cmdline
)
1323 if (isEmpty(cmdline
)) {
1324 printSafeHomes(DUMP_MASTER
, safeHomeConfig(0), NULL
);
1325 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1328 int32_t lat
=0, lon
=0;
1330 uint8_t validArgumentCount
= 0;
1331 const char *ptr
= cmdline
;
1332 int8_t i
= fastA2I(ptr
);
1333 if (i
< 0 || i
>= MAX_SAFE_HOMES
) {
1334 cliShowArgumentRangeError("safehome index", 0, MAX_SAFE_HOMES
- 1);
1336 if ((ptr
= nextArg(ptr
))) {
1337 enabled
= fastA2I(ptr
);
1338 validArgumentCount
++;
1340 if ((ptr
= nextArg(ptr
))) {
1342 validArgumentCount
++;
1344 if ((ptr
= nextArg(ptr
))) {
1346 validArgumentCount
++;
1348 if ((ptr
= nextArg(ptr
))) {
1349 // check for too many arguments
1350 validArgumentCount
++;
1352 if (validArgumentCount
!= 3) {
1353 cliShowParseError();
1355 safeHomeConfigMutable(i
)->enabled
= enabled
;
1356 safeHomeConfigMutable(i
)->lat
= lat
;
1357 safeHomeConfigMutable(i
)->lon
= lon
;
1364 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1365 static void printWaypoints(uint8_t dumpMask
, const navWaypoint_t
*navWaypoint
, const navWaypoint_t
*defaultNavWaypoint
)
1367 cliPrintLinef("#wp %d %svalid", posControl
.waypointCount
, posControl
.waypointListValid
? "" : "in"); //int8_t bool
1368 const char *format
= "wp %u %u %d %d %d %d %d %d %u"; //uint8_t action; int32_t lat; int32_t lon; int32_t alt; int16_t p1 int16_t p2 int16_t p3; uint8_t flag
1369 for (uint8_t i
= 0; i
< NAV_MAX_WAYPOINTS
; i
++) {
1370 bool equalsDefault
= false;
1371 if (defaultNavWaypoint
) {
1372 equalsDefault
= navWaypoint
[i
].action
== defaultNavWaypoint
[i
].action
1373 && navWaypoint
[i
].lat
== defaultNavWaypoint
[i
].lat
1374 && navWaypoint
[i
].lon
== defaultNavWaypoint
[i
].lon
1375 && navWaypoint
[i
].alt
== defaultNavWaypoint
[i
].alt
1376 && navWaypoint
[i
].p1
== defaultNavWaypoint
[i
].p1
1377 && navWaypoint
[i
].p2
== defaultNavWaypoint
[i
].p2
1378 && navWaypoint
[i
].p3
== defaultNavWaypoint
[i
].p3
1379 && navWaypoint
[i
].flag
== defaultNavWaypoint
[i
].flag
;
1380 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1382 defaultNavWaypoint
[i
].action
,
1383 defaultNavWaypoint
[i
].lat
,
1384 defaultNavWaypoint
[i
].lon
,
1385 defaultNavWaypoint
[i
].alt
,
1386 defaultNavWaypoint
[i
].p1
,
1387 defaultNavWaypoint
[i
].p2
,
1388 defaultNavWaypoint
[i
].p3
,
1389 defaultNavWaypoint
[i
].flag
1392 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1394 navWaypoint
[i
].action
,
1406 static void cliWaypoints(char *cmdline
)
1408 #ifdef USE_MULTI_MISSION
1409 static int8_t multiMissionWPCounter
= 0;
1411 if (isEmpty(cmdline
)) {
1412 printWaypoints(DUMP_MASTER
, posControl
.waypointList
, NULL
);
1413 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1414 resetWaypointList();
1415 } else if (sl_strcasecmp(cmdline
, "load") == 0) {
1416 loadNonVolatileWaypointList(true);
1417 } else if (sl_strcasecmp(cmdline
, "save") == 0) {
1418 posControl
.waypointListValid
= false;
1419 for (int i
= 0; i
< NAV_MAX_WAYPOINTS
; i
++) {
1420 if (!(posControl
.waypointList
[i
].action
== NAV_WP_ACTION_WAYPOINT
|| posControl
.waypointList
[i
].action
== NAV_WP_ACTION_JUMP
|| posControl
.waypointList
[i
].action
== NAV_WP_ACTION_RTH
|| posControl
.waypointList
[i
].action
== NAV_WP_ACTION_HOLD_TIME
|| posControl
.waypointList
[i
].action
== NAV_WP_ACTION_LAND
|| posControl
.waypointList
[i
].action
== NAV_WP_ACTION_SET_POI
|| posControl
.waypointList
[i
].action
== NAV_WP_ACTION_SET_HEAD
)) break;
1421 if (posControl
.waypointList
[i
].flag
== NAV_WP_FLAG_LAST
) {
1422 #ifdef USE_MULTI_MISSION
1423 if (posControl
.multiMissionCount
== 1) {
1424 posControl
.waypointCount
= i
+ 1;
1425 posControl
.waypointListValid
= true;
1426 multiMissionWPCounter
= 0;
1427 posControl
.multiMissionCount
= 0;
1430 posControl
.multiMissionCount
-= 1;
1433 posControl
.waypointCount
= i
+ 1;
1434 posControl
.waypointListValid
= true;
1439 if (posControl
.waypointListValid
) {
1440 saveNonVolatileWaypointList();
1442 cliShowParseError();
1445 int16_t i
, p1
=0,p2
=0,p3
=0,tmp
=0;
1446 uint8_t action
=0, flag
=0;
1447 int32_t lat
=0, lon
=0, alt
=0;
1448 uint8_t validArgumentCount
= 0;
1449 const char *ptr
= cmdline
;
1451 #ifdef USE_MULTI_MISSION
1452 if (i
+ multiMissionWPCounter
>= 0 && i
+ multiMissionWPCounter
< NAV_MAX_WAYPOINTS
) {
1454 if (i
>= 0 && i
< NAV_MAX_WAYPOINTS
) {
1458 action
= fastA2I(ptr
);
1459 validArgumentCount
++;
1464 validArgumentCount
++;
1469 validArgumentCount
++;
1474 validArgumentCount
++;
1479 validArgumentCount
++;
1484 validArgumentCount
++;
1486 /* We support pre-2.5 6 values (... p1,flags) or
1487 * 2.5 and later, 8 values (... p1,p2,p3,flags)
1493 validArgumentCount
++;
1496 flag
= fastA2I(ptr
);
1497 validArgumentCount
++;
1503 if (!(validArgumentCount
== 6 || validArgumentCount
== 8)) {
1504 cliShowParseError();
1505 } else if (!(action
== 0 || action
== NAV_WP_ACTION_WAYPOINT
|| action
== NAV_WP_ACTION_RTH
|| action
== NAV_WP_ACTION_JUMP
|| action
== NAV_WP_ACTION_HOLD_TIME
|| action
== NAV_WP_ACTION_LAND
|| action
== NAV_WP_ACTION_SET_POI
|| action
== NAV_WP_ACTION_SET_HEAD
) || !(flag
== 0 || flag
== NAV_WP_FLAG_LAST
|| flag
== NAV_WP_FLAG_HOME
)) {
1506 cliShowParseError();
1508 #ifdef USE_MULTI_MISSION
1509 if (i
+ multiMissionWPCounter
== 0) {
1510 posControl
.multiMissionCount
= 0;
1513 posControl
.waypointList
[i
+ multiMissionWPCounter
].action
= action
;
1514 posControl
.waypointList
[i
+ multiMissionWPCounter
].lat
= lat
;
1515 posControl
.waypointList
[i
+ multiMissionWPCounter
].lon
= lon
;
1516 posControl
.waypointList
[i
+ multiMissionWPCounter
].alt
= alt
;
1517 posControl
.waypointList
[i
+ multiMissionWPCounter
].p1
= p1
;
1518 posControl
.waypointList
[i
+ multiMissionWPCounter
].p2
= p2
;
1519 posControl
.waypointList
[i
+ multiMissionWPCounter
].p3
= p3
;
1520 posControl
.waypointList
[i
+ multiMissionWPCounter
].flag
= flag
;
1522 // Process WP entries made up of multiple successive WP missions (multiple NAV_WP_FLAG_LAST entries)
1523 // Individial missions loaded at runtime, mission selected nav_waypoint_multi_mission_index
1524 if (flag
== NAV_WP_FLAG_LAST
) {
1525 multiMissionWPCounter
+= i
+ 1;
1526 posControl
.multiMissionCount
+= 1;
1529 posControl
.waypointList
[i
].action
= action
;
1530 posControl
.waypointList
[i
].lat
= lat
;
1531 posControl
.waypointList
[i
].lon
= lon
;
1532 posControl
.waypointList
[i
].alt
= alt
;
1533 posControl
.waypointList
[i
].p1
= p1
;
1534 posControl
.waypointList
[i
].p2
= p2
;
1535 posControl
.waypointList
[i
].p3
= p3
;
1536 posControl
.waypointList
[i
].flag
= flag
;
1540 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS
- 1);
1547 #ifdef USE_LED_STRIP
1548 static void printLed(uint8_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
)
1550 const char *format
= "led %u %s";
1551 char ledConfigBuffer
[20];
1552 char ledConfigDefaultBuffer
[20];
1553 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1554 ledConfig_t ledConfig
= ledConfigs
[i
];
1555 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1556 bool equalsDefault
= false;
1557 if (defaultLedConfigs
) {
1558 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1559 equalsDefault
= !memcmp(&ledConfig
, &ledConfigDefault
, sizeof(ledConfig_t
));
1560 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1561 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1563 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1567 static void cliLed(char *cmdline
)
1572 if (isEmpty(cmdline
)) {
1573 printLed(DUMP_MASTER
, ledStripConfig()->ledConfigs
, NULL
);
1577 if (i
< LED_MAX_STRIP_LENGTH
) {
1578 ptr
= nextArg(cmdline
);
1579 if (!parseLedStripConfig(i
, ptr
)) {
1580 cliShowParseError();
1583 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH
- 1);
1588 static void printColor(uint8_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
)
1590 const char *format
= "color %u %d,%u,%u";
1591 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1592 const hsvColor_t
*color
= &colors
[i
];
1593 bool equalsDefault
= false;
1594 if (defaultColors
) {
1595 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1596 equalsDefault
= color
->h
== colorDefault
->h
1597 && color
->s
== colorDefault
->s
1598 && color
->v
== colorDefault
->v
;
1599 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1601 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1605 static void cliColor(char *cmdline
)
1607 if (isEmpty(cmdline
)) {
1608 printColor(DUMP_MASTER
, ledStripConfig()->colors
, NULL
);
1610 const char *ptr
= cmdline
;
1611 const int i
= fastA2I(ptr
);
1612 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1613 ptr
= nextArg(cmdline
);
1614 if (!parseColor(i
, ptr
)) {
1615 cliShowParseError();
1618 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1623 static void printModeColor(uint8_t dumpMask
, const ledStripConfig_t
*ledStripConfig
, const ledStripConfig_t
*defaultLedStripConfig
)
1625 const char *format
= "mode_color %u %u %u";
1626 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1627 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1628 int colorIndex
= ledStripConfig
->modeColors
[i
].color
[j
];
1629 bool equalsDefault
= false;
1630 if (defaultLedStripConfig
) {
1631 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1632 equalsDefault
= colorIndex
== colorIndexDefault
;
1633 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1635 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1639 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1640 const int colorIndex
= ledStripConfig
->specialColors
.color
[j
];
1641 bool equalsDefault
= false;
1642 if (defaultLedStripConfig
) {
1643 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1644 equalsDefault
= colorIndex
== colorIndexDefault
;
1645 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1647 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1651 static void cliModeColor(char *cmdline
)
1655 if (isEmpty(cmdline
)) {
1656 printModeColor(DUMP_MASTER
, ledStripConfig(), NULL
);
1658 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1659 int args
[ARGS_COUNT
];
1661 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1662 while (ptr
&& argNo
< ARGS_COUNT
) {
1663 args
[argNo
++] = fastA2I(ptr
);
1664 ptr
= strtok_r(NULL
, " ", &saveptr
);
1667 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1668 cliShowParseError();
1672 int modeIdx
= args
[MODE
];
1673 int funIdx
= args
[FUNCTION
];
1674 int color
= args
[COLOR
];
1675 if (!setModeColor(modeIdx
, funIdx
, color
)) {
1676 cliShowParseError();
1679 // values are validated
1680 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1685 static void cliDelay(char* cmdLine
) {
1687 if (isEmpty(cmdLine
)) {
1689 cliPrintLine("CLI delay deactivated");
1693 ms
= fastA2I(cmdLine
);
1696 cliPrintLinef("CLI delay set to %d ms", ms
);
1699 cliShowParseError();
1704 static void printServo(uint8_t dumpMask
, const servoParam_t
*servoParam
, const servoParam_t
*defaultServoParam
)
1706 // print out servo settings
1707 const char *format
= "servo %u %d %d %d %d";
1708 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1709 const servoParam_t
*servoConf
= &servoParam
[i
];
1710 bool equalsDefault
= false;
1711 if (defaultServoParam
) {
1712 const servoParam_t
*servoConfDefault
= &defaultServoParam
[i
];
1713 equalsDefault
= servoConf
->min
== servoConfDefault
->min
1714 && servoConf
->max
== servoConfDefault
->max
1715 && servoConf
->middle
== servoConfDefault
->middle
1716 && servoConf
->rate
== servoConfDefault
->rate
;
1717 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1719 servoConfDefault
->min
,
1720 servoConfDefault
->max
,
1721 servoConfDefault
->middle
,
1722 servoConfDefault
->rate
1725 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1735 static void cliServo(char *cmdline
)
1737 enum { SERVO_ARGUMENT_COUNT
= 5 };
1738 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1740 servoParam_t
*servo
;
1745 if (isEmpty(cmdline
)) {
1746 printServo(DUMP_MASTER
, servoParams(0), NULL
);
1748 int validArgumentCount
= 0;
1752 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1754 // If command line doesn't fit the format, don't modify the config
1756 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1757 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1758 cliShowParseError();
1762 arguments
[validArgumentCount
++] = fastA2I(ptr
);
1766 } while (*ptr
>= '0' && *ptr
<= '9');
1767 } else if (*ptr
== ' ') {
1770 cliShowParseError();
1775 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
};
1777 i
= arguments
[INDEX
];
1779 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1780 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1781 cliShowParseError();
1785 servo
= servoParamsMutable(i
);
1788 arguments
[MIN
] < SERVO_OUTPUT_MIN
|| arguments
[MIN
] > SERVO_OUTPUT_MAX
||
1789 arguments
[MAX
] < SERVO_OUTPUT_MIN
|| arguments
[MAX
] > SERVO_OUTPUT_MAX
||
1790 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
1791 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
1792 arguments
[RATE
] < -125 || arguments
[RATE
] > 125
1794 cliShowParseError();
1798 servo
->min
= arguments
[MIN
];
1799 servo
->max
= arguments
[MAX
];
1800 servo
->middle
= arguments
[MIDDLE
];
1801 servo
->rate
= arguments
[RATE
];
1805 static void printServoMix(uint8_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
)
1807 const char *format
= "smix %d %d %d %d %d %d";
1808 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1809 const servoMixer_t customServoMixer
= customServoMixers
[i
];
1810 if (customServoMixer
.rate
== 0) {
1814 bool equalsDefault
= false;
1815 if (defaultCustomServoMixers
) {
1816 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
1817 equalsDefault
= customServoMixer
.targetChannel
== customServoMixerDefault
.targetChannel
1818 && customServoMixer
.inputSource
== customServoMixerDefault
.inputSource
1819 && customServoMixer
.rate
== customServoMixerDefault
.rate
1820 && customServoMixer
.speed
== customServoMixerDefault
.speed
1821 #ifdef USE_PROGRAMMING_FRAMEWORK
1822 && customServoMixer
.conditionId
== customServoMixerDefault
.conditionId
1826 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1828 customServoMixerDefault
.targetChannel
,
1829 customServoMixerDefault
.inputSource
,
1830 customServoMixerDefault
.rate
,
1831 customServoMixerDefault
.speed
,
1832 #ifdef USE_PROGRAMMING_FRAMEWORK
1833 customServoMixer
.conditionId
1839 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1841 customServoMixer
.targetChannel
,
1842 customServoMixer
.inputSource
,
1843 customServoMixer
.rate
,
1844 customServoMixer
.speed
,
1845 #ifdef USE_PROGRAMMING_FRAMEWORK
1846 customServoMixer
.conditionId
1854 static void cliServoMix(char *cmdline
)
1857 int args
[6], check
= 0;
1858 uint8_t len
= strlen(cmdline
);
1861 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
);
1862 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1863 // erase custom mixer
1864 pgResetCopy(customServoMixersMutable(0), PG_SERVO_MIXER
);
1866 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, CONDITION
, ARGS_COUNT
};
1867 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1868 args
[CONDITION
] = -1;
1869 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1870 args
[check
++] = fastA2I(ptr
);
1871 ptr
= strtok_r(NULL
, " ", &saveptr
);
1874 if (ptr
!= NULL
|| (check
< ARGS_COUNT
- 1)) {
1875 cliShowParseError();
1879 int32_t i
= args
[RULE
];
1881 i
>= 0 && i
< MAX_SERVO_RULES
&&
1882 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
1883 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
1884 args
[RATE
] >= -1000 && args
[RATE
] <= 1000 &&
1885 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
1886 args
[CONDITION
] >= -1 && args
[CONDITION
] < MAX_LOGIC_CONDITIONS
1888 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
1889 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
1890 customServoMixersMutable(i
)->rate
= args
[RATE
];
1891 customServoMixersMutable(i
)->speed
= args
[SPEED
];
1892 #ifdef USE_PROGRAMMING_FRAMEWORK
1893 customServoMixersMutable(i
)->conditionId
= args
[CONDITION
];
1897 cliShowParseError();
1902 #ifdef USE_PROGRAMMING_FRAMEWORK
1904 static void printLogic(uint8_t dumpMask
, const logicCondition_t
*logicConditions
, const logicCondition_t
*defaultLogicConditions
, int16_t showLC
)
1906 const char *format
= "logic %d %d %d %d %d %d %d %d %d";
1907 for (uint8_t i
= 0; i
< MAX_LOGIC_CONDITIONS
; i
++) {
1908 if (showLC
== -1 || showLC
== i
) {
1909 const logicCondition_t logic
= logicConditions
[i
];
1911 bool equalsDefault
= false;
1912 if (defaultLogicConditions
) {
1913 logicCondition_t defaultValue
= defaultLogicConditions
[i
];
1915 logic
.enabled
== defaultValue
.enabled
&&
1916 logic
.activatorId
== defaultValue
.activatorId
&&
1917 logic
.operation
== defaultValue
.operation
&&
1918 logic
.operandA
.type
== defaultValue
.operandA
.type
&&
1919 logic
.operandA
.value
== defaultValue
.operandA
.value
&&
1920 logic
.operandB
.type
== defaultValue
.operandB
.type
&&
1921 logic
.operandB
.value
== defaultValue
.operandB
.value
&&
1922 logic
.flags
== defaultValue
.flags
;
1924 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1929 logic
.operandA
.type
,
1930 logic
.operandA
.value
,
1931 logic
.operandB
.type
,
1932 logic
.operandB
.value
,
1936 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1941 logic
.operandA
.type
,
1942 logic
.operandA
.value
,
1943 logic
.operandB
.type
,
1944 logic
.operandB
.value
,
1951 static void processCliLogic(char *cmdline
, int16_t lcIndex
) {
1953 int args
[9], check
= 0;
1954 uint8_t len
= strlen(cmdline
);
1957 if (!commandBatchActive
) {
1958 printLogic(DUMP_MASTER
, logicConditions(0), NULL
, -1);
1959 } else if (lcIndex
>= 0) {
1960 printLogic(DUMP_MASTER
, logicConditions(0), NULL
, lcIndex
);
1962 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1963 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS
);
1977 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1978 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1979 args
[check
++] = fastA2I(ptr
);
1980 ptr
= strtok_r(NULL
, " ", &saveptr
);
1983 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
1984 cliShowParseError();
1988 int32_t i
= args
[INDEX
];
1990 i
>= 0 && i
< MAX_LOGIC_CONDITIONS
&&
1991 args
[ENABLED
] >= 0 && args
[ENABLED
] <= 1 &&
1992 args
[ACTIVATOR_ID
] >= -1 && args
[ACTIVATOR_ID
] < MAX_LOGIC_CONDITIONS
&&
1993 args
[OPERATION
] >= 0 && args
[OPERATION
] < LOGIC_CONDITION_LAST
&&
1994 args
[OPERAND_A_TYPE
] >= 0 && args
[OPERAND_A_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
1995 args
[OPERAND_A_VALUE
] >= -1000000 && args
[OPERAND_A_VALUE
] <= 1000000 &&
1996 args
[OPERAND_B_TYPE
] >= 0 && args
[OPERAND_B_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
1997 args
[OPERAND_B_VALUE
] >= -1000000 && args
[OPERAND_B_VALUE
] <= 1000000 &&
1998 args
[FLAGS
] >= 0 && args
[FLAGS
] <= 255
2001 logicConditionsMutable(i
)->enabled
= args
[ENABLED
];
2002 logicConditionsMutable(i
)->activatorId
= args
[ACTIVATOR_ID
];
2003 logicConditionsMutable(i
)->operation
= args
[OPERATION
];
2004 logicConditionsMutable(i
)->operandA
.type
= args
[OPERAND_A_TYPE
];
2005 logicConditionsMutable(i
)->operandA
.value
= args
[OPERAND_A_VALUE
];
2006 logicConditionsMutable(i
)->operandB
.type
= args
[OPERAND_B_TYPE
];
2007 logicConditionsMutable(i
)->operandB
.value
= args
[OPERAND_B_VALUE
];
2008 logicConditionsMutable(i
)->flags
= args
[FLAGS
];
2010 processCliLogic("", i
);
2012 cliShowParseError();
2017 static void cliLogic(char *cmdline
) {
2018 processCliLogic(cmdline
, -1);
2021 static void printGvar(uint8_t dumpMask
, const globalVariableConfig_t
*gvars
, const globalVariableConfig_t
*defaultGvars
)
2023 const char *format
= "gvar %d %d %d %d";
2024 for (uint32_t i
= 0; i
< MAX_GLOBAL_VARIABLES
; i
++) {
2025 const globalVariableConfig_t gvar
= gvars
[i
];
2027 bool equalsDefault
= false;
2029 globalVariableConfig_t defaultValue
= defaultGvars
[i
];
2031 gvar
.defaultValue
== defaultValue
.defaultValue
&&
2032 gvar
.min
== defaultValue
.min
&&
2033 gvar
.max
== defaultValue
.max
;
2035 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2042 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2051 static void cliGvar(char *cmdline
) {
2053 int args
[4], check
= 0;
2054 uint8_t len
= strlen(cmdline
);
2057 printGvar(DUMP_MASTER
, globalVariableConfigs(0), NULL
);
2058 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2059 pgResetCopy(globalVariableConfigsMutable(0), PG_GLOBAL_VARIABLE_CONFIG
);
2068 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2069 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2070 args
[check
++] = fastA2I(ptr
);
2071 ptr
= strtok_r(NULL
, " ", &saveptr
);
2074 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2075 cliShowParseError();
2079 int32_t i
= args
[INDEX
];
2081 i
>= 0 && i
< MAX_GLOBAL_VARIABLES
&&
2082 args
[DEFAULT
] >= INT32_MIN
&& args
[DEFAULT
] <= INT32_MAX
&&
2083 args
[MIN
] >= INT32_MIN
&& args
[MIN
] <= INT32_MAX
&&
2084 args
[MAX
] >= INT32_MIN
&& args
[MAX
] <= INT32_MAX
2086 globalVariableConfigsMutable(i
)->defaultValue
= args
[DEFAULT
];
2087 globalVariableConfigsMutable(i
)->min
= args
[MIN
];
2088 globalVariableConfigsMutable(i
)->max
= args
[MAX
];
2092 cliShowParseError();
2097 static void printPid(uint8_t dumpMask
, const programmingPid_t
*programmingPids
, const programmingPid_t
*defaultProgrammingPids
)
2099 const char *format
= "pid %d %d %d %d %d %d %d %d %d %d";
2100 for (uint32_t i
= 0; i
< MAX_PROGRAMMING_PID_COUNT
; i
++) {
2101 const programmingPid_t pid
= programmingPids
[i
];
2103 bool equalsDefault
= false;
2104 if (defaultProgrammingPids
) {
2105 programmingPid_t defaultValue
= defaultProgrammingPids
[i
];
2107 pid
.enabled
== defaultValue
.enabled
&&
2108 pid
.setpoint
.type
== defaultValue
.setpoint
.type
&&
2109 pid
.setpoint
.value
== defaultValue
.setpoint
.value
&&
2110 pid
.measurement
.type
== defaultValue
.measurement
.type
&&
2111 pid
.measurement
.value
== defaultValue
.measurement
.value
&&
2112 pid
.gains
.P
== defaultValue
.gains
.P
&&
2113 pid
.gains
.I
== defaultValue
.gains
.I
&&
2114 pid
.gains
.D
== defaultValue
.gains
.D
&&
2115 pid
.gains
.FF
== defaultValue
.gains
.FF
;
2117 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2122 pid
.measurement
.type
,
2123 pid
.measurement
.value
,
2130 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2135 pid
.measurement
.type
,
2136 pid
.measurement
.value
,
2145 static void cliPid(char *cmdline
) {
2147 int args
[10], check
= 0;
2148 uint8_t len
= strlen(cmdline
);
2151 printPid(DUMP_MASTER
, programmingPids(0), NULL
);
2152 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2153 pgResetCopy(programmingPidsMutable(0), PG_LOGIC_CONDITIONS
);
2168 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2169 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2170 args
[check
++] = fastA2I(ptr
);
2171 ptr
= strtok_r(NULL
, " ", &saveptr
);
2174 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2175 cliShowParseError();
2179 int32_t i
= args
[INDEX
];
2181 i
>= 0 && i
< MAX_PROGRAMMING_PID_COUNT
&&
2182 args
[ENABLED
] >= 0 && args
[ENABLED
] <= 1 &&
2183 args
[SETPOINT_TYPE
] >= 0 && args
[SETPOINT_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2184 args
[SETPOINT_VALUE
] >= -1000000 && args
[SETPOINT_VALUE
] <= 1000000 &&
2185 args
[MEASUREMENT_TYPE
] >= 0 && args
[MEASUREMENT_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2186 args
[MEASUREMENT_VALUE
] >= -1000000 && args
[MEASUREMENT_VALUE
] <= 1000000 &&
2187 args
[P_GAIN
] >= 0 && args
[P_GAIN
] <= INT16_MAX
&&
2188 args
[I_GAIN
] >= 0 && args
[I_GAIN
] <= INT16_MAX
&&
2189 args
[D_GAIN
] >= 0 && args
[D_GAIN
] <= INT16_MAX
&&
2190 args
[FF_GAIN
] >= 0 && args
[FF_GAIN
] <= INT16_MAX
2192 programmingPidsMutable(i
)->enabled
= args
[ENABLED
];
2193 programmingPidsMutable(i
)->setpoint
.type
= args
[SETPOINT_TYPE
];
2194 programmingPidsMutable(i
)->setpoint
.value
= args
[SETPOINT_VALUE
];
2195 programmingPidsMutable(i
)->measurement
.type
= args
[MEASUREMENT_TYPE
];
2196 programmingPidsMutable(i
)->measurement
.value
= args
[MEASUREMENT_VALUE
];
2197 programmingPidsMutable(i
)->gains
.P
= args
[P_GAIN
];
2198 programmingPidsMutable(i
)->gains
.I
= args
[I_GAIN
];
2199 programmingPidsMutable(i
)->gains
.D
= args
[D_GAIN
];
2200 programmingPidsMutable(i
)->gains
.FF
= args
[FF_GAIN
];
2204 cliShowParseError();
2213 static void cliWriteBytes(const uint8_t *buffer
, int count
)
2222 static void cliSdInfo(char *cmdline
)
2226 cliPrint("SD card: ");
2228 if (!sdcard_isInserted()) {
2229 cliPrintLine("None inserted");
2233 if (!sdcard_isInitialized()) {
2234 cliPrintLine("Startup failed");
2238 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
2240 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2241 metadata
->manufacturerID
,
2242 metadata
->numBlocks
/ 2, /* One block is half a kB */
2243 metadata
->productionMonth
,
2244 metadata
->productionYear
,
2245 metadata
->productRevisionMajor
,
2246 metadata
->productRevisionMinor
2249 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
2251 cliPrint("'\r\n" "Filesystem: ");
2253 switch (afatfs_getFilesystemState()) {
2254 case AFATFS_FILESYSTEM_STATE_READY
:
2257 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
2258 cliPrint("Initializing");
2260 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
2261 case AFATFS_FILESYSTEM_STATE_FATAL
:
2264 switch (afatfs_getLastError()) {
2265 case AFATFS_ERROR_BAD_MBR
:
2266 cliPrint(" - no FAT MBR partitions");
2268 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
2269 cliPrint(" - bad FAT header");
2271 case AFATFS_ERROR_GENERIC
:
2272 case AFATFS_ERROR_NONE
:
2273 ; // Nothing more detailed to print
2285 static void cliFlashInfo(char *cmdline
)
2289 const flashGeometry_t
*layout
= flashGetGeometry();
2291 if (layout
->totalSize
== 0) {
2292 cliPrintLine("Flash not available");
2296 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2297 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
);
2299 for (uint8_t index
= 0; index
< FLASH_MAX_PARTITIONS
; index
++) {
2300 const flashPartition_t
*partition
;
2302 cliPrintLine("Paritions:");
2304 partition
= flashPartitionFindByIndex(index
);
2308 cliPrintLinef(" %d: %s %u %u", index
, flashPartitionGetTypeName(partition
->type
), partition
->startSector
, partition
->endSector
);
2311 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
2313 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2314 FLASH_PARTITION_SECTOR_COUNT(flashPartition
) * layout
->sectorSize
,
2320 static void cliFlashErase(char *cmdline
)
2324 const flashGeometry_t
*layout
= flashGetGeometry();
2326 if (layout
->totalSize
== 0) {
2327 cliPrintLine("Flash not available");
2331 cliPrintLine("Erasing...");
2332 flashfsEraseCompletely();
2334 while (!flashIsReady()) {
2338 cliPrintLine("Done.");
2341 #ifdef USE_FLASH_TOOLS
2343 static void cliFlashWrite(char *cmdline
)
2345 const uint32_t address
= fastA2I(cmdline
);
2346 const char *text
= strchr(cmdline
, ' ');
2349 cliShowParseError();
2351 flashfsSeekAbs(address
);
2352 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2355 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2359 static void cliFlashRead(char *cmdline
)
2361 uint32_t address
= fastA2I(cmdline
);
2363 const char *nextArg
= strchr(cmdline
, ' ');
2366 cliShowParseError();
2368 uint32_t length
= fastA2I(nextArg
);
2370 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2373 while (length
> 0) {
2374 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2376 for (int i
= 0; i
< bytesRead
; i
++) {
2377 cliWrite(buffer
[i
]);
2380 length
-= bytesRead
;
2381 address
+= bytesRead
;
2383 if (bytesRead
== 0) {
2384 //Assume we reached the end of the volume or something fatal happened
2396 static void printOsdLayout(uint8_t dumpMask
, const osdLayoutsConfig_t
*config
, const osdLayoutsConfig_t
*configDefault
, int layout
, int item
)
2398 // "<layout> <item> <col> <row> <visible>"
2399 const char *format
= "osd_layout %d %d %d %d %c";
2400 for (int ii
= 0; ii
< OSD_LAYOUT_COUNT
; ii
++) {
2401 if (layout
>= 0 && layout
!= ii
) {
2404 const uint16_t *layoutItems
= config
->item_pos
[ii
];
2405 const uint16_t *defaultLayoutItems
= configDefault
->item_pos
[ii
];
2406 for (int jj
= 0; jj
< OSD_ITEM_COUNT
; jj
++) {
2407 if (item
>= 0 && item
!= jj
) {
2410 bool equalsDefault
= layoutItems
[jj
] == defaultLayoutItems
[jj
];
2411 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2413 OSD_X(defaultLayoutItems
[jj
]),
2414 OSD_Y(defaultLayoutItems
[jj
]),
2415 OSD_VISIBLE(defaultLayoutItems
[jj
]) ? 'V' : 'H');
2417 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2419 OSD_X(layoutItems
[jj
]),
2420 OSD_Y(layoutItems
[jj
]),
2421 OSD_VISIBLE(layoutItems
[jj
]) ? 'V' : 'H');
2426 static void cliOsdLayout(char *cmdline
)
2434 bool visible
= false;
2435 char *tok
= strtok_r(cmdline
, " ", &saveptr
);
2439 for (ii
= 0; tok
!= NULL
; ii
++, tok
= strtok_r(NULL
, " ", &saveptr
)) {
2442 layout
= fastA2I(tok
);
2443 if (layout
< 0 || layout
>= OSD_LAYOUT_COUNT
) {
2444 cliShowParseError();
2449 item
= fastA2I(tok
);
2450 if (item
< 0 || item
>= OSD_ITEM_COUNT
) {
2451 cliShowParseError();
2457 if (col
< 0 || col
> OSD_X(OSD_POS_MAX
)) {
2458 cliShowParseError();
2464 if (row
< 0 || row
> OSD_Y(OSD_POS_MAX
)) {
2465 cliShowParseError();
2478 cliShowParseError();
2483 cliShowParseError();
2494 // No args, or just layout or layout and item. If any of them not provided,
2495 // it will be the -1 that we used during initialization, so printOsdLayout()
2496 // won't use them for filtering.
2497 printOsdLayout(DUMP_MASTER
, osdLayoutsConfig(), osdLayoutsConfig(), layout
, item
);
2500 // No visibility provided. Keep the previous one.
2501 visible
= OSD_VISIBLE(osdLayoutsConfig()->item_pos
[layout
][item
]);
2504 // Layout, item, pos and visibility. Set the item.
2505 osdLayoutsConfigMutable()->item_pos
[layout
][item
] = OSD_POS(col
, row
) | (visible
? OSD_VISIBLE_FLAG
: 0);
2509 cliShowParseError();
2516 static void printFeature(uint8_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
)
2518 uint32_t mask
= featureConfig
->enabledFeatures
;
2519 uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
2520 for (uint32_t i
= 0; ; i
++) { // disable all feature first
2521 if (featureNames
[i
] == NULL
)
2523 if (featureNames
[i
][0] == '\0')
2525 const char *format
= "feature -%s";
2526 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2527 cliDumpPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2529 for (uint32_t i
= 0; ; i
++) { // reenable what we want.
2530 if (featureNames
[i
] == NULL
)
2532 if (featureNames
[i
][0] == '\0')
2534 const char *format
= "feature %s";
2535 if (defaultMask
& (1 << i
)) {
2536 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2538 if (mask
& (1 << i
)) {
2539 cliDumpPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2544 static void cliFeature(char *cmdline
)
2546 uint32_t len
= strlen(cmdline
);
2547 uint32_t mask
= featureMask();
2550 cliPrint("Enabled: ");
2551 for (uint32_t i
= 0; ; i
++) {
2552 if (featureNames
[i
] == NULL
)
2554 if (featureNames
[i
][0] == '\0')
2556 if (mask
& (1 << i
))
2557 cliPrintf("%s ", featureNames
[i
]);
2560 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
2561 cliPrint("Available: ");
2562 for (uint32_t i
= 0; ; i
++) {
2563 if (featureNames
[i
] == NULL
)
2565 if (featureNames
[i
][0] == '\0')
2567 cliPrintf("%s ", featureNames
[i
]);
2572 bool remove
= false;
2573 if (cmdline
[0] == '-') {
2576 cmdline
++; // skip over -
2580 for (uint32_t i
= 0; ; i
++) {
2581 if (featureNames
[i
] == NULL
) {
2582 cliPrintErrorLine("Invalid name");
2586 if (sl_strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
2590 if (mask
& FEATURE_GPS
) {
2591 cliPrintErrorLine("unavailable");
2597 cliPrint("Disabled");
2600 cliPrint("Enabled");
2602 cliPrintLinef(" %s", featureNames
[i
]);
2610 static void printBlackbox(uint8_t dumpMask
, const blackboxConfig_t
*config
, const blackboxConfig_t
*configDefault
)
2613 UNUSED(configDefault
);
2615 uint32_t mask
= config
->includeFlags
;
2617 for (uint8_t i
= 0; ; i
++) { // reenable what we want.
2618 if (blackboxIncludeFlagNames
[i
] == NULL
) {
2622 const char *formatOn
= "blackbox %s";
2623 const char *formatOff
= "blackbox -%s";
2625 if (mask
& (1 << i
)) {
2626 cliDumpPrintLinef(dumpMask
, false, formatOn
, blackboxIncludeFlagNames
[i
]);
2627 cliDefaultPrintLinef(dumpMask
, false, formatOn
, blackboxIncludeFlagNames
[i
]);
2629 cliDumpPrintLinef(dumpMask
, false, formatOff
, blackboxIncludeFlagNames
[i
]);
2630 cliDefaultPrintLinef(dumpMask
, false, formatOff
, blackboxIncludeFlagNames
[i
]);
2636 static void cliBlackbox(char *cmdline
)
2638 uint32_t len
= strlen(cmdline
);
2639 uint32_t mask
= blackboxConfig()->includeFlags
;
2642 cliPrint("Enabled: ");
2643 for (uint8_t i
= 0; ; i
++) {
2644 if (blackboxIncludeFlagNames
[i
] == NULL
) {
2648 if (mask
& (1 << i
))
2649 cliPrintf("%s ", blackboxIncludeFlagNames
[i
]);
2652 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
2653 cliPrint("Available: ");
2654 for (uint32_t i
= 0; ; i
++) {
2655 if (blackboxIncludeFlagNames
[i
] == NULL
) {
2659 cliPrintf("%s ", blackboxIncludeFlagNames
[i
]);
2664 bool remove
= false;
2665 if (cmdline
[0] == '-') {
2668 cmdline
++; // skip over -
2672 for (uint32_t i
= 0; ; i
++) {
2673 if (blackboxIncludeFlagNames
[i
] == NULL
) {
2674 cliPrintErrorLine("Invalid name");
2678 if (sl_strncasecmp(cmdline
, blackboxIncludeFlagNames
[i
], len
) == 0) {
2683 blackboxIncludeFlagClear(mask
);
2684 cliPrint("Disabled");
2686 blackboxIncludeFlagSet(mask
);
2687 cliPrint("Enabled");
2689 cliPrintLinef(" %s", blackboxIncludeFlagNames
[i
]);
2697 #if defined(BEEPER) || defined(USE_DSHOT)
2698 static void printBeeper(uint8_t dumpMask
, const beeperConfig_t
*beeperConfig
, const beeperConfig_t
*beeperConfigDefault
)
2700 const uint8_t beeperCount
= beeperTableEntryCount();
2701 const uint32_t mask
= beeperConfig
->beeper_off_flags
;
2702 const uint32_t defaultMask
= beeperConfigDefault
->beeper_off_flags
;
2703 for (int i
= 0; i
< beeperCount
- 2; i
++) {
2704 const char *formatOff
= "beeper -%s";
2705 const char *formatOn
= "beeper %s";
2706 cliDefaultPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOn
: formatOff
, beeperNameForTableIndex(i
));
2707 cliDumpPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOff
: formatOn
, beeperNameForTableIndex(i
));
2711 static void cliBeeper(char *cmdline
)
2713 uint32_t len
= strlen(cmdline
);
2714 uint8_t beeperCount
= beeperTableEntryCount();
2715 uint32_t mask
= getBeeperOffMask();
2718 cliPrintf("Disabled:");
2719 for (int32_t i
= 0; ; i
++) {
2720 if (i
== beeperCount
- 2){
2725 if (mask
& (1 << (beeperModeForTableIndex(i
) - 1)))
2726 cliPrintf(" %s", beeperNameForTableIndex(i
));
2729 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
2730 cliPrint("Available:");
2731 for (uint32_t i
= 0; i
< beeperCount
; i
++)
2732 cliPrintf(" %s", beeperNameForTableIndex(i
));
2736 bool remove
= false;
2737 if (cmdline
[0] == '-') {
2738 remove
= true; // this is for beeper OFF condition
2743 for (uint32_t i
= 0; ; i
++) {
2744 if (i
== beeperCount
) {
2745 cliPrintErrorLine("Invalid name");
2748 if (sl_strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0) {
2749 if (remove
) { // beeper off
2750 if (i
== BEEPER_ALL
-1)
2751 beeperOffSetAll(beeperCount
-2);
2753 if (i
== BEEPER_PREFERENCE
-1)
2754 setBeeperOffMask(getPreferredBeeperOffMask());
2756 mask
= 1 << (beeperModeForTableIndex(i
) - 1);
2759 cliPrint("Disabled");
2762 if (i
== BEEPER_ALL
-1)
2763 beeperOffClearAll();
2765 if (i
== BEEPER_PREFERENCE
-1)
2766 setPreferredBeeperOffMask(getBeeperOffMask());
2768 mask
= 1 << (beeperModeForTableIndex(i
) - 1);
2769 beeperOffClear(mask
);
2771 cliPrint("Enabled");
2773 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
2781 static void printMap(uint8_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
)
2783 bool equalsDefault
= true;
2785 char bufDefault
[16];
2788 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
2789 buf
[i
] = bufDefault
[i
] = 0;
2792 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
2793 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2794 if (defaultRxConfig
) {
2795 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2796 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
2801 const char *formatMap
= "map %s";
2802 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
2803 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
2806 static void cliMap(char *cmdline
)
2809 char out
[MAX_MAPPABLE_RX_INPUTS
+ 1];
2811 len
= strlen(cmdline
);
2813 if (len
== MAX_MAPPABLE_RX_INPUTS
) {
2815 for (uint32_t i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
2816 cmdline
[i
] = sl_toupper((unsigned char)cmdline
[i
]);
2818 for (uint32_t i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
2819 if (strchr(rcChannelLetters
, cmdline
[i
]) && !strchr(cmdline
+ i
+ 1, cmdline
[i
])) {
2822 cliShowParseError();
2825 parseRcChannels(cmdline
);
2826 } else if (len
!= 0) {
2827 cliShowParseError();
2831 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++){
2832 out
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
2835 cliPrintLinef("%s", out
);
2838 static const char *checkCommand(const char *cmdLine
, const char *command
)
2840 if (!sl_strncasecmp(cmdLine
, command
, strlen(command
)) // command names match
2841 && !sl_isalnum((unsigned)cmdLine
[strlen(command
)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
2842 return cmdLine
+ strlen(command
) + 1;
2848 static void cliRebootEx(bool bootLoader
)
2850 cliPrint("\r\nRebooting");
2851 bufWriterFlush(cliWriter
);
2852 waitForSerialPortToFinishTransmitting(cliPort
);
2854 fcReboot(bootLoader
);
2857 static void cliReboot(void)
2862 static void cliDfu(char *cmdline
)
2865 #ifndef CLI_MINIMAL_VERBOSITY
2866 cliPrint("\r\nRestarting in DFU mode");
2871 #if defined (USE_SERIALRX_SRXL2)
2872 void cliRxBind(char *cmdline
){
2874 if (rxConfig()->receiverType
== RX_TYPE_SERIAL
) {
2875 switch (rxConfig()->serialrx_provider
) {
2877 cliPrint("Not supported.");
2879 #if defined(USE_SERIALRX_SRXL2)
2880 case SERIALRX_SRXL2
:
2882 cliPrint("Binding SRXL2 receiver...");
2890 static void cliExit(char *cmdline
)
2894 #ifndef CLI_MINIMAL_VERBOSITY
2895 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
2897 bufWriterFlush(cliWriter
);
2902 // incase a motor was left running during motortest, clear it here
2903 mixerResetDisarmedMotors();
2910 static void cliGpsPassthrough(char *cmdline
)
2914 gpsEnablePassthrough(cliPort
);
2918 static void cliMotor(char *cmdline
)
2920 int motor_index
= 0;
2921 int motor_value
= 0;
2926 if (isEmpty(cmdline
)) {
2927 cliShowParseError();
2932 pch
= strtok_r(cmdline
, " ", &saveptr
);
2933 while (pch
!= NULL
) {
2936 motor_index
= fastA2I(pch
);
2939 motor_value
= fastA2I(pch
);
2943 pch
= strtok_r(NULL
, " ", &saveptr
);
2946 if (motor_index
< 0 || motor_index
>= MAX_SUPPORTED_MOTORS
) {
2947 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
2952 if (motor_value
< PWM_RANGE_MIN
|| motor_value
> PWM_RANGE_MAX
) {
2953 cliShowArgumentRangeError("value", 1000, 2000);
2956 motor_disarmed
[motor_index
] = motor_value
;
2960 cliPrintLinef("motor %d: %d", motor_index
, motor_disarmed
[motor_index
]);
2963 static void cliPlaySound(char *cmdline
)
2967 static int lastSoundIdx
= -1;
2969 if (isEmpty(cmdline
)) {
2970 i
= lastSoundIdx
+ 1; //next sound index
2971 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
2972 while (true) { //no name for index; try next one
2973 if (++i
>= beeperTableEntryCount())
2974 i
= 0; //if end then wrap around to first entry
2975 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
2976 break; //if name OK then play sound below
2977 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
2978 cliPrintLine("Error playing sound");
2983 } else { //index value was given
2984 i
= fastA2I(cmdline
);
2985 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
2986 cliPrintLinef("No sound for index %d", i
);
2992 cliPrintLinef("Playing sound %d: %s", i
, name
);
2993 beeper(beeperModeForTableIndex(i
));
2996 static void cliProfile(char *cmdline
)
2998 // CLI profile index is 1-based
2999 if (isEmpty(cmdline
)) {
3000 cliPrintLinef("profile %d", getConfigProfile() + 1);
3003 const int i
= fastA2I(cmdline
) - 1;
3004 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
3005 setConfigProfileAndWriteEEPROM(i
);
3011 static void cliDumpProfile(uint8_t profileIndex
, uint8_t dumpMask
)
3013 if (profileIndex
>= MAX_PROFILE_COUNT
) {
3017 setConfigProfile(profileIndex
);
3018 cliPrintHashLine("profile");
3019 cliPrintLinef("profile %d\r\n", getConfigProfile() + 1);
3020 dumpAllValues(PROFILE_VALUE
, dumpMask
);
3021 dumpAllValues(CONTROL_RATE_VALUE
, dumpMask
);
3024 static void cliBatteryProfile(char *cmdline
)
3026 // CLI profile index is 1-based
3027 if (isEmpty(cmdline
)) {
3028 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
3031 const int i
= fastA2I(cmdline
) - 1;
3032 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
3033 setConfigBatteryProfileAndWriteEEPROM(i
);
3034 cliBatteryProfile("");
3039 static void cliDumpBatteryProfile(uint8_t profileIndex
, uint8_t dumpMask
)
3041 if (profileIndex
>= MAX_BATTERY_PROFILE_COUNT
) {
3045 setConfigBatteryProfile(profileIndex
);
3046 cliPrintHashLine("battery_profile");
3047 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
3048 dumpAllValues(BATTERY_CONFIG_VALUE
, dumpMask
);
3051 #ifdef USE_CLI_BATCH
3052 static void cliPrintCommandBatchWarning(const char *warning
)
3054 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
3056 cliPrintErrorLinef(warning
);
3060 static void resetCommandBatch(void)
3062 commandBatchActive
= false;
3063 commandBatchError
= false;
3066 static void cliBatch(char *cmdline
)
3068 if (strncasecmp(cmdline
, "start", 5) == 0) {
3069 if (!commandBatchActive
) {
3070 commandBatchActive
= true;
3071 commandBatchError
= false;
3073 cliPrintLine("Command batch started");
3074 } else if (strncasecmp(cmdline
, "end", 3) == 0) {
3075 if (commandBatchActive
&& commandBatchError
) {
3076 cliPrintCommandBatchWarning(NULL
);
3078 cliPrintLine("Command batch ended");
3080 resetCommandBatch();
3082 cliPrintErrorLinef("Invalid option");
3087 static void cliSave(char *cmdline
)
3091 #ifdef USE_CLI_BATCH
3092 if (commandBatchActive
&& commandBatchError
) {
3093 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3094 resetCommandBatch();
3100 //copyCurrentProfileToProfileSlot(getConfigProfile();
3105 static void cliDefaults(char *cmdline
)
3109 cliPrint("Resetting to defaults");
3112 #ifdef USE_CLI_BATCH
3113 commandBatchError
= false;
3116 if (!checkCommand(cmdline
, "noreboot"))
3120 static void cliGet(char *cmdline
)
3122 const setting_t
*val
;
3123 int matchedCommands
= 0;
3124 char name
[SETTING_MAX_NAME_LENGTH
];
3126 while(*cmdline
== ' ') ++cmdline
; // ignore spaces
3128 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3129 val
= settingGet(i
);
3130 if (settingNameContains(val
, name
, cmdline
)) {
3131 cliPrintf("%s = ", name
);
3132 if (strcmp(name
, "name") == 0) {
3133 // if the craftname has a leading space, then enclose the name in quotes
3134 const char * v
= (const char *)settingGetValuePointer(val
);
3135 cliPrintf(v
[0] == ' ' ? "\"%s\"" : "%s", v
);
3137 cliPrintVar(val
, 0);
3140 cliPrintVarRange(val
);
3148 if (matchedCommands
) {
3152 cliPrintErrorLine("Invalid name");
3155 static void cliSet(char *cmdline
)
3158 const setting_t
*val
;
3160 char name
[SETTING_MAX_NAME_LENGTH
];
3162 while(*cmdline
== ' ') ++cmdline
; // ignore spaces
3164 len
= strlen(cmdline
);
3166 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
3167 cliPrintLine("Current settings:");
3168 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3169 val
= settingGet(i
);
3170 settingGetName(val
, name
);
3171 cliPrintf("%s = ", name
);
3172 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3175 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
3178 char *lastNonSpaceCharacter
= eqptr
;
3179 while (*(lastNonSpaceCharacter
- 1) == ' ') {
3180 lastNonSpaceCharacter
--;
3182 uint8_t variableNameLength
= lastNonSpaceCharacter
- cmdline
;
3184 // skip the '=' and any ' ' characters
3186 while (*(eqptr
) == ' ') {
3190 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3191 val
= settingGet(i
);
3192 // ensure exact match when setting to prevent setting variables with shorter names
3193 if (settingNameIsExactMatch(val
, name
, cmdline
, variableNameLength
)) {
3194 const setting_type_e type
= SETTING_TYPE(val
);
3195 if (type
== VAR_STRING
) {
3196 // Convert strings to uppercase. Lower case is not supported by the OSD.
3197 sl_toupperptr(eqptr
);
3198 // if setting the craftname, remove any quotes around the name. This allows leading spaces in the name
3199 if ((strcmp(name
, "name") == 0 || strcmp(name
, "pilot_name") == 0) && (eqptr
[0] == '"' && eqptr
[strlen(eqptr
)-1] == '"')) {
3200 settingSetString(val
, eqptr
+ 1, strlen(eqptr
)-2);
3202 settingSetString(val
, eqptr
, strlen(eqptr
));
3206 const setting_mode_e mode
= SETTING_MODE(val
);
3207 bool changeValue
= false;
3208 int_float_value_t tmp
= {0};
3211 if (*eqptr
!= 0 && strspn(eqptr
, "0123456789.+-") == strlen(eqptr
)) {
3212 float valuef
= fastA2F(eqptr
);
3213 // note: compare float values
3214 if (valuef
>= (float)settingGetMin(val
) && valuef
<= (float)settingGetMax(val
)) {
3216 if (type
== VAR_FLOAT
)
3217 tmp
.float_value
= valuef
;
3218 else if (type
== VAR_UINT32
)
3219 tmp
.uint_value
= fastA2UL(eqptr
);
3221 tmp
.int_value
= fastA2I(eqptr
);
3229 const lookupTableEntry_t
*tableEntry
= settingLookupTable(val
);
3230 bool matched
= false;
3231 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
3232 matched
= sl_strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
3235 tmp
.int_value
= tableValueIndex
;
3244 cliSetIntFloatVar(val
, tmp
);
3246 cliPrintf("%s set to ", name
);
3247 cliPrintVar(val
, 0);
3249 cliPrintError("Invalid value. ");
3250 cliPrintVarRange(val
);
3257 cliPrintErrorLine("Invalid name");
3259 // no equals, check for matching variables.
3264 static const char * getBatteryStateString(void)
3266 static const char * const batteryStateStrings
[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
3268 return batteryStateStrings
[getBatteryState()];
3271 static void cliStatus(char *cmdline
)
3275 char buf
[MAX(FORMATTED_DATE_TIME_BUFSIZE
, SETTING_MAX_NAME_LENGTH
)];
3278 cliPrintLinef("%s/%s %s %s / %s (%s)",
3286 cliPrintLinef("GCC-%s",
3289 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3290 rtcGetDateTime(&dt
);
3291 dateTimeFormatLocal(buf
, &dt
);
3292 cliPrintLinef("Current Time: %s", buf
);
3293 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
3294 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock
/ 1000000));
3296 const uint32_t detectedSensorsMask
= sensorsMask();
3298 for (int i
= 0; i
< SENSOR_INDEX_COUNT
; i
++) {
3300 const uint32_t mask
= (1 << i
);
3301 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
3302 const int sensorHardwareIndex
= detectedSensors
[i
];
3303 if (sensorHardwareNames
[i
]) {
3304 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
3305 cliPrintf(", %s=%s", sensorTypeNames
[i
], sensorHardware
);
3310 #if !defined(SITL_BUILD)
3311 #if defined(AT32F43x)
3312 cliPrintLine("AT32 system clocks:");
3313 crm_clocks_freq_type clocks
;
3314 crm_clocks_freq_get(&clocks
);
3316 cliPrintLinef(" SYSCLK = %d MHz", clocks
.sclk_freq
/ 1000000);
3317 cliPrintLinef(" ABH = %d MHz", clocks
.ahb_freq
/ 1000000);
3318 cliPrintLinef(" ABP1 = %d MHz", clocks
.apb1_freq
/ 1000000);
3319 cliPrintLinef(" ABP2 = %d MHz", clocks
.apb2_freq
/ 1000000);
3321 cliPrintLine("STM32 system clocks:");
3322 #if defined(USE_HAL_DRIVER)
3323 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
3324 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
3325 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
3326 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
3328 RCC_ClocksTypeDef clocks
;
3329 RCC_GetClocksFreq(&clocks
);
3330 cliPrintLinef(" SYSCLK = %d MHz", clocks
.SYSCLK_Frequency
/ 1000000);
3331 cliPrintLinef(" HCLK = %d MHz", clocks
.HCLK_Frequency
/ 1000000);
3332 cliPrintLinef(" PCLK1 = %d MHz", clocks
.PCLK1_Frequency
/ 1000000);
3333 cliPrintLinef(" PCLK2 = %d MHz", clocks
.PCLK2_Frequency
/ 1000000);
3335 #endif // for if at32
3338 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
3339 hardwareSensorStatusNames
[getHwGyroStatus()],
3340 hardwareSensorStatusNames
[getHwAccelerometerStatus()],
3341 hardwareSensorStatusNames
[getHwCompassStatus()],
3342 hardwareSensorStatusNames
[getHwBarometerStatus()],
3343 hardwareSensorStatusNames
[getHwRangefinderStatus()],
3344 hardwareSensorStatusNames
[getHwOpticalFlowStatus()],
3345 hardwareSensorStatusNames
[getHwGPSStatus()]
3348 #ifdef USE_ESC_SENSOR
3349 uint8_t motorCount
= getMotorCount();
3350 if (STATE(ESC_SENSOR_ENABLED
) && motorCount
> 0) {
3351 cliPrintLinef("ESC Temperature(s): Motor Count = %d", motorCount
);
3352 for (uint8_t i
= 0; i
< motorCount
; i
++) {
3353 const escSensorData_t
*escState
= getEscTelemetry(i
); //Get ESC telemetry
3354 cliPrintf("ESC %d: %d\260C, ", i
, escState
->temperature
);
3364 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
3365 #elif !defined(SITL_BUILD)
3366 const uint16_t i2cErrorCounter
= 0;
3370 cliPrintf("Stack used: %d, ", stackUsedSize());
3372 #if !defined(SITL_BUILD)
3373 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
3375 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter
, getEEPROMConfigSize(), &__config_end
- &__config_start
);
3377 #if defined(USE_ADC) && !defined(SITL_BUILD)
3378 static char * adcFunctions
[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
3379 cliPrintLine("ADC channel usage:");
3380 for (int i
= 0; i
< ADC_FUNCTION_COUNT
; i
++) {
3381 cliPrintf(" %8s :", adcFunctions
[i
]);
3383 cliPrint(" configured = ");
3384 if (adcChannelConfig()->adcFunctionChannel
[i
] == ADC_CHN_NONE
) {
3388 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel
[i
]);
3391 cliPrint(", used = ");
3392 if (adcGetFunctionChannelAllocation(i
) == ADC_CHN_NONE
) {
3393 cliPrintLine("none");
3396 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i
));
3401 cliPrintf("System load: %d", averageSystemLoadPercent
);
3402 const timeDelta_t pidTaskDeltaTime
= getTaskDeltaTime(TASK_PID
);
3403 const int pidRate
= pidTaskDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)pidTaskDeltaTime
));
3404 const int rxRate
= getTaskDeltaTime(TASK_RX
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_RX
)));
3405 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
3406 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime
, pidRate
, rxRate
, systemRate
);
3407 #if !defined(CLI_MINIMAL_VERBOSITY)
3408 cliPrint("Arming disabled flags:");
3409 uint32_t flags
= armingFlags
& ARMING_DISABLED_ALL_FLAGS
;
3411 int bitpos
= ffs(flags
) - 1;
3412 flags
&= ~(1 << bitpos
);
3413 if (bitpos
> 6) cliPrintf(" %s", armingDisableFlagNames
[bitpos
- 7]);
3416 if (armingFlags
& ARMING_DISABLED_INVALID_SETTING
) {
3417 unsigned invalidIndex
;
3418 if (!settingsValidate(&invalidIndex
)) {
3419 settingGetName(settingGet(invalidIndex
), buf
);
3420 cliPrintErrorLinef("Invalid setting: %s", buf
);
3424 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags
& ARMING_DISABLED_ALL_FLAGS
);
3427 #if !defined(CLI_MINIMAL_VERBOSITY)
3429 #if defined(USE_OSD)
3430 displayPort_t
*osdDisplayPort
= osdGetDisplayPort();
3431 if (osdDisplayPort
!= NULL
) {
3432 cliPrintf("%s [%u x %u]", osdDisplayPort
->displayPortType
, osdDisplayPort
->cols
, osdDisplayPort
->rows
);
3434 cliPrint("not enabled");
3437 cliPrint("not used");
3442 #if defined(USE_VTX_CONTROL)
3443 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
3444 vtxDeviceOsdInfo_t osdInfo
;
3445 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo
);
3446 cliPrintf("band: %c, chan: %s, power: %c", osdInfo
.bandLetter
, osdInfo
.channelName
, osdInfo
.powerIndexLetter
);
3448 if (osdInfo
.powerMilliwatt
) {
3449 cliPrintf(" (%d mW)", osdInfo
.powerMilliwatt
);
3452 if (osdInfo
.frequency
) {
3453 cliPrintf(", freq: %d MHz", osdInfo
.frequency
);
3457 cliPrint("not detected");
3460 cliPrint("no VTX control");
3466 // If we are blocked by PWM init - provide more information
3467 if (getPwmInitError() != PWM_INIT_ERROR_NONE
) {
3468 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3472 static void cliTasks(char *cmdline
)
3476 int averageLoadSum
= 0;
3477 cfCheckFuncInfo_t checkFuncInfo
;
3479 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3480 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
3481 cfTaskInfo_t taskInfo
;
3482 getTaskInfo(taskId
, &taskInfo
);
3483 if (taskInfo
.isEnabled
) {
3484 const int taskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3485 const int maxLoad
= (taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
3486 const int averageLoad
= (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
3487 if (taskId
!= TASK_SERIAL
) {
3488 maxLoadSum
+= maxLoad
;
3489 averageLoadSum
+= averageLoad
;
3491 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
3492 taskId
, taskInfo
.taskName
, taskFrequency
, (uint32_t)taskInfo
.maxExecutionTime
, (uint32_t)taskInfo
.averageExecutionTime
,
3493 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, (uint32_t)taskInfo
.totalExecutionTime
/ 1000);
3496 getCheckFuncInfo(&checkFuncInfo
);
3497 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo
.maxExecutionTime
, (uint32_t)checkFuncInfo
.averageExecutionTime
, (uint32_t)checkFuncInfo
.totalExecutionTime
/ 1000);
3498 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
3501 static void cliVersion(char *cmdline
)
3505 cliPrintLinef("# %s/%s %s %s / %s (%s)",
3513 cliPrintLinef("# GCC-%s",
3518 static void cliMemory(char *cmdline
)
3521 cliPrintLinef("Dynamic memory usage:");
3522 for (unsigned i
= 0; i
< OWNER_TOTAL_COUNT
; i
++) {
3523 const char * owner
= ownerNames
[i
];
3524 const uint32_t memUsed
= memGetUsedBytesByOwner(i
);
3527 cliPrintLinef("%s : %d bytes", owner
, memUsed
);
3532 static void cliResource(char *cmdline
)
3535 cliPrintLinef("IO:\r\n----------------------");
3536 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
3538 owner
= ownerNames
[ioRecs
[i
].owner
];
3540 const char* resource
;
3541 resource
= resourceNames
[ioRecs
[i
].resource
];
3543 if (ioRecs
[i
].index
> 0) {
3544 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
, ioRecs
[i
].index
, resource
);
3546 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
, resource
);
3551 static void backupConfigs(void)
3553 // make copies of configs to do differencing
3555 if (pgIsProfile(pg
)) {
3556 memcpy(pg
->copy
, pg
->address
, pgSize(pg
) * MAX_PROFILE_COUNT
);
3558 memcpy(pg
->copy
, pg
->address
, pgSize(pg
));
3563 static void restoreConfigs(void)
3566 if (pgIsProfile(pg
)) {
3567 memcpy(pg
->address
, pg
->copy
, pgSize(pg
) * MAX_PROFILE_COUNT
);
3569 memcpy(pg
->address
, pg
->copy
, pgSize(pg
));
3574 static void printConfig(const char *cmdline
, bool doDiff
)
3576 uint8_t dumpMask
= DUMP_MASTER
;
3577 const char *options
;
3578 if ((options
= checkCommand(cmdline
, "master"))) {
3579 dumpMask
= DUMP_MASTER
; // only
3580 } else if ((options
= checkCommand(cmdline
, "profile"))) {
3581 dumpMask
= DUMP_PROFILE
; // only
3582 } else if ((options
= checkCommand(cmdline
, "battery_profile"))) {
3583 dumpMask
= DUMP_BATTERY_PROFILE
; // only
3584 } else if ((options
= checkCommand(cmdline
, "all"))) {
3585 dumpMask
= DUMP_ALL
; // all profiles and rates
3591 dumpMask
= dumpMask
| DO_DIFF
;
3594 const int currentProfileIndexSave
= getConfigProfile();
3595 const int currentBatteryProfileIndexSave
= getConfigBatteryProfile();
3597 // reset all configs to defaults to do differencing
3599 // restore the profile indices, since they should not be reset for proper comparison
3600 setConfigProfile(currentProfileIndexSave
);
3601 setConfigBatteryProfile(currentBatteryProfileIndexSave
);
3603 if (checkCommand(options
, "showdefaults")) {
3604 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
3607 #ifdef USE_CLI_BATCH
3608 bool batchModeEnabled
= false;
3611 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
3612 cliPrintHashLine("version");
3615 #ifdef USE_CLI_BATCH
3616 cliPrintHashLine("start the command batch");
3617 cliPrintLine("batch start");
3618 batchModeEnabled
= true;
3621 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
3622 #ifndef CLI_MINIMAL_VERBOSITY
3623 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
3625 cliPrintLinef("defaults noreboot");
3629 cliPrintHashLine("resources");
3630 //printResource(dumpMask, &defaultConfig);
3632 cliPrintHashLine("Mixer: motor mixer");
3633 cliDumpPrintLinef(dumpMask
, primaryMotorMixer_CopyArray
[0].throttle
== 0.0f
, "\r\nmmix reset\r\n");
3635 printMotorMix(dumpMask
, primaryMotorMixer_CopyArray
, primaryMotorMixer(0));
3637 // print custom servo mixer if exists
3638 cliPrintHashLine("Mixer: servo mixer");
3639 cliDumpPrintLinef(dumpMask
, customServoMixers_CopyArray
[0].rate
== 0, "smix reset\r\n");
3640 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0));
3642 // print servo parameters
3643 cliPrintHashLine("Outputs [servo]");
3644 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0));
3646 #if defined(USE_SAFE_HOME)
3647 cliPrintHashLine("safehome");
3648 printSafeHomes(dumpMask
, safeHomeConfig_CopyArray
, safeHomeConfig(0));
3651 cliPrintHashLine("features");
3652 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig());
3654 #if defined(BEEPER) || defined(USE_DSHOT)
3655 cliPrintHashLine("beeper");
3656 printBeeper(dumpMask
, &beeperConfig_Copy
, beeperConfig());
3660 cliPrintHashLine("blackbox");
3661 printBlackbox(dumpMask
, &blackboxConfig_Copy
, blackboxConfig());
3664 cliPrintHashLine("Receiver: Channel map");
3665 printMap(dumpMask
, &rxConfig_Copy
, rxConfig());
3667 cliPrintHashLine("Ports");
3668 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig());
3670 #ifdef USE_LED_STRIP
3671 cliPrintHashLine("LEDs");
3672 printLed(dumpMask
, ledStripConfig_Copy
.ledConfigs
, ledStripConfig()->ledConfigs
);
3674 cliPrintHashLine("LED color");
3675 printColor(dumpMask
, ledStripConfig_Copy
.colors
, ledStripConfig()->colors
);
3677 cliPrintHashLine("LED mode_color");
3678 printModeColor(dumpMask
, &ledStripConfig_Copy
, ledStripConfig());
3681 cliPrintHashLine("Modes [aux]");
3682 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0));
3684 cliPrintHashLine("Adjustments [adjrange]");
3685 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0));
3687 cliPrintHashLine("Receiver rxrange");
3688 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0));
3690 #ifdef USE_TEMPERATURE_SENSOR
3691 cliPrintHashLine("temp_sensor");
3692 printTempSensor(dumpMask
, tempSensorConfig_CopyArray
, tempSensorConfig(0));
3695 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
3696 cliPrintHashLine("Mission Control Waypoints [wp]");
3697 printWaypoints(dumpMask
, posControl
.waypointList
, nonVolatileWaypointList(0));
3701 cliPrintHashLine("OSD [osd_layout]");
3702 printOsdLayout(dumpMask
, &osdLayoutsConfig_Copy
, osdLayoutsConfig(), -1, -1);
3705 #ifdef USE_PROGRAMMING_FRAMEWORK
3706 cliPrintHashLine("Programming: logic");
3707 printLogic(dumpMask
, logicConditions_CopyArray
, logicConditions(0), -1);
3709 cliPrintHashLine("Programming: global variables");
3710 printGvar(dumpMask
, globalVariableConfigs_CopyArray
, globalVariableConfigs(0));
3712 cliPrintHashLine("Programming: PID controllers");
3713 printPid(dumpMask
, programmingPids_CopyArray
, programmingPids(0));
3716 cliPrintHashLine("master");
3717 dumpAllValues(MASTER_VALUE
, dumpMask
);
3719 if (dumpMask
& DUMP_ALL
) {
3720 // dump all profiles
3721 const int currentProfileIndexSave
= getConfigProfile();
3722 const int currentBatteryProfileIndexSave
= getConfigBatteryProfile();
3723 for (int ii
= 0; ii
< MAX_PROFILE_COUNT
; ++ii
) {
3724 cliDumpProfile(ii
, dumpMask
);
3726 for (int ii
= 0; ii
< MAX_BATTERY_PROFILE_COUNT
; ++ii
) {
3727 cliDumpBatteryProfile(ii
, dumpMask
);
3729 setConfigProfile(currentProfileIndexSave
);
3730 setConfigBatteryProfile(currentBatteryProfileIndexSave
);
3732 cliPrintHashLine("restore original profile selection");
3733 cliPrintLinef("profile %d", currentProfileIndexSave
+ 1);
3734 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave
+ 1);
3736 #ifdef USE_CLI_BATCH
3737 batchModeEnabled
= false;
3740 // dump just the current profiles
3741 cliDumpProfile(getConfigProfile(), dumpMask
);
3742 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask
);
3746 if (dumpMask
& DUMP_PROFILE
) {
3747 cliDumpProfile(getConfigProfile(), dumpMask
);
3750 if (dumpMask
& DUMP_BATTERY_PROFILE
) {
3751 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask
);
3754 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
3755 cliPrintHashLine("save configuration\r\nsave");
3758 #ifdef USE_CLI_BATCH
3759 if (batchModeEnabled
) {
3760 cliPrintHashLine("end the command batch");
3761 cliPrintLine("batch end");
3765 // restore configs from copies
3769 static void cliDump(char *cmdline
)
3771 printConfig(cmdline
, false);
3774 static void cliDiff(char *cmdline
)
3776 printConfig(cmdline
, true);
3780 static void cliMsc(char *cmdline
)
3786 || sdcard_isFunctional()
3789 || flashfsGetSize() > 0
3792 cliPrintHashLine("restarting in mass storage mode");
3793 cliPrint("\r\nRebooting");
3794 bufWriterFlush(cliWriter
);
3796 waitForSerialPortToFinishTransmitting(cliPort
);
3798 systemResetRequest(RESET_MSC_REQUEST
);
3800 cliPrint("\r\nStorage not present or failed to initialize!");
3801 bufWriterFlush(cliWriter
);
3809 #ifndef SKIP_CLI_COMMAND_HELP
3810 const char *description
;
3813 void (*func
)(char *cmdline
);
3816 #ifndef SKIP_CLI_COMMAND_HELP
3817 #define CLI_COMMAND_DEF(name, description, args, method) \
3825 #define CLI_COMMAND_DEF(name, description, args, method) \
3832 static void cliHelp(char *cmdline
);
3834 // should be sorted a..z for bsearch()
3835 const clicmd_t cmdTable
[] = {
3836 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
3837 #if defined(USE_ASSERT)
3838 CLI_COMMAND_DEF("assert", "", NULL
, cliAssert
),
3840 CLI_COMMAND_DEF("aux", "configure modes", NULL
, cliAux
),
3841 #ifdef USE_CLI_BATCH
3842 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch
),
3844 #if defined(BEEPER) || defined(USE_DSHOT)
3845 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3846 "\t<+|->[name]", cliBeeper
),
3848 #if defined (USE_SERIALRX_SRXL2)
3849 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL
, cliRxBind
),
3851 #if defined(USE_BOOTLOG)
3852 CLI_COMMAND_DEF("bootlog", "show boot events", NULL
, cliBootlog
),
3854 #ifdef USE_LED_STRIP
3855 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
3856 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
3858 CLI_COMMAND_DEF("cli_delay", "CLI Delay", "Delay in ms", cliDelay
),
3859 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL
, cliDefaults
),
3860 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL
, cliDfu
),
3861 CLI_COMMAND_DEF("diff", "list configuration changes from default",
3862 "[master|battery_profile|profile|rates|all] {showdefaults}", cliDiff
),
3863 CLI_COMMAND_DEF("dump", "dump configuration",
3864 "[master|battery_profile|profile|rates|all] {showdefaults}", cliDump
),
3865 #ifdef USE_RX_ELERES
3866 CLI_COMMAND_DEF("eleres_bind", NULL
, NULL
, cliEleresBind
),
3867 #endif // USE_RX_ELERES
3868 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
3869 CLI_COMMAND_DEF("feature", "configure features",
3871 "\t<+|->[name]", cliFeature
),
3873 CLI_COMMAND_DEF("blackbox", "configure blackbox fields",
3875 "\t<+|->[name]", cliBlackbox
),
3878 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
3879 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
3880 #ifdef USE_FLASH_TOOLS
3881 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
3882 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
3885 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
3887 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
3889 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
3890 #ifdef USE_LED_STRIP
3891 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
3893 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
3894 CLI_COMMAND_DEF("memory", "view memory usage", NULL
, cliMemory
),
3895 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
3896 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
3898 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
3900 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]\r\n", cliPlaySound
),
3901 CLI_COMMAND_DEF("profile", "change profile",
3902 "[<index>]", cliProfile
),
3903 CLI_COMMAND_DEF("battery_profile", "change battery profile",
3904 "[<index>]", cliBatteryProfile
),
3905 CLI_COMMAND_DEF("resource", "view currently used resources", NULL
, cliResource
),
3906 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
3907 #if defined(USE_SAFE_HOME)
3908 CLI_COMMAND_DEF("safehome", "safe home list", NULL
, cliSafeHomes
),
3910 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
3911 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
3912 #ifdef USE_SERIAL_PASSTHROUGH
3913 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough
),
3915 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
3916 #ifdef USE_PROGRAMMING_FRAMEWORK
3917 CLI_COMMAND_DEF("logic", "configure logic conditions",
3918 "<rule> <enabled> <activatorId> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
3919 "\treset\r\n", cliLogic
),
3921 CLI_COMMAND_DEF("gvar", "configure global variables",
3922 "<gvar> <default> <min> <max>\r\n"
3923 "\treset\r\n", cliGvar
),
3925 CLI_COMMAND_DEF("pid", "configurable PID controllers",
3926 "<#> <enabled> <setpoint type> <setpoint value> <measurement type> <measurement value> <P gain> <I gain> <D gain> <FF gain>\r\n"
3927 "\treset\r\n", cliPid
),
3929 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
3930 CLI_COMMAND_DEF("smix", "servo mixer",
3931 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
3932 "\treset\r\n", cliServoMix
),
3934 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
3936 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
3937 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
3938 #ifdef USE_TEMPERATURE_SENSOR
3939 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL
, cliTempSensor
),
3941 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
3942 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
3943 CLI_COMMAND_DEF("wp", "waypoint list", NULL
, cliWaypoints
),
3946 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout
),
3950 static void cliHelp(char *cmdline
)
3954 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
3955 cliPrint(cmdTable
[i
].name
);
3956 #ifndef SKIP_CLI_COMMAND_HELP
3957 if (cmdTable
[i
].description
) {
3958 cliPrintf(" - %s", cmdTable
[i
].description
);
3960 if (cmdTable
[i
].args
) {
3961 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
3968 void cliProcess(void)
3974 // Be a little bit tricky. Flush the last inputs buffer, if any.
3975 bufWriterFlush(cliWriter
);
3977 while (serialRxBytesWaiting(cliPort
)) {
3978 uint8_t c
= serialRead(cliPort
);
3979 if (c
== '\t' || c
== '?') {
3980 // do tab completion
3981 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
3982 uint32_t i
= bufferIndex
;
3983 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
3984 if (bufferIndex
&& (sl_strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
3990 if (pstart
) { /* Buffer matches one or more commands */
3991 for (; ; bufferIndex
++) {
3992 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
3994 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
3995 /* Unambiguous -- append a space */
3996 cliBuffer
[bufferIndex
++] = ' ';
3997 cliBuffer
[bufferIndex
] = '\0';
4000 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
4003 if (!bufferIndex
|| pstart
!= pend
) {
4004 /* Print list of ambiguous matches */
4005 cliPrint("\r\033[K");
4006 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
4007 cliPrint(cmd
->name
);
4011 i
= 0; /* Redraw prompt */
4013 for (; i
< bufferIndex
; i
++)
4014 cliWrite(cliBuffer
[i
]);
4015 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
4018 } else if (c
== 12) { // NewPage / CTRL-L
4020 cliPrint("\033[2J\033[1;1H");
4022 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
4026 // Strip comment starting with # from line
4027 char *p
= cliBuffer
;
4030 bufferIndex
= (uint32_t)(p
- cliBuffer
);
4033 // Strip trailing whitespace
4034 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
4038 // Process non-empty lines
4039 if (bufferIndex
> 0) {
4040 cliBuffer
[bufferIndex
] = 0; // null terminate
4042 const clicmd_t
*cmd
;
4043 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4044 if (!sl_strncasecmp(cliBuffer
, cmd
->name
, strlen(cmd
->name
)) // command names match
4045 && !sl_isalnum((unsigned)cliBuffer
[strlen(cmd
->name
)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
4048 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
))
4049 cmd
->func(cliBuffer
+ strlen(cmd
->name
) + 1);
4051 cliPrintError("Unknown command, try 'help'");
4055 ZERO_FARRAY(cliBuffer
);
4057 // 'exit' will reset this flag, so we don't need to print prompt again
4062 } else if (c
== 127) {
4065 cliBuffer
[--bufferIndex
] = 0;
4066 cliPrint("\010 \010");
4068 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
4069 if (!bufferIndex
&& c
== ' ')
4070 continue; // Ignore leading spaces
4071 cliBuffer
[bufferIndex
++] = c
;
4077 void cliEnter(serialPort_t
*serialPort
)
4084 cliPort
= serialPort
;
4085 setPrintfSerialPort(cliPort
);
4086 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
4088 #ifndef CLI_MINIMAL_VERBOSITY
4089 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4091 cliPrintLine("\r\nCLI");
4095 #ifdef USE_CLI_BATCH
4096 resetCommandBatch();
4099 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI
);
4102 void cliInit(const serialConfig_t
*serialConfig
)
4104 UNUSED(serialConfig
);