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"
71 #include "drivers/light_ws2811strip.h"
73 #include "fc/fc_core.h"
75 #include "fc/config.h"
76 #include "fc/controlrate_profile.h"
77 #include "fc/rc_adjustments.h"
78 #include "fc/rc_controls.h"
79 #include "fc/rc_modes.h"
80 #include "fc/runtime_config.h"
81 #include "fc/settings.h"
83 #include "flight/failsafe.h"
84 #include "flight/imu.h"
85 #include "flight/mixer_profile.h"
86 #include "flight/pid.h"
87 #include "flight/servos.h"
89 #include "io/asyncfatfs/asyncfatfs.h"
90 #include "io/beeper.h"
91 #include "io/flashfs.h"
93 #include "io/gps_ublox.h"
94 #include "io/ledstrip.h"
96 #include "io/osd/custom_elements.h"
97 #include "io/serial.h"
99 #include "fc/fc_msp_box.h"
101 #include "navigation/navigation.h"
102 #include "navigation/navigation_private.h"
105 #include "rx/spektrum.h"
106 #include "rx/srxl2.h"
109 #include "scheduler/scheduler.h"
111 #include "sensors/acceleration.h"
112 #include "sensors/barometer.h"
113 #include "sensors/battery.h"
114 #include "sensors/boardalignment.h"
115 #include "sensors/compass.h"
116 #include "sensors/diagnostics.h"
117 #include "sensors/gyro.h"
118 #include "sensors/pitotmeter.h"
119 #include "sensors/rangefinder.h"
120 #include "sensors/opflow.h"
121 #include "sensors/sensors.h"
122 #include "sensors/temperature.h"
123 #ifdef USE_ESC_SENSOR
124 #include "sensors/esc_sensor.h"
127 #include "telemetry/telemetry.h"
128 #include "build/debug.h"
130 extern timeDelta_t cycleTime
; // FIXME dependency on mw.c
131 extern uint8_t detectedSensors
[SENSOR_INDEX_COUNT
];
133 static serialPort_t
*cliPort
;
135 static bufWriter_t
*cliWriter
;
136 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + 128];
138 static char cliBuffer
[64];
139 static uint32_t bufferIndex
= 0;
140 static uint16_t cliDelayMs
= 0;
142 #if defined(USE_ASSERT)
143 static void cliAssert(char *cmdline
);
147 static bool commandBatchActive
= false;
148 static bool commandBatchError
= false;
149 static uint8_t commandBatchErrorCount
= 0;
152 // sync this with features_e
153 static const char * const featureNames
[] = {
154 "THR_VBAT_COMP", "VBAT", "TX_PROF_SEL", "BAT_PROF_AUTOSWITCH", "MOTOR_STOP",
155 "", "SOFTSERIAL", "GPS", "RPM_FILTERS",
156 "", "TELEMETRY", "CURRENT_METER", "REVERSIBLE_MOTORS", "",
157 "", "RSSI_ADC", "LED_STRIP", "DASHBOARD", "",
158 "BLACKBOX", "", "TRANSPONDER", "AIRMODE",
159 "SUPEREXPO", "VTX", "", "", "", "PWM_OUTPUT_ENABLE",
160 "OSD", "FW_LAUNCH", "FW_AUTOTRIM", NULL
163 static const char * outputModeNames
[] = {
172 static const char * const blackboxIncludeFlagNames
[] = {
191 static const char *debugModeNames
[DEBUG_COUNT
] = {
220 /* Sensor names (used in lookup tables for *_hardware settings and in status
222 // sync with gyroSensor_e
223 static const char *const gyroNames
[] = {
224 "NONE", "AUTO", "MPU6000", "MPU6500", "MPU9250", "BMI160",
225 "ICM20689", "BMI088", "ICM42605", "BMI270", "LSM6DXX", "FAKE"};
227 // sync this with sensors_e
228 static const char * const sensorTypeNames
[] = {
229 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "PITOT", "OPFLOW", "GPS", "GPS+MAG", NULL
232 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER | SENSOR_PITOT | SENSOR_OPFLOW)
234 static const char * const hardwareSensorStatusNames
[] = {
235 "NONE", "OK", "UNAVAILABLE", "FAILING"
238 static const char * const *sensorHardwareNames
[] = {
251 #ifdef USE_RANGEFINDER
252 table_rangefinder_hardware
,
257 table_pitot_hardware
,
262 table_opflow_hardware
,
268 static void cliPrint(const char *str
)
271 bufWriterAppend(cliWriter
, *str
++);
275 static void cliPrintLinefeed(void)
283 static void cliPrintLine(const char *str
)
290 static void cliPrintError(const char *str
)
292 cliPrint("### ERROR: ");
295 if (commandBatchActive
) {
296 commandBatchError
= true;
297 commandBatchErrorCount
++;
302 static void cliPrintErrorLine(const char *str
)
304 cliPrint("### ERROR: ");
307 if (commandBatchActive
) {
308 commandBatchError
= true;
309 commandBatchErrorCount
++;
314 #ifdef CLI_MINIMAL_VERBOSITY
315 #define cliPrintHashLine(str)
317 static void cliPrintHashLine(const char *str
)
324 static void cliPutp(void *p
, char ch
)
326 bufWriterAppend(p
, ch
);
330 DUMP_MASTER
= (1 << 0),
331 DUMP_CONTROL_PROFILE
= (1 << 1),
332 DUMP_BATTERY_PROFILE
= (1 << 2),
333 DUMP_MIXER_PROFILE
= (1 << 3),
336 SHOW_DEFAULTS
= (1 << 6),
337 HIDE_UNUSED
= (1 << 7)
340 static void cliPrintfva(const char *format
, va_list va
)
342 tfp_format(cliWriter
, cliPutp
, format
, va
);
343 bufWriterFlush(cliWriter
);
346 static void cliPrintLinefva(const char *format
, va_list va
)
348 tfp_format(cliWriter
, cliPutp
, format
, va
);
349 bufWriterFlush(cliWriter
);
353 static bool cliDumpPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
355 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
357 va_start(va
, format
);
358 cliPrintLinefva(format
, va
);
366 static void cliWrite(uint8_t ch
)
368 bufWriterAppend(cliWriter
, ch
);
371 static bool cliDefaultPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
373 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
377 va_start(va
, format
);
378 cliPrintLinefva(format
, va
);
386 static void cliPrintf(const char *format
, ...)
389 va_start(va
, format
);
390 cliPrintfva(format
, va
);
395 static void cliPrintLinef(const char *format
, ...)
398 va_start(va
, format
);
399 cliPrintLinefva(format
, va
);
403 static void cliPrintErrorVa(const char *format
, va_list va
)
405 cliPrint("### ERROR: ");
406 cliPrintfva(format
, va
);
410 if (commandBatchActive
) {
411 commandBatchError
= true;
412 commandBatchErrorCount
++;
417 static void cliPrintErrorLinef(const char *format
, ...)
420 va_start(va
, format
);
421 cliPrintErrorVa(format
, va
);
425 static void printValuePointer(const setting_t
*var
, const void *valuePointer
, uint32_t full
)
428 char buf
[SETTING_MAX_NAME_LENGTH
];
430 switch (SETTING_TYPE(var
)) {
432 value
= *(uint8_t *)valuePointer
;
436 value
= *(int8_t *)valuePointer
;
440 value
= *(uint16_t *)valuePointer
;
444 value
= *(int16_t *)valuePointer
;
448 value
= *(uint32_t *)valuePointer
;
452 cliPrintf("%s", ftoa(*(float *)valuePointer
, buf
));
454 if (SETTING_MODE(var
) == MODE_DIRECT
) {
455 cliPrintf(" %s", ftoa((float)settingGetMin(var
), buf
));
456 cliPrintf(" %s", ftoa((float)settingGetMax(var
), buf
));
459 return; // return from case for float only
462 cliPrintf("%s", (const char *)valuePointer
);
466 switch (SETTING_MODE(var
)) {
468 if (SETTING_TYPE(var
) == VAR_UINT32
)
469 cliPrintf("%u", value
);
471 cliPrintf("%d", value
);
473 if (SETTING_MODE(var
) == MODE_DIRECT
) {
474 cliPrintf(" %d %u", settingGetMin(var
), settingGetMax(var
));
480 const char *name
= settingLookupValueName(var
, value
);
484 settingGetName(var
, buf
);
485 cliPrintErrorLinef("VALUE %d OUT OF RANGE FOR %s", (int)value
, buf
);
492 static bool valuePtrEqualsDefault(const setting_t
*value
, const void *ptr
, const void *ptrDefault
)
495 switch (SETTING_TYPE(value
)) {
497 result
= *(uint8_t *)ptr
== *(uint8_t *)ptrDefault
;
501 result
= *(int8_t *)ptr
== *(int8_t *)ptrDefault
;
505 result
= *(uint16_t *)ptr
== *(uint16_t *)ptrDefault
;
509 result
= *(int16_t *)ptr
== *(int16_t *)ptrDefault
;
513 result
= *(uint32_t *)ptr
== *(uint32_t *)ptrDefault
;
517 result
= *(float *)ptr
== *(float *)ptrDefault
;
521 result
= strncmp(ptr
, ptrDefault
, settingGetStringMaxLength(value
) + 1) == 0;
527 static void dumpPgValue(const setting_t
*value
, uint8_t dumpMask
)
529 char name
[SETTING_MAX_NAME_LENGTH
];
530 const char *format
= "set %s = ";
531 const char *defaultFormat
= "#set %s = ";
532 // During a dump, the PGs have been backed up to their "copy"
533 // regions and the actual values have been reset to its
534 // defaults. This means that settingGetValuePointer() will
535 // return the default value while settingGetCopyValuePointer()
536 // will return the actual value.
537 const void *valuePointer
= settingGetCopyValuePointer(value
);
538 const void *defaultValuePointer
= settingGetValuePointer(value
);
539 const bool equalsDefault
= valuePtrEqualsDefault(value
, valuePointer
, defaultValuePointer
);
540 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
541 settingGetName(value
, name
);
542 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
543 cliPrintf(defaultFormat
, name
);
544 // if the craftname has a leading space, then enclose the name in quotes
545 if (strcmp(name
, "name") == 0 && ((const char *)valuePointer
)[0] == ' ') {
546 cliPrintf("\"%s\"", (const char *)valuePointer
);
548 printValuePointer(value
, valuePointer
, 0);
552 cliPrintf(format
, name
);
553 printValuePointer(value
, valuePointer
, 0);
558 static void dumpAllValues(uint16_t valueSection
, uint8_t dumpMask
)
560 for (unsigned i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
561 const setting_t
*value
= settingGet(i
);
562 bufWriterFlush(cliWriter
);
563 if (SETTING_SECTION(value
) == valueSection
) {
564 dumpPgValue(value
, dumpMask
);
569 static void cliPrintVar(const setting_t
*var
, uint32_t full
)
571 const void *ptr
= settingGetValuePointer(var
);
573 printValuePointer(var
, ptr
, full
);
576 static void cliPrintVarRange(const setting_t
*var
)
578 switch (SETTING_MODE(var
)) {
580 if (SETTING_TYPE(var
) == VAR_STRING
) {
581 cliPrintLinef("Max. length: %u", settingGetStringMaxLength(var
));
584 cliPrintLinef("Allowed range: %d - %u", settingGetMin(var
), settingGetMax(var
));
588 const lookupTableEntry_t
*tableEntry
= settingLookupTable(var
);
589 cliPrint("Allowed values:");
590 for (uint32_t i
= 0; i
< tableEntry
->valueCount
; i
++) {
593 cliPrintf(" %s", tableEntry
->values
[i
]);
607 static void cliSetIntFloatVar(const setting_t
*var
, const int_float_value_t value
)
609 void *ptr
= settingGetValuePointer(var
);
611 switch (SETTING_TYPE(var
)) {
614 *(int8_t *)ptr
= value
.int_value
;
619 *(int16_t *)ptr
= value
.int_value
;
623 *(uint32_t *)ptr
= value
.uint_value
;
627 *(float *)ptr
= (float)value
.float_value
;
631 // Handled by cliSet directly
636 static void cliPrompt(void)
639 bufWriterFlush(cliWriter
);
642 static void cliShowParseError(void)
644 cliPrintErrorLinef("Parse error");
647 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
649 cliPrintErrorLinef("%s must be between %d and %d", name
, min
, max
);
652 static const char *nextArg(const char *currentArg
)
654 const char *ptr
= strchr(currentArg
, ' ');
655 while (ptr
&& *ptr
== ' ') {
662 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
664 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
667 int val
= fastA2I(ptr
);
668 val
= CHANNEL_VALUE_TO_STEP(val
);
669 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
671 range
->startStep
= val
;
673 range
->endStep
= val
;
675 (*validArgumentCount
)++;
683 // Check if a string's length is zero
684 static bool isEmpty(const char *string
)
686 return (string
== NULL
|| *string
== '\0') ? true : false;
689 #if defined(USE_ASSERT)
690 static void cliAssert(char *cmdline
)
694 if (assertFailureLine
) {
695 if (assertFailureFile
) {
696 cliPrintErrorLinef("Assertion failed at line %d, file %s", assertFailureLine
, assertFailureFile
);
699 cliPrintErrorLinef("Assertion failed at line %d", assertFailureLine
);
702 if (commandBatchActive
) {
703 commandBatchError
= true;
704 commandBatchErrorCount
++;
709 cliPrintLine("No assert() failed");
714 static void printAux(uint8_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
)
716 const char *format
= "aux %u %u %u %u %u";
717 // print out aux channel settings
718 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
719 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
720 bool equalsDefault
= false;
721 if (defaultModeActivationConditions
) {
722 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
723 equalsDefault
= mac
->modeId
== macDefault
->modeId
724 && mac
->auxChannelIndex
== macDefault
->auxChannelIndex
725 && mac
->range
.startStep
== macDefault
->range
.startStep
726 && mac
->range
.endStep
== macDefault
->range
.endStep
;
727 const box_t
*box
= findBoxByActiveBoxId(macDefault
->modeId
);
728 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
731 macDefault
->auxChannelIndex
,
732 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
733 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
)
736 const box_t
*box
= findBoxByActiveBoxId(mac
->modeId
);
737 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
740 mac
->auxChannelIndex
,
741 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
742 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
)
747 static void cliAux(char *cmdline
)
752 if (isEmpty(cmdline
)) {
753 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
);
757 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
758 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
759 uint8_t validArgumentCount
= 0;
764 const box_t
*box
= findBoxByPermanentId(val
);
766 mac
->modeId
= box
->boxId
;
767 validArgumentCount
++;
774 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
775 mac
->auxChannelIndex
= val
;
776 validArgumentCount
++;
779 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
781 if (validArgumentCount
!= 4) {
782 memset(mac
, 0, sizeof(modeActivationCondition_t
));
785 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
790 static void printSerial(uint8_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
)
792 const char *format
= "serial %d %d %ld %ld %ld %ld";
793 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
794 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
797 bool equalsDefault
= false;
798 if (serialConfigDefault
) {
799 equalsDefault
= serialConfig
->portConfigs
[i
].identifier
== serialConfigDefault
->portConfigs
[i
].identifier
800 && serialConfig
->portConfigs
[i
].functionMask
== serialConfigDefault
->portConfigs
[i
].functionMask
801 && serialConfig
->portConfigs
[i
].msp_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
802 && serialConfig
->portConfigs
[i
].gps_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
803 && serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
804 && serialConfig
->portConfigs
[i
].peripheral_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].peripheral_baudrateIndex
;
805 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
806 serialConfigDefault
->portConfigs
[i
].identifier
,
807 serialConfigDefault
->portConfigs
[i
].functionMask
,
808 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
809 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
810 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
811 baudRates
[serialConfigDefault
->portConfigs
[i
].peripheral_baudrateIndex
]
814 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
815 serialConfig
->portConfigs
[i
].identifier
,
816 serialConfig
->portConfigs
[i
].functionMask
,
817 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
818 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
819 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
820 baudRates
[serialConfig
->portConfigs
[i
].peripheral_baudrateIndex
]
825 static void cliSerial(char *cmdline
)
827 if (isEmpty(cmdline
)) {
828 printSerial(DUMP_MASTER
, serialConfig(), NULL
);
831 serialPortConfig_t portConfig
;
833 serialPortConfig_t
*currentConfig
;
835 uint8_t validArgumentCount
= 0;
837 const char *ptr
= cmdline
;
839 int val
= fastA2I(ptr
++);
840 currentConfig
= serialFindPortConfiguration(val
);
841 if (!currentConfig
) {
843 cliPrintErrorLinef("Invalid port ID %d", val
);
846 memcpy(&portConfig
, currentConfig
, sizeof(portConfig
));
847 validArgumentCount
++;
856 portConfig
.functionMask
|= (1 << val
);
862 portConfig
.functionMask
&= 0xFFFFFFFF ^ (1 << val
);
867 portConfig
.functionMask
= val
& 0xFFFFFFFF;
870 validArgumentCount
++;
873 for (int i
= 0; i
< 4; i
++) {
881 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
882 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
888 baudRateIndex
= constrain(baudRateIndex
, BAUD_MIN
, BAUD_MAX
);
889 portConfig
.msp_baudrateIndex
= baudRateIndex
;
892 baudRateIndex
= constrain(baudRateIndex
, BAUD_MIN
, BAUD_MAX
);
893 portConfig
.gps_baudrateIndex
= baudRateIndex
;
896 baudRateIndex
= constrain(baudRateIndex
, BAUD_MIN
, BAUD_MAX
);
897 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
900 baudRateIndex
= constrain(baudRateIndex
, BAUD_MIN
, BAUD_MAX
);
901 portConfig
.peripheral_baudrateIndex
= baudRateIndex
;
905 validArgumentCount
++;
908 if (validArgumentCount
< 2) {
913 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
916 #ifdef USE_SERIAL_PASSTHROUGH
918 portOptions_t
constructPortOptions(char *options
) {
919 if (strlen(options
) != 3 || options
[0] != '8') {
924 portOptions_t result
= 0;
926 switch (options
[1]) {
928 result
|= SERIAL_PARITY_NO
;
931 result
|= SERIAL_PARITY_EVEN
;
938 switch (options
[2]) {
940 result
|= SERIAL_STOPBITS_1
;
943 result
|= SERIAL_STOPBITS_2
;
953 static void cliSerialPassthrough(char *cmdline
)
957 if (isEmpty(cmdline
)) {
965 portOptions_t options
= SERIAL_NOT_INVERTED
;
966 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
969 while (tok
!= NULL
) {
978 if (strstr(tok
, "rx") || strstr(tok
, "RX"))
980 if (strstr(tok
, "tx") || strstr(tok
, "TX"))
984 options
|= constructPortOptions(tok
);
988 tok
= strtok_r(NULL
, " ", &saveptr
);
991 serialPort_t
*passThroughPort
;
992 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
993 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
995 tfp_printf("Port %d is closed, must specify baud.\r\n", id
);
1001 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
, NULL
,
1004 if (!passThroughPort
) {
1005 tfp_printf("Port %d could not be opened.\r\n", id
);
1008 tfp_printf("Port %d opened, baud = %u.\r\n", id
, (unsigned)baud
);
1010 passThroughPort
= passThroughPortUsage
->serialPort
;
1011 // If the user supplied a mode, override the port's mode, otherwise
1012 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1013 tfp_printf("Port %d already open.\r\n", id
);
1014 if (mode
&& passThroughPort
->mode
!= mode
) {
1015 tfp_printf("Adjusting mode from %d to %d.\r\n",
1016 passThroughPort
->mode
, mode
);
1017 serialSetMode(passThroughPort
, mode
);
1019 if (options
&& passThroughPort
->options
!= options
) {
1020 tfp_printf("Adjusting options from %d to %d.\r\n",
1021 passThroughPort
->options
, options
);
1022 serialSetOptions(passThroughPort
, options
);
1024 // If this port has a rx callback associated we need to remove it now.
1025 // Otherwise no data will be pushed in the serial port buffer!
1026 if (passThroughPort
->rxCallback
) {
1027 tfp_printf("Removing rxCallback\r\n");
1028 passThroughPort
->rxCallback
= 0;
1032 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id
);
1034 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
1038 static void printAdjustmentRange(uint8_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
)
1040 const char *format
= "adjrange %u %u %u %u %u %u %u";
1041 // print out adjustment ranges channel settings
1042 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
1043 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
1044 bool equalsDefault
= false;
1045 if (defaultAdjustmentRanges
) {
1046 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
1047 equalsDefault
= ar
->auxChannelIndex
== arDefault
->auxChannelIndex
1048 && ar
->range
.startStep
== arDefault
->range
.startStep
1049 && ar
->range
.endStep
== arDefault
->range
.endStep
1050 && ar
->adjustmentFunction
== arDefault
->adjustmentFunction
1051 && ar
->auxSwitchChannelIndex
== arDefault
->auxSwitchChannelIndex
1052 && ar
->adjustmentIndex
== arDefault
->adjustmentIndex
;
1053 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1055 arDefault
->adjustmentIndex
,
1056 arDefault
->auxChannelIndex
,
1057 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1058 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1059 arDefault
->adjustmentFunction
,
1060 arDefault
->auxSwitchChannelIndex
1063 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1065 ar
->adjustmentIndex
,
1066 ar
->auxChannelIndex
,
1067 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1068 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1069 ar
->adjustmentFunction
,
1070 ar
->auxSwitchChannelIndex
1075 static void cliAdjustmentRange(char *cmdline
)
1080 if (isEmpty(cmdline
)) {
1081 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
);
1085 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1086 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1087 uint8_t validArgumentCount
= 0;
1092 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
1093 ar
->adjustmentIndex
= val
;
1094 validArgumentCount
++;
1100 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1101 ar
->auxChannelIndex
= val
;
1102 validArgumentCount
++;
1106 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1111 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1112 ar
->adjustmentFunction
= val
;
1113 validArgumentCount
++;
1119 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1120 ar
->auxSwitchChannelIndex
= val
;
1121 validArgumentCount
++;
1125 if (validArgumentCount
!= 6) {
1126 memset(ar
, 0, sizeof(adjustmentRange_t
));
1127 cliShowParseError();
1130 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1135 static void printMotorMix(uint8_t dumpMask
, const motorMixer_t
*primaryMotorMixer
, const motorMixer_t
*defaultprimaryMotorMixer
)
1137 const char *format
= "mmix %d %s %s %s %s";
1138 char buf0
[FTOA_BUFFER_SIZE
];
1139 char buf1
[FTOA_BUFFER_SIZE
];
1140 char buf2
[FTOA_BUFFER_SIZE
];
1141 char buf3
[FTOA_BUFFER_SIZE
];
1142 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1143 if (primaryMotorMixer
[i
].throttle
== 0.0f
)
1145 const float thr
= primaryMotorMixer
[i
].throttle
;
1146 const float roll
= primaryMotorMixer
[i
].roll
;
1147 const float pitch
= primaryMotorMixer
[i
].pitch
;
1148 const float yaw
= primaryMotorMixer
[i
].yaw
;
1149 bool equalsDefault
= false;
1150 if (defaultprimaryMotorMixer
) {
1151 const float thrDefault
= defaultprimaryMotorMixer
[i
].throttle
;
1152 const float rollDefault
= defaultprimaryMotorMixer
[i
].roll
;
1153 const float pitchDefault
= defaultprimaryMotorMixer
[i
].pitch
;
1154 const float yawDefault
= defaultprimaryMotorMixer
[i
].yaw
;
1155 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1157 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1159 ftoa(thrDefault
, buf0
),
1160 ftoa(rollDefault
, buf1
),
1161 ftoa(pitchDefault
, buf2
),
1162 ftoa(yawDefault
, buf3
));
1164 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1173 static void cliMotorMix(char *cmdline
)
1178 if (isEmpty(cmdline
)) {
1179 printMotorMix(DUMP_MASTER
, primaryMotorMixer(0), NULL
);
1180 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1181 // erase custom mixer
1182 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1183 primaryMotorMixerMutable(i
)->throttle
= 0.0f
;
1187 uint32_t i
= fastA2I(ptr
); // get motor number
1188 if (i
< MAX_SUPPORTED_MOTORS
) {
1191 primaryMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1196 primaryMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1201 primaryMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1206 primaryMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1210 cliShowParseError();
1212 printMotorMix(DUMP_MASTER
, primaryMotorMixer(0), NULL
);
1215 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
1220 static void printRxRange(uint8_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
)
1222 const char *format
= "rxrange %u %u %u";
1223 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1224 bool equalsDefault
= false;
1225 if (defaultChannelRangeConfigs
) {
1226 equalsDefault
= channelRangeConfigs
[i
].min
== defaultChannelRangeConfigs
[i
].min
1227 && channelRangeConfigs
[i
].max
== defaultChannelRangeConfigs
[i
].max
;
1228 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1230 defaultChannelRangeConfigs
[i
].min
,
1231 defaultChannelRangeConfigs
[i
].max
1234 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1236 channelRangeConfigs
[i
].min
,
1237 channelRangeConfigs
[i
].max
1242 static void cliRxRange(char *cmdline
)
1244 int i
, validArgumentCount
= 0;
1247 if (isEmpty(cmdline
)) {
1248 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
);
1249 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1250 resetAllRxChannelRangeConfigurations();
1254 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1255 int rangeMin
= 0, rangeMax
= 0;
1259 rangeMin
= fastA2I(ptr
);
1260 validArgumentCount
++;
1265 rangeMax
= fastA2I(ptr
);
1266 validArgumentCount
++;
1269 if (validArgumentCount
!= 2) {
1270 cliShowParseError();
1271 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1272 cliShowParseError();
1274 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1275 channelRangeConfig
->min
= rangeMin
;
1276 channelRangeConfig
->max
= rangeMax
;
1279 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT
- 1);
1284 #ifdef USE_TEMPERATURE_SENSOR
1285 static void printTempSensor(uint8_t dumpMask
, const tempSensorConfig_t
*tempSensorConfigs
, const tempSensorConfig_t
*defaultTempSensorConfigs
)
1287 const char *format
= "temp_sensor %u %u %s %d %d %u %s";
1288 for (uint8_t i
= 0; i
< MAX_TEMP_SENSORS
; i
++) {
1289 bool equalsDefault
= false;
1290 char label
[5], hex_address
[17];
1291 strncpy(label
, tempSensorConfigs
[i
].label
, TEMPERATURE_LABEL_LEN
);
1293 tempSensorAddressToString(tempSensorConfigs
[i
].address
, hex_address
);
1294 if (defaultTempSensorConfigs
) {
1295 equalsDefault
= tempSensorConfigs
[i
].type
== defaultTempSensorConfigs
[i
].type
1296 && tempSensorConfigs
[i
].address
== defaultTempSensorConfigs
[i
].address
1297 && tempSensorConfigs
[i
].osdSymbol
== defaultTempSensorConfigs
[i
].osdSymbol
1298 && !memcmp(tempSensorConfigs
[i
].label
, defaultTempSensorConfigs
[i
].label
, TEMPERATURE_LABEL_LEN
)
1299 && tempSensorConfigs
[i
].alarm_min
== defaultTempSensorConfigs
[i
].alarm_min
1300 && tempSensorConfigs
[i
].alarm_max
== defaultTempSensorConfigs
[i
].alarm_max
;
1301 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1303 defaultTempSensorConfigs
[i
].type
,
1305 defaultTempSensorConfigs
[i
].alarm_min
,
1306 defaultTempSensorConfigs
[i
].alarm_max
,
1311 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1313 tempSensorConfigs
[i
].type
,
1315 tempSensorConfigs
[i
].alarm_min
,
1316 tempSensorConfigs
[i
].alarm_max
,
1317 tempSensorConfigs
[i
].osdSymbol
,
1323 static void cliTempSensor(char *cmdline
)
1325 if (isEmpty(cmdline
)) {
1326 printTempSensor(DUMP_MASTER
, tempSensorConfig(0), NULL
);
1327 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1328 resetTempSensorConfig();
1331 const char *ptr
= cmdline
, *label
;
1332 int16_t type
=0, alarm_min
=0, alarm_max
=0;
1333 bool addressValid
= false;
1336 uint8_t validArgumentCount
= 0;
1338 if (i
>= 0 && i
< MAX_TEMP_SENSORS
) {
1342 type
= fastA2I(ptr
);
1343 validArgumentCount
++;
1348 addressValid
= tempSensorStringToAddress(ptr
, &address
);
1349 validArgumentCount
++;
1354 alarm_min
= fastA2I(ptr
);
1355 validArgumentCount
++;
1360 alarm_max
= fastA2I(ptr
);
1361 validArgumentCount
++;
1366 osdSymbol
= fastA2I(ptr
);
1367 validArgumentCount
++;
1370 label
= nextArg(ptr
);
1372 ++validArgumentCount
;
1376 if (validArgumentCount
< 4) {
1377 cliShowParseError();
1378 } 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
) {
1379 cliShowParseError();
1381 tempSensorConfig_t
*sensorConfig
= tempSensorConfigMutable(i
);
1382 sensorConfig
->type
= type
;
1383 sensorConfig
->address
= address
;
1384 sensorConfig
->alarm_min
= alarm_min
;
1385 sensorConfig
->alarm_max
= alarm_max
;
1386 sensorConfig
->osdSymbol
= osdSymbol
;
1387 for (uint8_t index
= 0; index
< TEMPERATURE_LABEL_LEN
; ++index
) {
1388 sensorConfig
->label
[index
] = toupper(label
[index
]);
1389 if (label
[index
] == '\0') break;
1393 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS
- 1);
1399 #ifdef USE_FW_AUTOLAND
1400 static void printFwAutolandApproach(uint8_t dumpMask
, const navFwAutolandApproach_t
*navFwAutolandApproach
, const navFwAutolandApproach_t
*defaultFwAutolandApproach
)
1402 const char *format
= "fwapproach %u %d %d %u %d %d %u";
1403 for (uint8_t i
= 0; i
< MAX_FW_LAND_APPOACH_SETTINGS
; i
++) {
1404 bool equalsDefault
= false;
1405 if (defaultFwAutolandApproach
) {
1406 equalsDefault
= navFwAutolandApproach
[i
].approachDirection
== defaultFwAutolandApproach
[i
].approachDirection
1407 && navFwAutolandApproach
[i
].approachAlt
== defaultFwAutolandApproach
[i
].approachAlt
1408 && navFwAutolandApproach
[i
].landAlt
== defaultFwAutolandApproach
[i
].landAlt
1409 && navFwAutolandApproach
[i
].landApproachHeading1
== defaultFwAutolandApproach
[i
].landApproachHeading1
1410 && navFwAutolandApproach
[i
].landApproachHeading2
== defaultFwAutolandApproach
[i
].landApproachHeading2
1411 && navFwAutolandApproach
[i
].isSeaLevelRef
== defaultFwAutolandApproach
[i
].isSeaLevelRef
;
1412 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1413 defaultFwAutolandApproach
[i
].approachAlt
, defaultFwAutolandApproach
[i
].landAlt
, defaultFwAutolandApproach
[i
].approachDirection
, defaultFwAutolandApproach
[i
].landApproachHeading1
, defaultFwAutolandApproach
[i
].landApproachHeading2
, defaultFwAutolandApproach
[i
].isSeaLevelRef
);
1415 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1416 navFwAutolandApproach
[i
].approachAlt
, navFwAutolandApproach
[i
].landAlt
, navFwAutolandApproach
[i
].approachDirection
, navFwAutolandApproach
[i
].landApproachHeading1
, navFwAutolandApproach
[i
].landApproachHeading2
, navFwAutolandApproach
[i
].isSeaLevelRef
);
1420 static void cliFwAutolandApproach(char * cmdline
)
1422 if (isEmpty(cmdline
)) {
1423 printFwAutolandApproach(DUMP_MASTER
, fwAutolandApproachConfig(0), NULL
);
1424 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1425 resetFwAutolandApproach(-1);
1427 int32_t approachAlt
= 0, heading1
= 0, heading2
= 0, landDirection
= 0, landAlt
= 0;
1428 bool isSeaLevelRef
= false;
1429 uint8_t validArgumentCount
= 0;
1430 const char *ptr
= cmdline
;
1431 int8_t i
= fastA2I(ptr
);
1432 if (i
< 0 || i
>= MAX_FW_LAND_APPOACH_SETTINGS
) {
1433 cliShowArgumentRangeError("fwapproach index", 0, MAX_FW_LAND_APPOACH_SETTINGS
- 1);
1435 if ((ptr
= nextArg(ptr
))) {
1436 approachAlt
= fastA2I(ptr
);
1437 validArgumentCount
++;
1440 if ((ptr
= nextArg(ptr
))) {
1441 landAlt
= fastA2I(ptr
);
1442 validArgumentCount
++;
1445 if ((ptr
= nextArg(ptr
))) {
1446 landDirection
= fastA2I(ptr
);
1448 if (landDirection
!= 0 && landDirection
!= 1) {
1449 cliShowParseError();
1453 validArgumentCount
++;
1456 if ((ptr
= nextArg(ptr
))) {
1457 heading1
= fastA2I(ptr
);
1459 if (heading1
< -360 || heading1
> 360) {
1460 cliShowParseError();
1464 validArgumentCount
++;
1467 if ((ptr
= nextArg(ptr
))) {
1468 heading2
= fastA2I(ptr
);
1470 if (heading2
< -360 || heading2
> 360) {
1471 cliShowParseError();
1475 validArgumentCount
++;
1478 if ((ptr
= nextArg(ptr
))) {
1479 isSeaLevelRef
= fastA2I(ptr
);
1480 validArgumentCount
++;
1483 if ((ptr
= nextArg(ptr
))) {
1484 // check for too many arguments
1485 validArgumentCount
++;
1488 if (validArgumentCount
!= 6) {
1489 cliShowParseError();
1491 fwAutolandApproachConfigMutable(i
)->approachAlt
= approachAlt
;
1492 fwAutolandApproachConfigMutable(i
)->landAlt
= landAlt
;
1493 fwAutolandApproachConfigMutable(i
)->approachDirection
= (fwAutolandApproachDirection_e
)landDirection
;
1494 fwAutolandApproachConfigMutable(i
)->landApproachHeading1
= (int16_t)heading1
;
1495 fwAutolandApproachConfigMutable(i
)->landApproachHeading2
= (int16_t)heading2
;
1496 fwAutolandApproachConfigMutable(i
)->isSeaLevelRef
= isSeaLevelRef
;
1503 #if defined(USE_SAFE_HOME)
1504 static void printSafeHomes(uint8_t dumpMask
, const navSafeHome_t
*navSafeHome
, const navSafeHome_t
*defaultSafeHome
)
1506 const char *format
= "safehome %u %u %d %d"; // uint8_t enabled, int32_t lat; int32_t lon
1507 for (uint8_t i
= 0; i
< MAX_SAFE_HOMES
; i
++) {
1508 bool equalsDefault
= false;
1509 if (defaultSafeHome
) {
1510 equalsDefault
= navSafeHome
[i
].enabled
== defaultSafeHome
[i
].enabled
1511 && navSafeHome
[i
].lat
== defaultSafeHome
[i
].lat
1512 && navSafeHome
[i
].lon
== defaultSafeHome
[i
].lon
;
1513 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1514 defaultSafeHome
[i
].enabled
, defaultSafeHome
[i
].lat
, defaultSafeHome
[i
].lon
);
1516 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1517 navSafeHome
[i
].enabled
, navSafeHome
[i
].lat
, navSafeHome
[i
].lon
);
1521 static void cliSafeHomes(char *cmdline
)
1523 if (isEmpty(cmdline
)) {
1524 printSafeHomes(DUMP_MASTER
, safeHomeConfig(0), NULL
);
1525 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1528 int32_t lat
=0, lon
=0;
1530 uint8_t validArgumentCount
= 0;
1531 const char *ptr
= cmdline
;
1532 int8_t i
= fastA2I(ptr
);
1533 if (i
< 0 || i
>= MAX_SAFE_HOMES
) {
1534 cliShowArgumentRangeError("safehome index", 0, MAX_SAFE_HOMES
- 1);
1536 if ((ptr
= nextArg(ptr
))) {
1537 enabled
= fastA2I(ptr
);
1538 validArgumentCount
++;
1540 if ((ptr
= nextArg(ptr
))) {
1542 validArgumentCount
++;
1544 if ((ptr
= nextArg(ptr
))) {
1546 validArgumentCount
++;
1548 if ((ptr
= nextArg(ptr
))) {
1549 // check for too many arguments
1550 validArgumentCount
++;
1552 if (validArgumentCount
!= 3) {
1553 cliShowParseError();
1555 safeHomeConfigMutable(i
)->enabled
= enabled
;
1556 safeHomeConfigMutable(i
)->lat
= lat
;
1557 safeHomeConfigMutable(i
)->lon
= lon
;
1564 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1565 static void printWaypoints(uint8_t dumpMask
, const navWaypoint_t
*navWaypoint
, const navWaypoint_t
*defaultNavWaypoint
)
1567 cliPrintLinef("#wp %d %svalid", posControl
.waypointCount
, posControl
.waypointListValid
? "" : "in"); //int8_t bool
1568 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
1569 for (uint8_t i
= 0; i
< NAV_MAX_WAYPOINTS
; i
++) {
1570 bool equalsDefault
= false;
1571 if (defaultNavWaypoint
) {
1572 equalsDefault
= navWaypoint
[i
].action
== defaultNavWaypoint
[i
].action
1573 && navWaypoint
[i
].lat
== defaultNavWaypoint
[i
].lat
1574 && navWaypoint
[i
].lon
== defaultNavWaypoint
[i
].lon
1575 && navWaypoint
[i
].alt
== defaultNavWaypoint
[i
].alt
1576 && navWaypoint
[i
].p1
== defaultNavWaypoint
[i
].p1
1577 && navWaypoint
[i
].p2
== defaultNavWaypoint
[i
].p2
1578 && navWaypoint
[i
].p3
== defaultNavWaypoint
[i
].p3
1579 && navWaypoint
[i
].flag
== defaultNavWaypoint
[i
].flag
;
1580 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1582 defaultNavWaypoint
[i
].action
,
1583 defaultNavWaypoint
[i
].lat
,
1584 defaultNavWaypoint
[i
].lon
,
1585 defaultNavWaypoint
[i
].alt
,
1586 defaultNavWaypoint
[i
].p1
,
1587 defaultNavWaypoint
[i
].p2
,
1588 defaultNavWaypoint
[i
].p3
,
1589 defaultNavWaypoint
[i
].flag
1592 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1594 navWaypoint
[i
].action
,
1606 static void cliWaypoints(char *cmdline
)
1608 #ifdef USE_MULTI_MISSION
1609 static int8_t multiMissionWPCounter
= 0;
1611 if (isEmpty(cmdline
)) {
1612 printWaypoints(DUMP_MASTER
, posControl
.waypointList
, NULL
);
1613 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1614 resetWaypointList();
1615 } else if (sl_strcasecmp(cmdline
, "load") == 0) {
1616 loadNonVolatileWaypointList(true);
1617 } else if (sl_strcasecmp(cmdline
, "save") == 0) {
1618 posControl
.waypointListValid
= false;
1619 for (int i
= 0; i
< NAV_MAX_WAYPOINTS
; i
++) {
1620 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;
1621 if (posControl
.waypointList
[i
].flag
== NAV_WP_FLAG_LAST
) {
1622 #ifdef USE_MULTI_MISSION
1623 if (posControl
.multiMissionCount
== 1) {
1624 posControl
.waypointCount
= i
+ 1;
1625 posControl
.waypointListValid
= true;
1626 multiMissionWPCounter
= 0;
1627 posControl
.multiMissionCount
= 0;
1630 posControl
.multiMissionCount
-= 1;
1633 posControl
.waypointCount
= i
+ 1;
1634 posControl
.waypointListValid
= true;
1639 if (posControl
.waypointListValid
) {
1640 saveNonVolatileWaypointList();
1642 cliShowParseError();
1645 int16_t i
, p1
=0,p2
=0,p3
=0,tmp
=0;
1646 uint8_t action
=0, flag
=0;
1647 int32_t lat
=0, lon
=0, alt
=0;
1648 uint8_t validArgumentCount
= 0;
1649 const char *ptr
= cmdline
;
1651 #ifdef USE_MULTI_MISSION
1652 if (i
+ multiMissionWPCounter
>= 0 && i
+ multiMissionWPCounter
< NAV_MAX_WAYPOINTS
) {
1654 if (i
>= 0 && i
< NAV_MAX_WAYPOINTS
) {
1658 action
= fastA2I(ptr
);
1659 validArgumentCount
++;
1664 validArgumentCount
++;
1669 validArgumentCount
++;
1674 validArgumentCount
++;
1679 validArgumentCount
++;
1684 validArgumentCount
++;
1686 /* We support pre-2.5 6 values (... p1,flags) or
1687 * 2.5 and later, 8 values (... p1,p2,p3,flags)
1693 validArgumentCount
++;
1696 flag
= fastA2I(ptr
);
1697 validArgumentCount
++;
1703 if (!(validArgumentCount
== 6 || validArgumentCount
== 8)) {
1704 cliShowParseError();
1705 } 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
)) {
1706 cliShowParseError();
1708 #ifdef USE_MULTI_MISSION
1709 if (i
+ multiMissionWPCounter
== 0) {
1710 posControl
.multiMissionCount
= 0;
1713 posControl
.waypointList
[i
+ multiMissionWPCounter
].action
= action
;
1714 posControl
.waypointList
[i
+ multiMissionWPCounter
].lat
= lat
;
1715 posControl
.waypointList
[i
+ multiMissionWPCounter
].lon
= lon
;
1716 posControl
.waypointList
[i
+ multiMissionWPCounter
].alt
= alt
;
1717 posControl
.waypointList
[i
+ multiMissionWPCounter
].p1
= p1
;
1718 posControl
.waypointList
[i
+ multiMissionWPCounter
].p2
= p2
;
1719 posControl
.waypointList
[i
+ multiMissionWPCounter
].p3
= p3
;
1720 posControl
.waypointList
[i
+ multiMissionWPCounter
].flag
= flag
;
1722 // Process WP entries made up of multiple successive WP missions (multiple NAV_WP_FLAG_LAST entries)
1723 // Individial missions loaded at runtime, mission selected nav_waypoint_multi_mission_index
1724 if (flag
== NAV_WP_FLAG_LAST
) {
1725 multiMissionWPCounter
+= i
+ 1;
1726 posControl
.multiMissionCount
+= 1;
1729 posControl
.waypointList
[i
].action
= action
;
1730 posControl
.waypointList
[i
].lat
= lat
;
1731 posControl
.waypointList
[i
].lon
= lon
;
1732 posControl
.waypointList
[i
].alt
= alt
;
1733 posControl
.waypointList
[i
].p1
= p1
;
1734 posControl
.waypointList
[i
].p2
= p2
;
1735 posControl
.waypointList
[i
].p3
= p3
;
1736 posControl
.waypointList
[i
].flag
= flag
;
1740 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS
- 1);
1747 #ifdef USE_LED_STRIP
1748 static void printLed(uint8_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
)
1750 const char *format
= "led %u %s";
1751 char ledConfigBuffer
[20];
1752 char ledConfigDefaultBuffer
[20];
1753 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1754 ledConfig_t ledConfig
= ledConfigs
[i
];
1755 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1756 bool equalsDefault
= false;
1757 if (defaultLedConfigs
) {
1758 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1759 equalsDefault
= !memcmp(&ledConfig
, &ledConfigDefault
, sizeof(ledConfig_t
));
1760 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1761 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1763 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1767 static void cliLed(char *cmdline
)
1772 if (isEmpty(cmdline
)) {
1773 printLed(DUMP_MASTER
, ledStripConfig()->ledConfigs
, NULL
);
1777 if (i
< LED_MAX_STRIP_LENGTH
) {
1778 ptr
= nextArg(cmdline
);
1779 if (!parseLedStripConfig(i
, ptr
)) {
1780 cliShowParseError();
1783 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH
- 1);
1788 static void printColor(uint8_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
)
1790 const char *format
= "color %u %d,%u,%u";
1791 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1792 const hsvColor_t
*color
= &colors
[i
];
1793 bool equalsDefault
= false;
1794 if (defaultColors
) {
1795 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1796 equalsDefault
= color
->h
== colorDefault
->h
1797 && color
->s
== colorDefault
->s
1798 && color
->v
== colorDefault
->v
;
1799 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1801 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1805 static void cliColor(char *cmdline
)
1807 if (isEmpty(cmdline
)) {
1808 printColor(DUMP_MASTER
, ledStripConfig()->colors
, NULL
);
1810 const char *ptr
= cmdline
;
1811 const int i
= fastA2I(ptr
);
1812 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1813 ptr
= nextArg(cmdline
);
1814 if (!parseColor(i
, ptr
)) {
1815 cliShowParseError();
1818 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1823 static void printModeColor(uint8_t dumpMask
, const ledStripConfig_t
*ledStripConfig
, const ledStripConfig_t
*defaultLedStripConfig
)
1825 const char *format
= "mode_color %u %u %u";
1826 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1827 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1828 int colorIndex
= ledStripConfig
->modeColors
[i
].color
[j
];
1829 bool equalsDefault
= false;
1830 if (defaultLedStripConfig
) {
1831 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1832 equalsDefault
= colorIndex
== colorIndexDefault
;
1833 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1835 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1839 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1840 const int colorIndex
= ledStripConfig
->specialColors
.color
[j
];
1841 bool equalsDefault
= false;
1842 if (defaultLedStripConfig
) {
1843 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1844 equalsDefault
= colorIndex
== colorIndexDefault
;
1845 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1847 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1851 static void cliModeColor(char *cmdline
)
1855 if (isEmpty(cmdline
)) {
1856 printModeColor(DUMP_MASTER
, ledStripConfig(), NULL
);
1858 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1859 int args
[ARGS_COUNT
];
1861 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1862 while (ptr
&& argNo
< ARGS_COUNT
) {
1863 args
[argNo
++] = fastA2I(ptr
);
1864 ptr
= strtok_r(NULL
, " ", &saveptr
);
1867 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1868 cliShowParseError();
1872 int modeIdx
= args
[MODE
];
1873 int funIdx
= args
[FUNCTION
];
1874 int color
= args
[COLOR
];
1875 if (!setModeColor(modeIdx
, funIdx
, color
)) {
1876 cliShowParseError();
1879 // values are validated
1880 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1884 static void cliLedPinPWM(char *cmdline
)
1888 if (isEmpty(cmdline
)) {
1890 cliPrintLine("PWM stopped");
1892 i
= fastA2I(cmdline
);
1894 cliPrintLinef("PWM started: %d%%",i
);
1899 static void cliDelay(char* cmdLine
) {
1901 if (isEmpty(cmdLine
)) {
1903 cliPrintLine("CLI delay deactivated");
1907 ms
= fastA2I(cmdLine
);
1910 cliPrintLinef("CLI delay set to %d ms", ms
);
1913 cliShowParseError();
1918 static void printServo(uint8_t dumpMask
, const servoParam_t
*servoParam
, const servoParam_t
*defaultServoParam
)
1920 // print out servo settings
1921 const char *format
= "servo %u %d %d %d %d";
1922 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1923 const servoParam_t
*servoConf
= &servoParam
[i
];
1924 bool equalsDefault
= false;
1925 if (defaultServoParam
) {
1926 const servoParam_t
*servoConfDefault
= &defaultServoParam
[i
];
1927 equalsDefault
= servoConf
->min
== servoConfDefault
->min
1928 && servoConf
->max
== servoConfDefault
->max
1929 && servoConf
->middle
== servoConfDefault
->middle
1930 && servoConf
->rate
== servoConfDefault
->rate
;
1931 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1933 servoConfDefault
->min
,
1934 servoConfDefault
->max
,
1935 servoConfDefault
->middle
,
1936 servoConfDefault
->rate
1939 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1949 static void cliServo(char *cmdline
)
1951 enum { SERVO_ARGUMENT_COUNT
= 5 };
1952 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1954 servoParam_t
*servo
;
1959 if (isEmpty(cmdline
)) {
1960 printServo(DUMP_MASTER
, servoParams(0), NULL
);
1962 int validArgumentCount
= 0;
1966 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1968 // If command line doesn't fit the format, don't modify the config
1970 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1971 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1972 cliShowParseError();
1976 arguments
[validArgumentCount
++] = fastA2I(ptr
);
1980 } while (*ptr
>= '0' && *ptr
<= '9');
1981 } else if (*ptr
== ' ') {
1984 cliShowParseError();
1989 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
};
1991 i
= arguments
[INDEX
];
1993 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1994 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1995 cliShowParseError();
1999 servo
= servoParamsMutable(i
);
2002 arguments
[MIN
] < SERVO_OUTPUT_MIN
|| arguments
[MIN
] > SERVO_OUTPUT_MAX
||
2003 arguments
[MAX
] < SERVO_OUTPUT_MIN
|| arguments
[MAX
] > SERVO_OUTPUT_MAX
||
2004 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
2005 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
2006 arguments
[RATE
] < -125 || arguments
[RATE
] > 125
2008 cliShowParseError();
2012 servo
->min
= arguments
[MIN
];
2013 servo
->max
= arguments
[MAX
];
2014 servo
->middle
= arguments
[MIDDLE
];
2015 servo
->rate
= arguments
[RATE
];
2019 static void printServoMix(uint8_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
)
2021 const char *format
= "smix %d %d %d %d %d %d";
2022 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
2023 const servoMixer_t customServoMixer
= customServoMixers
[i
];
2024 if (customServoMixer
.rate
== 0) {
2028 bool equalsDefault
= false;
2029 if (defaultCustomServoMixers
) {
2030 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
2031 equalsDefault
= customServoMixer
.targetChannel
== customServoMixerDefault
.targetChannel
2032 && customServoMixer
.inputSource
== customServoMixerDefault
.inputSource
2033 && customServoMixer
.rate
== customServoMixerDefault
.rate
2034 && customServoMixer
.speed
== customServoMixerDefault
.speed
2035 #ifdef USE_PROGRAMMING_FRAMEWORK
2036 && customServoMixer
.conditionId
== customServoMixerDefault
.conditionId
2040 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2042 customServoMixerDefault
.targetChannel
,
2043 customServoMixerDefault
.inputSource
,
2044 customServoMixerDefault
.rate
,
2045 customServoMixerDefault
.speed
,
2046 #ifdef USE_PROGRAMMING_FRAMEWORK
2047 customServoMixer
.conditionId
2053 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2055 customServoMixer
.targetChannel
,
2056 customServoMixer
.inputSource
,
2057 customServoMixer
.rate
,
2058 customServoMixer
.speed
,
2059 #ifdef USE_PROGRAMMING_FRAMEWORK
2060 customServoMixer
.conditionId
2068 static void cliServoMix(char *cmdline
)
2071 int args
[6], check
= 0;
2072 uint8_t len
= strlen(cmdline
);
2075 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
);
2076 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2077 // erase custom mixer
2078 Reset_servoMixers(customServoMixersMutable(0));
2080 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, CONDITION
, ARGS_COUNT
};
2081 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2082 args
[CONDITION
] = -1;
2083 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2084 args
[check
++] = fastA2I(ptr
);
2085 ptr
= strtok_r(NULL
, " ", &saveptr
);
2088 if (ptr
!= NULL
|| (check
< ARGS_COUNT
- 1)) {
2089 cliShowParseError();
2093 int32_t i
= args
[RULE
];
2095 i
>= 0 && i
< MAX_SERVO_RULES
&&
2096 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
2097 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
2098 args
[RATE
] >= -1000 && args
[RATE
] <= 1000 &&
2099 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
2100 args
[CONDITION
] >= -1 && args
[CONDITION
] < MAX_LOGIC_CONDITIONS
2102 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
2103 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
2104 customServoMixersMutable(i
)->rate
= args
[RATE
];
2105 customServoMixersMutable(i
)->speed
= args
[SPEED
];
2106 #ifdef USE_PROGRAMMING_FRAMEWORK
2107 customServoMixersMutable(i
)->conditionId
= args
[CONDITION
];
2111 cliShowParseError();
2116 #ifdef USE_PROGRAMMING_FRAMEWORK
2118 static void printLogic(uint8_t dumpMask
, const logicCondition_t
*logicConditions
, const logicCondition_t
*defaultLogicConditions
, int16_t showLC
)
2120 const char *format
= "logic %d %d %d %d %d %d %d %d %d";
2121 for (uint8_t i
= 0; i
< MAX_LOGIC_CONDITIONS
; i
++) {
2122 if (showLC
== -1 || showLC
== i
) {
2123 const logicCondition_t logic
= logicConditions
[i
];
2125 bool equalsDefault
= false;
2126 if (defaultLogicConditions
) {
2127 logicCondition_t defaultValue
= defaultLogicConditions
[i
];
2129 logic
.enabled
== defaultValue
.enabled
&&
2130 logic
.activatorId
== defaultValue
.activatorId
&&
2131 logic
.operation
== defaultValue
.operation
&&
2132 logic
.operandA
.type
== defaultValue
.operandA
.type
&&
2133 logic
.operandA
.value
== defaultValue
.operandA
.value
&&
2134 logic
.operandB
.type
== defaultValue
.operandB
.type
&&
2135 logic
.operandB
.value
== defaultValue
.operandB
.value
&&
2136 logic
.flags
== defaultValue
.flags
;
2138 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2143 logic
.operandA
.type
,
2144 logic
.operandA
.value
,
2145 logic
.operandB
.type
,
2146 logic
.operandB
.value
,
2150 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2155 logic
.operandA
.type
,
2156 logic
.operandA
.value
,
2157 logic
.operandB
.type
,
2158 logic
.operandB
.value
,
2165 static void processCliLogic(char *cmdline
, int16_t lcIndex
) {
2167 int args
[9], check
= 0;
2168 uint8_t len
= strlen(cmdline
);
2171 if (!commandBatchActive
) {
2172 printLogic(DUMP_MASTER
, logicConditions(0), NULL
, -1);
2173 } else if (lcIndex
>= 0) {
2174 printLogic(DUMP_MASTER
, logicConditions(0), NULL
, lcIndex
);
2176 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2177 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS
);
2191 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2192 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2193 args
[check
++] = fastA2I(ptr
);
2194 ptr
= strtok_r(NULL
, " ", &saveptr
);
2197 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2198 cliShowParseError();
2202 int32_t i
= args
[INDEX
];
2204 i
>= 0 && i
< MAX_LOGIC_CONDITIONS
&&
2205 args
[ENABLED
] >= 0 && args
[ENABLED
] <= 1 &&
2206 args
[ACTIVATOR_ID
] >= -1 && args
[ACTIVATOR_ID
] < MAX_LOGIC_CONDITIONS
&&
2207 args
[OPERATION
] >= 0 && args
[OPERATION
] < LOGIC_CONDITION_LAST
&&
2208 args
[OPERAND_A_TYPE
] >= 0 && args
[OPERAND_A_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2209 args
[OPERAND_A_VALUE
] >= -1000000 && args
[OPERAND_A_VALUE
] <= 1000000 &&
2210 args
[OPERAND_B_TYPE
] >= 0 && args
[OPERAND_B_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2211 args
[OPERAND_B_VALUE
] >= -1000000 && args
[OPERAND_B_VALUE
] <= 1000000 &&
2212 args
[FLAGS
] >= 0 && args
[FLAGS
] <= 255
2215 logicConditionsMutable(i
)->enabled
= args
[ENABLED
];
2216 logicConditionsMutable(i
)->activatorId
= args
[ACTIVATOR_ID
];
2217 logicConditionsMutable(i
)->operation
= args
[OPERATION
];
2218 logicConditionsMutable(i
)->operandA
.type
= args
[OPERAND_A_TYPE
];
2219 logicConditionsMutable(i
)->operandA
.value
= args
[OPERAND_A_VALUE
];
2220 logicConditionsMutable(i
)->operandB
.type
= args
[OPERAND_B_TYPE
];
2221 logicConditionsMutable(i
)->operandB
.value
= args
[OPERAND_B_VALUE
];
2222 logicConditionsMutable(i
)->flags
= args
[FLAGS
];
2224 processCliLogic("", i
);
2226 cliShowParseError();
2231 static void cliLogic(char *cmdline
) {
2232 processCliLogic(cmdline
, -1);
2235 static void printGvar(uint8_t dumpMask
, const globalVariableConfig_t
*gvars
, const globalVariableConfig_t
*defaultGvars
)
2237 const char *format
= "gvar %d %d %d %d";
2238 for (uint32_t i
= 0; i
< MAX_GLOBAL_VARIABLES
; i
++) {
2239 const globalVariableConfig_t gvar
= gvars
[i
];
2241 bool equalsDefault
= false;
2243 globalVariableConfig_t defaultValue
= defaultGvars
[i
];
2245 gvar
.defaultValue
== defaultValue
.defaultValue
&&
2246 gvar
.min
== defaultValue
.min
&&
2247 gvar
.max
== defaultValue
.max
;
2249 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2256 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2265 static void cliGvar(char *cmdline
) {
2267 int args
[4], check
= 0;
2268 uint8_t len
= strlen(cmdline
);
2271 printGvar(DUMP_MASTER
, globalVariableConfigs(0), NULL
);
2272 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2273 pgResetCopy(globalVariableConfigsMutable(0), PG_GLOBAL_VARIABLE_CONFIG
);
2282 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2283 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2284 args
[check
++] = fastA2I(ptr
);
2285 ptr
= strtok_r(NULL
, " ", &saveptr
);
2288 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2289 cliShowParseError();
2293 int32_t i
= args
[INDEX
];
2295 i
>= 0 && i
< MAX_GLOBAL_VARIABLES
&&
2296 args
[DEFAULT
] >= INT32_MIN
&& args
[DEFAULT
] <= INT32_MAX
&&
2297 args
[MIN
] >= INT32_MIN
&& args
[MIN
] <= INT32_MAX
&&
2298 args
[MAX
] >= INT32_MIN
&& args
[MAX
] <= INT32_MAX
2300 globalVariableConfigsMutable(i
)->defaultValue
= args
[DEFAULT
];
2301 globalVariableConfigsMutable(i
)->min
= args
[MIN
];
2302 globalVariableConfigsMutable(i
)->max
= args
[MAX
];
2306 cliShowParseError();
2311 static void printPid(uint8_t dumpMask
, const programmingPid_t
*programmingPids
, const programmingPid_t
*defaultProgrammingPids
)
2313 const char *format
= "pid %d %d %d %d %d %d %d %d %d %d";
2314 for (uint32_t i
= 0; i
< MAX_PROGRAMMING_PID_COUNT
; i
++) {
2315 const programmingPid_t pid
= programmingPids
[i
];
2317 bool equalsDefault
= false;
2318 if (defaultProgrammingPids
) {
2319 programmingPid_t defaultValue
= defaultProgrammingPids
[i
];
2321 pid
.enabled
== defaultValue
.enabled
&&
2322 pid
.setpoint
.type
== defaultValue
.setpoint
.type
&&
2323 pid
.setpoint
.value
== defaultValue
.setpoint
.value
&&
2324 pid
.measurement
.type
== defaultValue
.measurement
.type
&&
2325 pid
.measurement
.value
== defaultValue
.measurement
.value
&&
2326 pid
.gains
.P
== defaultValue
.gains
.P
&&
2327 pid
.gains
.I
== defaultValue
.gains
.I
&&
2328 pid
.gains
.D
== defaultValue
.gains
.D
&&
2329 pid
.gains
.FF
== defaultValue
.gains
.FF
;
2331 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2336 pid
.measurement
.type
,
2337 pid
.measurement
.value
,
2344 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2349 pid
.measurement
.type
,
2350 pid
.measurement
.value
,
2359 static void cliPid(char *cmdline
) {
2361 int args
[10], check
= 0;
2362 uint8_t len
= strlen(cmdline
);
2365 printPid(DUMP_MASTER
, programmingPids(0), NULL
);
2366 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2367 pgResetCopy(programmingPidsMutable(0), PG_LOGIC_CONDITIONS
);
2382 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2383 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2384 args
[check
++] = fastA2I(ptr
);
2385 ptr
= strtok_r(NULL
, " ", &saveptr
);
2388 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2389 cliShowParseError();
2393 int32_t i
= args
[INDEX
];
2395 i
>= 0 && i
< MAX_PROGRAMMING_PID_COUNT
&&
2396 args
[ENABLED
] >= 0 && args
[ENABLED
] <= 1 &&
2397 args
[SETPOINT_TYPE
] >= 0 && args
[SETPOINT_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2398 args
[SETPOINT_VALUE
] >= -1000000 && args
[SETPOINT_VALUE
] <= 1000000 &&
2399 args
[MEASUREMENT_TYPE
] >= 0 && args
[MEASUREMENT_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2400 args
[MEASUREMENT_VALUE
] >= -1000000 && args
[MEASUREMENT_VALUE
] <= 1000000 &&
2401 args
[P_GAIN
] >= 0 && args
[P_GAIN
] <= INT16_MAX
&&
2402 args
[I_GAIN
] >= 0 && args
[I_GAIN
] <= INT16_MAX
&&
2403 args
[D_GAIN
] >= 0 && args
[D_GAIN
] <= INT16_MAX
&&
2404 args
[FF_GAIN
] >= 0 && args
[FF_GAIN
] <= INT16_MAX
2406 programmingPidsMutable(i
)->enabled
= args
[ENABLED
];
2407 programmingPidsMutable(i
)->setpoint
.type
= args
[SETPOINT_TYPE
];
2408 programmingPidsMutable(i
)->setpoint
.value
= args
[SETPOINT_VALUE
];
2409 programmingPidsMutable(i
)->measurement
.type
= args
[MEASUREMENT_TYPE
];
2410 programmingPidsMutable(i
)->measurement
.value
= args
[MEASUREMENT_VALUE
];
2411 programmingPidsMutable(i
)->gains
.P
= args
[P_GAIN
];
2412 programmingPidsMutable(i
)->gains
.I
= args
[I_GAIN
];
2413 programmingPidsMutable(i
)->gains
.D
= args
[D_GAIN
];
2414 programmingPidsMutable(i
)->gains
.FF
= args
[FF_GAIN
];
2418 cliShowParseError();
2423 static void printOsdCustomElements(uint8_t dumpMask
, const osdCustomElement_t
*osdCustomElements
, const osdCustomElement_t
*defaultosdCustomElements
)
2425 const char *format
= "osd_custom_elements %d %d %d %d %d %d %d %d %d \"%s\"";
2427 if(CUSTOM_ELEMENTS_PARTS
!= 3)
2429 cliPrintHashLine("Incompatible count of elements for custom OSD elements");
2432 for (uint8_t i
= 0; i
< MAX_CUSTOM_ELEMENTS
; i
++) {
2433 bool equalsDefault
= false;
2435 const osdCustomElement_t osdCustomElement
= osdCustomElements
[i
];
2436 if(defaultosdCustomElements
){
2437 const osdCustomElement_t defaultValue
= defaultosdCustomElements
[i
];
2439 osdCustomElement
.part
[0].type
== defaultValue
.part
[0].type
&&
2440 osdCustomElement
.part
[0].value
== defaultValue
.part
[0].value
&&
2441 osdCustomElement
.part
[1].type
== defaultValue
.part
[1].type
&&
2442 osdCustomElement
.part
[1].value
== defaultValue
.part
[1].value
&&
2443 osdCustomElement
.part
[2].type
== defaultValue
.part
[2].type
&&
2444 osdCustomElement
.part
[2].value
== defaultValue
.part
[2].value
&&
2445 osdCustomElement
.visibility
.type
== defaultValue
.visibility
.type
&&
2446 osdCustomElement
.visibility
.value
== defaultValue
.visibility
.value
&&
2447 strcmp(osdCustomElement
.osdCustomElementText
, defaultValue
.osdCustomElementText
) == 0;
2449 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2451 osdCustomElement
.part
[0].type
,
2452 osdCustomElement
.part
[0].value
,
2453 osdCustomElement
.part
[1].type
,
2454 osdCustomElement
.part
[1].value
,
2455 osdCustomElement
.part
[2].type
,
2456 osdCustomElement
.part
[2].value
,
2457 osdCustomElement
.visibility
.type
,
2458 osdCustomElement
.visibility
.value
,
2459 osdCustomElement
.osdCustomElementText
2463 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2465 osdCustomElement
.part
[0].type
,
2466 osdCustomElement
.part
[0].value
,
2467 osdCustomElement
.part
[1].type
,
2468 osdCustomElement
.part
[1].value
,
2469 osdCustomElement
.part
[2].type
,
2470 osdCustomElement
.part
[2].value
,
2471 osdCustomElement
.visibility
.type
,
2472 osdCustomElement
.visibility
.value
,
2473 osdCustomElement
.osdCustomElementText
2478 static void osdCustom(char *cmdline
){
2480 char * saveptrParams
;
2481 int args
[10], check
= 0;
2482 char text
[OSD_CUSTOM_ELEMENT_TEXT_SIZE
];
2483 uint8_t len
= strlen(cmdline
);
2486 printOsdCustomElements(DUMP_MASTER
, osdCustomElements(0), NULL
);
2488 //split by ", first are params second is text
2489 char *ptrMain
= strtok_r(cmdline
, "\"", &saveptrMain
);
2502 char *ptrParams
= strtok_r(ptrMain
, " ", &saveptrParams
);
2503 while (ptrParams
!= NULL
&& check
< ARGS_COUNT
) {
2504 args
[check
++] = fastA2I(ptrParams
);
2505 ptrParams
= strtok_r(NULL
, " ", &saveptrParams
);
2508 if (check
!= ARGS_COUNT
) {
2509 cliShowParseError();
2514 char *ptrText
= strtok_r(NULL
, "\"", &saveptrMain
);
2515 size_t copySize
= 0;
2516 if(ptrText
!= NULL
){
2517 copySize
= MIN(strlen(ptrText
), (size_t)(sizeof(text
) - 1));
2519 memcpy(text
, ptrText
, copySize
);
2522 text
[copySize
] = '\0';
2524 int32_t i
= args
[INDEX
];
2526 i
>= 0 && i
< MAX_CUSTOM_ELEMENTS
&&
2527 args
[PART0_TYPE
] >= 0 && args
[PART0_TYPE
] <= 7 &&
2528 args
[PART0_VALUE
] >= 0 && args
[PART0_VALUE
] <= UINT8_MAX
&&
2529 args
[PART1_TYPE
] >= 0 && args
[PART1_TYPE
] <= 7 &&
2530 args
[PART1_VALUE
] >= 0 && args
[PART1_VALUE
] <= UINT8_MAX
&&
2531 args
[PART2_TYPE
] >= 0 && args
[PART2_TYPE
] <= 7 &&
2532 args
[PART2_VALUE
] >= 0 && args
[PART2_VALUE
] <= UINT8_MAX
&&
2533 args
[VISIBILITY_TYPE
] >= 0 && args
[VISIBILITY_TYPE
] <= 2 &&
2534 args
[VISIBILITY_VALUE
] >= 0 && args
[VISIBILITY_VALUE
] <= UINT8_MAX
2536 osdCustomElementsMutable(i
)->part
[0].type
= args
[PART0_TYPE
];
2537 osdCustomElementsMutable(i
)->part
[0].value
= args
[PART0_VALUE
];
2538 osdCustomElementsMutable(i
)->part
[1].type
= args
[PART1_TYPE
];
2539 osdCustomElementsMutable(i
)->part
[1].value
= args
[PART1_VALUE
];
2540 osdCustomElementsMutable(i
)->part
[2].type
= args
[PART2_TYPE
];
2541 osdCustomElementsMutable(i
)->part
[2].value
= args
[PART2_VALUE
];
2542 osdCustomElementsMutable(i
)->visibility
.type
= args
[VISIBILITY_TYPE
];
2543 osdCustomElementsMutable(i
)->visibility
.value
= args
[VISIBILITY_VALUE
];
2544 memcpy(osdCustomElementsMutable(i
)->osdCustomElementText
, text
, OSD_CUSTOM_ELEMENT_TEXT_SIZE
);
2548 cliShowParseError();
2558 static void cliWriteBytes(const uint8_t *buffer
, int count
)
2567 static void cliSdInfo(char *cmdline
)
2571 cliPrint("SD card: ");
2573 if (!sdcard_isInserted()) {
2574 cliPrintLine("None inserted");
2578 if (!sdcard_isInitialized()) {
2579 cliPrintLine("Startup failed");
2583 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
2585 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2586 metadata
->manufacturerID
,
2587 metadata
->numBlocks
/ 2, /* One block is half a kB */
2588 metadata
->productionMonth
,
2589 metadata
->productionYear
,
2590 metadata
->productRevisionMajor
,
2591 metadata
->productRevisionMinor
2594 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
2596 cliPrint("'\r\n" "Filesystem: ");
2598 switch (afatfs_getFilesystemState()) {
2599 case AFATFS_FILESYSTEM_STATE_READY
:
2602 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
2603 cliPrint("Initializing");
2605 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
2606 case AFATFS_FILESYSTEM_STATE_FATAL
:
2609 switch (afatfs_getLastError()) {
2610 case AFATFS_ERROR_BAD_MBR
:
2611 cliPrint(" - no FAT MBR partitions");
2613 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
2614 cliPrint(" - bad FAT header");
2616 case AFATFS_ERROR_GENERIC
:
2617 case AFATFS_ERROR_NONE
:
2618 ; // Nothing more detailed to print
2630 static void cliFlashInfo(char *cmdline
)
2634 const flashGeometry_t
*layout
= flashGetGeometry();
2636 if (layout
->totalSize
== 0) {
2637 cliPrintLine("Flash not available");
2641 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2642 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
);
2644 for (uint8_t index
= 0; index
< FLASH_MAX_PARTITIONS
; index
++) {
2645 const flashPartition_t
*partition
;
2647 cliPrintLine("Paritions:");
2649 partition
= flashPartitionFindByIndex(index
);
2653 cliPrintLinef(" %d: %s %u %u", index
, flashPartitionGetTypeName(partition
->type
), partition
->startSector
, partition
->endSector
);
2656 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
2658 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2659 FLASH_PARTITION_SECTOR_COUNT(flashPartition
) * layout
->sectorSize
,
2665 static void cliFlashErase(char *cmdline
)
2669 const flashGeometry_t
*layout
= flashGetGeometry();
2671 if (layout
->totalSize
== 0) {
2672 cliPrintLine("Flash not available");
2676 cliPrintLine("Erasing...");
2677 flashfsEraseCompletely();
2679 while (!flashIsReady()) {
2683 cliPrintLine("Done.");
2686 #ifdef USE_FLASH_TOOLS
2688 static void cliFlashWrite(char *cmdline
)
2690 const uint32_t address
= fastA2I(cmdline
);
2691 const char *text
= strchr(cmdline
, ' ');
2694 cliShowParseError();
2696 flashfsSeekAbs(address
);
2697 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2700 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2704 static void cliFlashRead(char *cmdline
)
2706 uint32_t address
= fastA2I(cmdline
);
2708 const char *nextArg
= strchr(cmdline
, ' ');
2711 cliShowParseError();
2713 uint32_t length
= fastA2I(nextArg
);
2715 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2718 while (length
> 0) {
2719 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2721 for (int i
= 0; i
< bytesRead
; i
++) {
2722 cliWrite(buffer
[i
]);
2725 length
-= bytesRead
;
2726 address
+= bytesRead
;
2728 if (bytesRead
== 0) {
2729 //Assume we reached the end of the volume or something fatal happened
2741 static void printOsdLayout(uint8_t dumpMask
, const osdLayoutsConfig_t
*config
, const osdLayoutsConfig_t
*configDefault
, int layout
, int item
)
2743 // "<layout> <item> <col> <row> <visible>"
2744 const char *format
= "osd_layout %d %d %d %d %c";
2745 for (int ii
= 0; ii
< OSD_LAYOUT_COUNT
; ii
++) {
2746 if (layout
>= 0 && layout
!= ii
) {
2749 const uint16_t *layoutItems
= config
->item_pos
[ii
];
2750 const uint16_t *defaultLayoutItems
= configDefault
->item_pos
[ii
];
2751 for (int jj
= 0; jj
< OSD_ITEM_COUNT
; jj
++) {
2752 if (item
>= 0 && item
!= jj
) {
2755 bool equalsDefault
= layoutItems
[jj
] == defaultLayoutItems
[jj
];
2756 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2758 OSD_X(defaultLayoutItems
[jj
]),
2759 OSD_Y(defaultLayoutItems
[jj
]),
2760 OSD_VISIBLE(defaultLayoutItems
[jj
]) ? 'V' : 'H');
2762 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2764 OSD_X(layoutItems
[jj
]),
2765 OSD_Y(layoutItems
[jj
]),
2766 OSD_VISIBLE(layoutItems
[jj
]) ? 'V' : 'H');
2771 static void cliOsdLayout(char *cmdline
)
2779 bool visible
= false;
2780 char *tok
= strtok_r(cmdline
, " ", &saveptr
);
2784 for (ii
= 0; tok
!= NULL
; ii
++, tok
= strtok_r(NULL
, " ", &saveptr
)) {
2787 layout
= fastA2I(tok
);
2788 if (layout
< 0 || layout
>= OSD_LAYOUT_COUNT
) {
2789 cliShowParseError();
2794 item
= fastA2I(tok
);
2795 if (item
< 0 || item
>= OSD_ITEM_COUNT
) {
2796 cliShowParseError();
2802 if (col
< 0 || col
> OSD_X(OSD_POS_MAX
)) {
2803 cliShowParseError();
2809 if (row
< 0 || row
> OSD_Y(OSD_POS_MAX
)) {
2810 cliShowParseError();
2823 cliShowParseError();
2828 cliShowParseError();
2839 // No args, or just layout or layout and item. If any of them not provided,
2840 // it will be the -1 that we used during initialization, so printOsdLayout()
2841 // won't use them for filtering.
2842 printOsdLayout(DUMP_MASTER
, osdLayoutsConfig(), osdLayoutsConfig(), layout
, item
);
2845 // No visibility provided. Keep the previous one.
2846 visible
= OSD_VISIBLE(osdLayoutsConfig()->item_pos
[layout
][item
]);
2849 // Layout, item, pos and visibility. Set the item.
2850 osdLayoutsConfigMutable()->item_pos
[layout
][item
] = OSD_POS(col
, row
) | (visible
? OSD_VISIBLE_FLAG
: 0);
2854 cliShowParseError();
2861 static void printTimerOutputModes(dumpFlags_e dumpFlags
, const timerOverride_t
* to
, const timerOverride_t
* defaultTimerOverride
, int timer
)
2863 const char *format
= "timer_output_mode %d %s";
2865 for (int i
= 0; i
< HARDWARE_TIMER_DEFINITION_COUNT
; ++i
) {
2866 if (timer
< 0 || timer
== i
) {
2867 outputMode_e mode
= to
[i
].outputMode
;
2868 bool equalsDefault
= false;
2869 if(defaultTimerOverride
) {
2870 outputMode_e defaultMode
= defaultTimerOverride
[i
].outputMode
;
2871 equalsDefault
= mode
== defaultMode
;
2872 cliDefaultPrintLinef(dumpFlags
, equalsDefault
, format
, i
, outputModeNames
[defaultMode
]);
2874 cliDumpPrintLinef(dumpFlags
, equalsDefault
, format
, i
, outputModeNames
[mode
]);
2879 static void cliTimerOutputMode(char *cmdline
)
2885 char *tok
= strtok_r(cmdline
, " ", &saveptr
);
2889 for (ii
= 0; tok
!= NULL
; ii
++, tok
= strtok_r(NULL
, " ", &saveptr
)) {
2892 timer
= fastA2I(tok
);
2893 if (timer
< 0 || timer
>= HARDWARE_TIMER_DEFINITION_COUNT
) {
2894 cliShowParseError();
2899 if(!sl_strcasecmp("AUTO", tok
)) {
2900 mode
= OUTPUT_MODE_AUTO
;
2901 } else if(!sl_strcasecmp("MOTORS", tok
)) {
2902 mode
= OUTPUT_MODE_MOTORS
;
2903 } else if(!sl_strcasecmp("SERVOS", tok
)) {
2904 mode
= OUTPUT_MODE_SERVOS
;
2905 } else if(!sl_strcasecmp("LED", tok
)) {
2906 mode
= OUTPUT_MODE_LED
;
2908 cliShowParseError();
2913 cliShowParseError();
2922 // No args, or just timer. If any of them not provided,
2923 // it will be the -1 that we used during initialization, so printOsdLayout()
2924 // won't use them for filtering.
2925 printTimerOutputModes(DUMP_MASTER
, timerOverrides(0), NULL
, timer
);
2928 timerOverridesMutable(timer
)->outputMode
= mode
;
2929 printTimerOutputModes(DUMP_MASTER
, timerOverrides(0), NULL
, timer
);
2933 cliShowParseError();
2939 static void printFeature(uint8_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
)
2941 uint32_t mask
= featureConfig
->enabledFeatures
;
2942 uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
2943 for (uint32_t i
= 0; ; i
++) { // disable all feature first
2944 if (featureNames
[i
] == NULL
)
2946 if (featureNames
[i
][0] == '\0')
2948 const char *format
= "feature -%s";
2949 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2950 cliDumpPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2952 for (uint32_t i
= 0; ; i
++) { // reenable what we want.
2953 if (featureNames
[i
] == NULL
)
2955 if (featureNames
[i
][0] == '\0')
2957 const char *format
= "feature %s";
2958 if (defaultMask
& (1 << i
)) {
2959 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2961 if (mask
& (1 << i
)) {
2962 cliDumpPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2967 static void cliFeature(char *cmdline
)
2969 uint32_t len
= strlen(cmdline
);
2970 uint32_t mask
= featureMask();
2973 cliPrint("Enabled: ");
2974 for (uint32_t i
= 0; ; i
++) {
2975 if (featureNames
[i
] == NULL
)
2977 if (featureNames
[i
][0] == '\0')
2979 if (mask
& (1 << i
))
2980 cliPrintf("%s ", featureNames
[i
]);
2983 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
2984 cliPrint("Available: ");
2985 for (uint32_t i
= 0; ; i
++) {
2986 if (featureNames
[i
] == NULL
)
2988 if (featureNames
[i
][0] == '\0')
2990 cliPrintf("%s ", featureNames
[i
]);
2995 bool remove
= false;
2996 if (cmdline
[0] == '-') {
2999 cmdline
++; // skip over -
3003 for (uint32_t i
= 0; ; i
++) {
3004 if (featureNames
[i
] == NULL
) {
3005 cliPrintErrorLine("Invalid name");
3009 if (sl_strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
3013 if (mask
& FEATURE_GPS
) {
3014 cliPrintErrorLine("unavailable");
3020 cliPrint("Disabled");
3023 cliPrint("Enabled");
3025 cliPrintLinef(" %s", featureNames
[i
]);
3033 static void printBlackbox(uint8_t dumpMask
, const blackboxConfig_t
*config
, const blackboxConfig_t
*configDefault
)
3036 UNUSED(configDefault
);
3038 uint32_t mask
= config
->includeFlags
;
3040 for (uint8_t i
= 0; ; i
++) { // reenable what we want.
3041 if (blackboxIncludeFlagNames
[i
] == NULL
) {
3045 const char *formatOn
= "blackbox %s";
3046 const char *formatOff
= "blackbox -%s";
3048 if (mask
& (1 << i
)) {
3049 cliDumpPrintLinef(dumpMask
, false, formatOn
, blackboxIncludeFlagNames
[i
]);
3050 cliDefaultPrintLinef(dumpMask
, false, formatOn
, blackboxIncludeFlagNames
[i
]);
3052 cliDumpPrintLinef(dumpMask
, false, formatOff
, blackboxIncludeFlagNames
[i
]);
3053 cliDefaultPrintLinef(dumpMask
, false, formatOff
, blackboxIncludeFlagNames
[i
]);
3059 static void cliBlackbox(char *cmdline
)
3061 uint32_t len
= strlen(cmdline
);
3062 uint32_t mask
= blackboxConfig()->includeFlags
;
3065 cliPrint("Enabled: ");
3066 for (uint8_t i
= 0; ; i
++) {
3067 if (blackboxIncludeFlagNames
[i
] == NULL
) {
3071 if (mask
& (1 << i
))
3072 cliPrintf("%s ", blackboxIncludeFlagNames
[i
]);
3075 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
3076 cliPrint("Available: ");
3077 for (uint32_t i
= 0; ; i
++) {
3078 if (blackboxIncludeFlagNames
[i
] == NULL
) {
3082 cliPrintf("%s ", blackboxIncludeFlagNames
[i
]);
3087 bool remove
= false;
3088 if (cmdline
[0] == '-') {
3091 cmdline
++; // skip over -
3095 for (uint32_t i
= 0; ; i
++) {
3096 if (blackboxIncludeFlagNames
[i
] == NULL
) {
3097 cliPrintErrorLine("Invalid name");
3101 if (sl_strncasecmp(cmdline
, blackboxIncludeFlagNames
[i
], len
) == 0) {
3106 blackboxIncludeFlagClear(mask
);
3107 cliPrint("Disabled");
3109 blackboxIncludeFlagSet(mask
);
3110 cliPrint("Enabled");
3112 cliPrintLinef(" %s", blackboxIncludeFlagNames
[i
]);
3120 #if defined(BEEPER) || defined(USE_DSHOT)
3121 static void printBeeper(uint8_t dumpMask
, const beeperConfig_t
*beeperConfig
, const beeperConfig_t
*beeperConfigDefault
)
3123 const uint8_t beeperCount
= beeperTableEntryCount();
3124 const uint32_t mask
= beeperConfig
->beeper_off_flags
;
3125 const uint32_t defaultMask
= beeperConfigDefault
->beeper_off_flags
;
3126 for (int i
= 0; i
< beeperCount
- 2; i
++) {
3127 const char *formatOff
= "beeper -%s";
3128 const char *formatOn
= "beeper %s";
3129 cliDefaultPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOn
: formatOff
, beeperNameForTableIndex(i
));
3130 cliDumpPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOff
: formatOn
, beeperNameForTableIndex(i
));
3134 static void cliBeeper(char *cmdline
)
3136 uint32_t len
= strlen(cmdline
);
3137 uint8_t beeperCount
= beeperTableEntryCount();
3138 uint32_t mask
= getBeeperOffMask();
3141 cliPrintf("Disabled:");
3142 for (int32_t i
= 0; ; i
++) {
3143 if (i
== beeperCount
- 2){
3148 if (mask
& (1 << (beeperModeForTableIndex(i
) - 1)))
3149 cliPrintf(" %s", beeperNameForTableIndex(i
));
3152 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
3153 cliPrint("Available:");
3154 for (uint32_t i
= 0; i
< beeperCount
; i
++)
3155 cliPrintf(" %s", beeperNameForTableIndex(i
));
3159 bool remove
= false;
3160 if (cmdline
[0] == '-') {
3161 remove
= true; // this is for beeper OFF condition
3166 for (uint32_t i
= 0; ; i
++) {
3167 if (i
== beeperCount
) {
3168 cliPrintErrorLine("Invalid name");
3171 if (sl_strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0) {
3172 if (remove
) { // beeper off
3173 if (i
== BEEPER_ALL
-1)
3174 beeperOffSetAll(beeperCount
-2);
3176 if (i
== BEEPER_PREFERENCE
-1)
3177 setBeeperOffMask(getPreferredBeeperOffMask());
3179 mask
= 1 << (beeperModeForTableIndex(i
) - 1);
3182 cliPrint("Disabled");
3185 if (i
== BEEPER_ALL
-1)
3186 beeperOffClearAll();
3188 if (i
== BEEPER_PREFERENCE
-1)
3189 setPreferredBeeperOffMask(getBeeperOffMask());
3191 mask
= 1 << (beeperModeForTableIndex(i
) - 1);
3192 beeperOffClear(mask
);
3194 cliPrint("Enabled");
3196 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
3204 static void printMap(uint8_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
)
3206 bool equalsDefault
= true;
3208 char bufDefault
[16];
3211 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
3212 buf
[i
] = bufDefault
[i
] = 0;
3215 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
3216 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3217 if (defaultRxConfig
) {
3218 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3219 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
3224 const char *formatMap
= "map %s";
3225 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
3226 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
3229 static void cliMap(char *cmdline
)
3232 char out
[MAX_MAPPABLE_RX_INPUTS
+ 1];
3234 len
= strlen(cmdline
);
3236 if (len
== MAX_MAPPABLE_RX_INPUTS
) {
3238 for (uint32_t i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
3239 cmdline
[i
] = sl_toupper((unsigned char)cmdline
[i
]);
3241 for (uint32_t i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
3242 if (strchr(rcChannelLetters
, cmdline
[i
]) && !strchr(cmdline
+ i
+ 1, cmdline
[i
])) {
3245 cliShowParseError();
3248 parseRcChannels(cmdline
);
3249 } else if (len
!= 0) {
3250 cliShowParseError();
3254 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++){
3255 out
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
3258 cliPrintLinef("%s", out
);
3261 static const char *checkCommand(const char *cmdLine
, const char *command
)
3263 if (!sl_strncasecmp(cmdLine
, command
, strlen(command
)) // command names match
3264 && !sl_isalnum((unsigned)cmdLine
[strlen(command
)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
3265 return cmdLine
+ strlen(command
) + 1;
3271 static void cliRebootEx(bool bootLoader
)
3273 cliPrint("\r\nRebooting");
3274 bufWriterFlush(cliWriter
);
3275 waitForSerialPortToFinishTransmitting(cliPort
);
3277 fcReboot(bootLoader
);
3280 static void cliReboot(void)
3285 static void cliDfu(char *cmdline
)
3288 #ifndef CLI_MINIMAL_VERBOSITY
3289 cliPrint("\r\nRestarting in DFU mode");
3294 #if defined (USE_SERIALRX_SRXL2)
3295 void cliRxBind(char *cmdline
){
3297 if (rxConfig()->receiverType
== RX_TYPE_SERIAL
) {
3298 switch (rxConfig()->serialrx_provider
) {
3300 cliPrint("Not supported.");
3302 #if defined(USE_SERIALRX_SRXL2)
3303 case SERIALRX_SRXL2
:
3305 cliPrint("Binding SRXL2 receiver...");
3308 #if defined(USE_SERIALRX_CRSF)
3311 cliPrint("Binding CRSF receiver...");
3319 static void cliExit(char *cmdline
)
3323 #ifndef CLI_MINIMAL_VERBOSITY
3324 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
3326 bufWriterFlush(cliWriter
);
3331 // incase a motor was left running during motortest, clear it here
3332 mixerResetDisarmedMotors();
3339 static void cliGpsPassthrough(char *cmdline
)
3343 gpsEnablePassthrough(cliPort
);
3347 static void cliMotor(char *cmdline
)
3349 int motor_index
= 0;
3350 int motor_value
= 0;
3355 if (isEmpty(cmdline
)) {
3356 cliShowParseError();
3361 pch
= strtok_r(cmdline
, " ", &saveptr
);
3362 while (pch
!= NULL
) {
3365 motor_index
= fastA2I(pch
);
3368 motor_value
= fastA2I(pch
);
3372 pch
= strtok_r(NULL
, " ", &saveptr
);
3375 if (motor_index
< 0 || motor_index
>= MAX_SUPPORTED_MOTORS
) {
3376 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
3381 if (motor_value
< PWM_RANGE_MIN
|| motor_value
> PWM_RANGE_MAX
) {
3382 cliShowArgumentRangeError("value", 1000, 2000);
3385 motor_disarmed
[motor_index
] = motor_value
;
3389 cliPrintLinef("motor %d: %d", motor_index
, motor_disarmed
[motor_index
]);
3392 static void cliPlaySound(char *cmdline
)
3396 static int lastSoundIdx
= -1;
3398 if (isEmpty(cmdline
)) {
3399 i
= lastSoundIdx
+ 1; //next sound index
3400 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3401 while (true) { //no name for index; try next one
3402 if (++i
>= beeperTableEntryCount())
3403 i
= 0; //if end then wrap around to first entry
3404 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
3405 break; //if name OK then play sound below
3406 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
3407 cliPrintLine("Error playing sound");
3412 } else { //index value was given
3413 i
= fastA2I(cmdline
);
3414 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3415 cliPrintLinef("No sound for index %d", i
);
3421 cliPrintLinef("Playing sound %d: %s", i
, name
);
3422 beeper(beeperModeForTableIndex(i
));
3425 static void cliControlProfile(char *cmdline
)
3427 // CLI profile index is 1-based
3428 if (isEmpty(cmdline
)) {
3429 cliPrintLinef("control_profile %d", getConfigProfile() + 1);
3432 const int i
= fastA2I(cmdline
) - 1;
3433 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
3434 setConfigProfileAndWriteEEPROM(i
);
3435 cliControlProfile("");
3440 static void cliDumpControlProfile(uint8_t profileIndex
, uint8_t dumpMask
)
3442 if (profileIndex
>= MAX_PROFILE_COUNT
) {
3446 setConfigProfile(profileIndex
);
3447 cliPrintHashLine("control_profile");
3448 cliPrintLinef("control_profile %d\r\n", getConfigProfile() + 1);
3449 dumpAllValues(PROFILE_VALUE
, dumpMask
);
3450 dumpAllValues(CONTROL_RATE_VALUE
, dumpMask
);
3451 dumpAllValues(EZ_TUNE_VALUE
, dumpMask
);
3454 static void cliBatteryProfile(char *cmdline
)
3456 // CLI profile index is 1-based
3457 if (isEmpty(cmdline
)) {
3458 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
3461 const int i
= fastA2I(cmdline
) - 1;
3462 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
3463 setConfigBatteryProfileAndWriteEEPROM(i
);
3464 cliBatteryProfile("");
3469 static void cliDumpBatteryProfile(uint8_t profileIndex
, uint8_t dumpMask
)
3471 if (profileIndex
>= MAX_BATTERY_PROFILE_COUNT
) {
3475 setConfigBatteryProfile(profileIndex
);
3476 cliPrintHashLine("battery_profile");
3477 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
3478 dumpAllValues(BATTERY_CONFIG_VALUE
, dumpMask
);
3481 static void cliMixerProfile(char *cmdline
)
3483 // CLI profile index is 1-based
3484 if (isEmpty(cmdline
)) {
3485 cliPrintLinef("mixer_profile %d", getConfigMixerProfile() + 1);
3488 const int i
= fastA2I(cmdline
) - 1;
3489 if (i
>= 0 && i
< MAX_MIXER_PROFILE_COUNT
) {
3490 setConfigMixerProfileAndWriteEEPROM(i
);
3491 cliMixerProfile("");
3496 static void cliDumpMixerProfile(uint8_t profileIndex
, uint8_t dumpMask
)
3498 if (profileIndex
>= MAX_MIXER_PROFILE_COUNT
) {
3502 setConfigMixerProfile(profileIndex
);
3503 cliPrintHashLine("mixer_profile");
3504 cliPrintLinef("mixer_profile %d\r\n", getConfigMixerProfile() + 1);
3505 dumpAllValues(MIXER_CONFIG_VALUE
, dumpMask
);
3506 cliPrintHashLine("Mixer: motor mixer");
3507 cliDumpPrintLinef(dumpMask
, primaryMotorMixer_CopyArray()[0].throttle
== 0.0f
, "\r\nmmix reset\r\n");
3508 printMotorMix(dumpMask
, primaryMotorMixer_CopyArray(), primaryMotorMixer(0));
3509 cliPrintHashLine("Mixer: servo mixer");
3510 cliDumpPrintLinef(dumpMask
, customServoMixers_CopyArray()[0].rate
== 0, "smix reset\r\n");
3511 printServoMix(dumpMask
, customServoMixers_CopyArray(), customServoMixers(0));
3514 #ifdef USE_CLI_BATCH
3515 static void cliPrintCommandBatchWarning(const char *warning
)
3518 tfp_sprintf(errorBuf
, "%d ERRORS WERE DETECTED - Please review and fix before continuing!", commandBatchErrorCount
);
3520 cliPrintErrorLinef(errorBuf
);
3522 cliPrintErrorLinef(warning
);
3526 static void resetCommandBatch(void)
3528 commandBatchActive
= false;
3529 commandBatchError
= false;
3530 commandBatchErrorCount
= 0;
3533 static void cliBatch(char *cmdline
)
3535 if (strncasecmp(cmdline
, "start", 5) == 0) {
3536 if (!commandBatchActive
) {
3537 commandBatchActive
= true;
3538 commandBatchError
= false;
3539 commandBatchErrorCount
= 0;
3541 cliPrintLine("Command batch started");
3542 } else if (strncasecmp(cmdline
, "end", 3) == 0) {
3543 if (commandBatchActive
&& commandBatchError
) {
3544 cliPrintCommandBatchWarning(NULL
);
3546 cliPrintLine("Command batch ended");
3548 resetCommandBatch();
3550 cliPrintErrorLinef("Invalid option");
3555 static void cliSave(char *cmdline
)
3559 #ifdef USE_CLI_BATCH
3560 if (commandBatchActive
&& commandBatchError
) {
3561 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3562 resetCommandBatch();
3568 //copyCurrentProfileToProfileSlot(getConfigProfile();
3575 static void cliDefaults(char *cmdline
)
3579 cliPrint("Resetting to defaults");
3585 #ifdef USE_CLI_BATCH
3586 commandBatchError
= false;
3589 if (!checkCommand(cmdline
, "noreboot"))
3593 static void cliGet(char *cmdline
)
3595 const setting_t
*val
;
3596 int matchedCommands
= 0;
3597 char name
[SETTING_MAX_NAME_LENGTH
];
3599 while(*cmdline
== ' ') ++cmdline
; // ignore spaces
3601 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3602 val
= settingGet(i
);
3603 if (settingNameContains(val
, name
, cmdline
)) {
3604 cliPrintf("%s = ", name
);
3605 if (strcmp(name
, "name") == 0) {
3606 // if the craftname has a leading space, then enclose the name in quotes
3607 const char * v
= (const char *)settingGetValuePointer(val
);
3608 cliPrintf(v
[0] == ' ' ? "\"%s\"" : "%s", v
);
3610 cliPrintVar(val
, 0);
3613 cliPrintVarRange(val
);
3621 if (matchedCommands
) {
3625 cliPrintErrorLine("Invalid name");
3628 static void cliSet(char *cmdline
)
3631 const setting_t
*val
;
3633 char name
[SETTING_MAX_NAME_LENGTH
];
3635 while(*cmdline
== ' ') ++cmdline
; // ignore spaces
3637 len
= strlen(cmdline
);
3639 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
3640 cliPrintLine("Current settings:");
3641 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3642 val
= settingGet(i
);
3643 settingGetName(val
, name
);
3644 cliPrintf("%s = ", name
);
3645 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3648 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
3651 char *lastNonSpaceCharacter
= eqptr
;
3652 while (*(lastNonSpaceCharacter
- 1) == ' ') {
3653 lastNonSpaceCharacter
--;
3655 uint8_t variableNameLength
= lastNonSpaceCharacter
- cmdline
;
3657 // skip the '=' and any ' ' characters
3659 while (*(eqptr
) == ' ') {
3663 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3664 val
= settingGet(i
);
3665 // ensure exact match when setting to prevent setting variables with shorter names
3666 if (settingNameIsExactMatch(val
, name
, cmdline
, variableNameLength
)) {
3667 const setting_type_e type
= SETTING_TYPE(val
);
3668 if (type
== VAR_STRING
) {
3669 // Convert strings to uppercase. Lower case is not supported by the OSD.
3670 sl_toupperptr(eqptr
);
3671 // if setting the craftname, remove any quotes around the name. This allows leading spaces in the name
3672 if ((strcmp(name
, "name") == 0 || strcmp(name
, "pilot_name") == 0) && (eqptr
[0] == '"' && eqptr
[strlen(eqptr
)-1] == '"')) {
3673 settingSetString(val
, eqptr
+ 1, strlen(eqptr
)-2);
3675 settingSetString(val
, eqptr
, strlen(eqptr
));
3679 const setting_mode_e mode
= SETTING_MODE(val
);
3680 bool changeValue
= false;
3681 int_float_value_t tmp
= {0};
3684 if (*eqptr
!= 0 && strspn(eqptr
, "0123456789.+-") == strlen(eqptr
)) {
3685 float valuef
= fastA2F(eqptr
);
3686 // note: compare float values
3687 if (valuef
>= (float)settingGetMin(val
) && valuef
<= (float)settingGetMax(val
)) {
3689 if (type
== VAR_FLOAT
)
3690 tmp
.float_value
= valuef
;
3691 else if (type
== VAR_UINT32
)
3692 tmp
.uint_value
= fastA2UL(eqptr
);
3694 tmp
.int_value
= fastA2I(eqptr
);
3702 const lookupTableEntry_t
*tableEntry
= settingLookupTable(val
);
3703 bool matched
= false;
3704 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
3705 matched
= sl_strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
3708 tmp
.int_value
= tableValueIndex
;
3717 cliSetIntFloatVar(val
, tmp
);
3719 cliPrintf("%s set to ", name
);
3720 cliPrintVar(val
, 0);
3722 cliPrintError("Invalid value. ");
3723 cliPrintVarRange(val
);
3730 cliPrintErrorLine("Invalid name");
3732 // no equals, check for matching variables.
3737 static const char * getBatteryStateString(void)
3739 static const char * const batteryStateStrings
[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
3741 return batteryStateStrings
[getBatteryState()];
3744 static void cliStatus(char *cmdline
)
3748 char buf
[MAX(FORMATTED_DATE_TIME_BUFSIZE
, SETTING_MAX_NAME_LENGTH
)];
3751 cliPrintLinef("%s/%s %s %s / %s (%s) %s",
3760 cliPrintLinef("GCC-%s",
3763 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3764 rtcGetDateTime(&dt
);
3765 dateTimeFormatLocal(buf
, &dt
);
3766 cliPrintLinef("Current Time: %s", buf
);
3767 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
3768 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock
/ 1000000));
3770 const uint32_t detectedSensorsMask
= sensorsMask();
3772 for (int i
= 0; i
< SENSOR_INDEX_COUNT
; i
++) {
3774 const uint32_t mask
= (1 << i
);
3775 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
3776 const int sensorHardwareIndex
= detectedSensors
[i
];
3777 if (sensorHardwareNames
[i
]) {
3778 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
3779 cliPrintf(", %s=%s", sensorTypeNames
[i
], sensorHardware
);
3784 #if !defined(SITL_BUILD)
3785 #if defined(AT32F43x)
3786 cliPrintLine("AT32 system clocks:");
3787 crm_clocks_freq_type clocks
;
3788 crm_clocks_freq_get(&clocks
);
3790 cliPrintLinef(" SYSCLK = %d MHz", clocks
.sclk_freq
/ 1000000);
3791 cliPrintLinef(" ABH = %d MHz", clocks
.ahb_freq
/ 1000000);
3792 cliPrintLinef(" ABP1 = %d MHz", clocks
.apb1_freq
/ 1000000);
3793 cliPrintLinef(" ABP2 = %d MHz", clocks
.apb2_freq
/ 1000000);
3795 cliPrintLine("STM32 system clocks:");
3796 #if defined(USE_HAL_DRIVER)
3797 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
3798 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
3799 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
3800 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
3802 RCC_ClocksTypeDef clocks
;
3803 RCC_GetClocksFreq(&clocks
);
3804 cliPrintLinef(" SYSCLK = %d MHz", clocks
.SYSCLK_Frequency
/ 1000000);
3805 cliPrintLinef(" HCLK = %d MHz", clocks
.HCLK_Frequency
/ 1000000);
3806 cliPrintLinef(" PCLK1 = %d MHz", clocks
.PCLK1_Frequency
/ 1000000);
3807 cliPrintLinef(" PCLK2 = %d MHz", clocks
.PCLK2_Frequency
/ 1000000);
3809 #endif // for if at32
3812 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
3813 hardwareSensorStatusNames
[getHwGyroStatus()],
3814 hardwareSensorStatusNames
[getHwAccelerometerStatus()],
3815 hardwareSensorStatusNames
[getHwCompassStatus()],
3816 hardwareSensorStatusNames
[getHwBarometerStatus()],
3817 hardwareSensorStatusNames
[getHwRangefinderStatus()],
3818 hardwareSensorStatusNames
[getHwOpticalFlowStatus()],
3819 hardwareSensorStatusNames
[getHwGPSStatus()]
3822 #ifdef USE_ESC_SENSOR
3823 uint8_t motorCount
= getMotorCount();
3824 if (STATE(ESC_SENSOR_ENABLED
) && motorCount
> 0) {
3825 cliPrintLinef("ESC Temperature(s): Motor Count = %d", motorCount
);
3826 for (uint8_t i
= 0; i
< motorCount
; i
++) {
3827 const escSensorData_t
*escState
= getEscTelemetry(i
); //Get ESC telemetry
3828 cliPrintf("ESC %d: %d\260C, ", i
, escState
->temperature
);
3838 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
3839 #elif !defined(SITL_BUILD)
3840 const uint16_t i2cErrorCounter
= 0;
3844 cliPrintf("Stack used: %d, ", stackUsedSize());
3846 #if !defined(SITL_BUILD)
3847 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
3849 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter
, getEEPROMConfigSize(), &__config_end
- &__config_start
);
3851 #if defined(USE_ADC) && !defined(SITL_BUILD)
3852 static char * adcFunctions
[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
3853 cliPrintLine("ADC channel usage:");
3854 for (int i
= 0; i
< ADC_FUNCTION_COUNT
; i
++) {
3855 cliPrintf(" %8s :", adcFunctions
[i
]);
3857 cliPrint(" configured = ");
3858 if (adcChannelConfig()->adcFunctionChannel
[i
] == ADC_CHN_NONE
) {
3862 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel
[i
]);
3865 cliPrint(", used = ");
3866 if (adcGetFunctionChannelAllocation(i
) == ADC_CHN_NONE
) {
3867 cliPrintLine("none");
3870 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i
));
3875 cliPrintf("System load: %d", averageSystemLoadPercent
);
3876 const timeDelta_t pidTaskDeltaTime
= getTaskDeltaTime(TASK_PID
);
3877 const int pidRate
= pidTaskDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)pidTaskDeltaTime
));
3878 const int rxRate
= getTaskDeltaTime(TASK_RX
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_RX
)));
3879 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
3880 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime
, pidRate
, rxRate
, systemRate
);
3881 #if !defined(CLI_MINIMAL_VERBOSITY)
3882 cliPrint("Arming disabled flags:");
3883 uint32_t flags
= armingFlags
& ARMING_DISABLED_ALL_FLAGS
;
3885 int bitpos
= ffs(flags
) - 1;
3886 flags
&= ~(1 << bitpos
);
3887 if (bitpos
> 6) cliPrintf(" %s", armingDisableFlagNames
[bitpos
- 7]);
3890 if (armingFlags
& ARMING_DISABLED_INVALID_SETTING
) {
3891 unsigned invalidIndex
;
3892 if (!settingsValidate(&invalidIndex
)) {
3893 settingGetName(settingGet(invalidIndex
), buf
);
3894 cliPrintErrorLinef("Invalid setting: %s", buf
);
3898 #if defined(USE_OSD)
3899 if (armingFlags
& ARMING_DISABLED_NAVIGATION_UNSAFE
) {
3900 navArmingBlocker_e reason
= navigationIsBlockingArming(NULL
);
3901 if (reason
& NAV_ARMING_BLOCKER_JUMP_WAYPOINT_ERROR
)
3902 cliPrintLinef(" %s", OSD_MSG_JUMP_WP_MISCONFIG
);
3903 if (reason
& NAV_ARMING_BLOCKER_MISSING_GPS_FIX
) {
3904 cliPrintLinef(" %s", OSD_MSG_WAITING_GPS_FIX
);
3906 if (reason
& NAV_ARMING_BLOCKER_NAV_IS_ALREADY_ACTIVE
)
3907 cliPrintLinef(" %s", OSD_MSG_DISABLE_NAV_FIRST
);
3908 if (reason
& NAV_ARMING_BLOCKER_FIRST_WAYPOINT_TOO_FAR
)
3909 cliPrintLinef(" FIRST WP TOO FAR");
3916 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags
& ARMING_DISABLED_ALL_FLAGS
);
3919 #if !defined(CLI_MINIMAL_VERBOSITY)
3921 #if defined(USE_OSD)
3922 displayPort_t
*osdDisplayPort
= osdGetDisplayPort();
3923 if (osdDisplayPort
!= NULL
) {
3924 cliPrintf("%s [%u x %u]", osdDisplayPort
->displayPortType
, osdDisplayPort
->cols
, osdDisplayPort
->rows
);
3926 cliPrint("not enabled");
3929 cliPrint("not used");
3934 #if defined(USE_VTX_CONTROL)
3935 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
3936 vtxDeviceOsdInfo_t osdInfo
;
3937 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo
);
3938 cliPrintf("band: %c, chan: %s, power: %c", osdInfo
.bandLetter
, osdInfo
.channelName
, osdInfo
.powerIndexLetter
);
3940 if (osdInfo
.powerMilliwatt
) {
3941 cliPrintf(" (%d mW)", osdInfo
.powerMilliwatt
);
3944 if (osdInfo
.frequency
) {
3945 cliPrintf(", freq: %d MHz", osdInfo
.frequency
);
3949 cliPrint("not detected");
3952 cliPrint("no VTX control");
3958 if (featureConfigured(FEATURE_GPS
) && isGpsUblox()) {
3960 cliPrintf("HW Version: %s Proto: %d.%02d Baud: %d", getGpsHwVersion(), getGpsProtoMajorVersion(), getGpsProtoMinorVersion(), getGpsBaudrate());
3961 if(ubloxVersionLT(15, 0)) {
3962 cliPrintf(" (UBLOX Proto >= 15.0 required)");
3965 cliPrintLinef(" SATS: %i", gpsSol
.numSat
);
3966 cliPrintLinef(" HDOP: %f", (double)(gpsSol
.hdop
/ (float)HDOP_SCALE
));
3967 cliPrintLinef(" EPH : %f m", (double)(gpsSol
.eph
/ 100.0f
));
3968 cliPrintLinef(" EPV : %f m", (double)(gpsSol
.epv
/ 100.0f
));
3969 //cliPrintLinef(" GNSS Capabilities: %d", gpsUbloxCapLastUpdate());
3970 cliPrintLinef(" GNSS Capabilities:");
3971 cliPrintLine(" GNSS Provider active/default");
3972 cliPrintLine(" GPS 1/1");
3973 if(gpsUbloxHasGalileo())
3974 cliPrintLinef(" Galileo %d/%d", gpsUbloxGalileoEnabled(), gpsUbloxGalileoDefault());
3975 if(gpsUbloxHasBeidou())
3976 cliPrintLinef(" BeiDou %d/%d", gpsUbloxBeidouEnabled(), gpsUbloxBeidouDefault());
3977 if(gpsUbloxHasGlonass())
3978 cliPrintLinef(" Glonass %d/%d", gpsUbloxGlonassEnabled(), gpsUbloxGlonassDefault());
3979 cliPrintLinef(" Max concurrent: %d", gpsUbloxMaxGnss());
3982 // If we are blocked by PWM init - provide more information
3983 if (getPwmInitError() != PWM_INIT_ERROR_NONE
) {
3984 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3988 static void cliTasks(char *cmdline
)
3992 int averageLoadSum
= 0;
3993 cfCheckFuncInfo_t checkFuncInfo
;
3995 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3996 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
3997 cfTaskInfo_t taskInfo
;
3998 getTaskInfo(taskId
, &taskInfo
);
3999 if (taskInfo
.isEnabled
) {
4000 const int taskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
4001 const int maxLoad
= (taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
4002 const int averageLoad
= (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
4003 if (taskId
!= TASK_SERIAL
) {
4004 maxLoadSum
+= maxLoad
;
4005 averageLoadSum
+= averageLoad
;
4007 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
4008 taskId
, taskInfo
.taskName
, taskFrequency
, (uint32_t)taskInfo
.maxExecutionTime
, (uint32_t)taskInfo
.averageExecutionTime
,
4009 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, (uint32_t)taskInfo
.totalExecutionTime
/ 1000);
4012 getCheckFuncInfo(&checkFuncInfo
);
4013 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo
.maxExecutionTime
, (uint32_t)checkFuncInfo
.averageExecutionTime
, (uint32_t)checkFuncInfo
.totalExecutionTime
/ 1000);
4014 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
4017 static void cliVersion(char *cmdline
)
4021 cliPrintLinef("# %s/%s %s %s / %s (%s) %s",
4030 cliPrintLinef("# GCC-%s",
4035 static void cliMemory(char *cmdline
)
4038 cliPrintLinef("Dynamic memory usage:");
4039 for (unsigned i
= 0; i
< OWNER_TOTAL_COUNT
; i
++) {
4040 const char * owner
= ownerNames
[i
];
4041 const uint32_t memUsed
= memGetUsedBytesByOwner(i
);
4044 cliPrintLinef("%s : %d bytes", owner
, memUsed
);
4049 static void cliResource(char *cmdline
)
4052 cliPrintLinef("IO:\r\n----------------------");
4053 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
4055 owner
= ownerNames
[ioRecs
[i
].owner
];
4057 const char* resource
;
4058 resource
= resourceNames
[ioRecs
[i
].resource
];
4060 if (ioRecs
[i
].index
> 0) {
4061 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
, ioRecs
[i
].index
, resource
);
4063 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
, resource
);
4068 static void backupConfigs(void)
4070 // make copies of configs to do differencing
4072 if (pgIsProfile(pg
)) {
4073 memcpy(pg
->copy
, pg
->address
, pgSize(pg
) * MAX_PROFILE_COUNT
);
4075 memcpy(pg
->copy
, pg
->address
, pgSize(pg
));
4080 static void restoreConfigs(void)
4083 if (pgIsProfile(pg
)) {
4084 memcpy(pg
->address
, pg
->copy
, pgSize(pg
) * MAX_PROFILE_COUNT
);
4086 memcpy(pg
->address
, pg
->copy
, pgSize(pg
));
4091 static void printConfig(const char *cmdline
, bool doDiff
)
4093 uint8_t dumpMask
= DUMP_MASTER
;
4094 const char *options
;
4095 if ((options
= checkCommand(cmdline
, "master"))) {
4096 dumpMask
= DUMP_MASTER
; // only
4097 } else if ((options
= checkCommand(cmdline
, "control_profile"))) {
4098 dumpMask
= DUMP_CONTROL_PROFILE
; // only
4099 } else if ((options
= checkCommand(cmdline
, "mixer_profile"))) {
4100 dumpMask
= DUMP_MIXER_PROFILE
; // only
4101 } else if ((options
= checkCommand(cmdline
, "battery_profile"))) {
4102 dumpMask
= DUMP_BATTERY_PROFILE
; // only
4103 } else if ((options
= checkCommand(cmdline
, "all"))) {
4104 dumpMask
= DUMP_ALL
; // all profiles and rates
4110 dumpMask
= dumpMask
| DO_DIFF
;
4113 const int currentControlProfileIndexSave
= getConfigProfile();
4114 const int currentMixerProfileIndexSave
= getConfigMixerProfile();
4115 const int currentBatteryProfileIndexSave
= getConfigBatteryProfile();
4117 // reset all configs to defaults to do differencing
4119 // restore the profile indices, since they should not be reset for proper comparison
4120 setConfigProfile(currentControlProfileIndexSave
);
4121 setConfigMixerProfile(currentMixerProfileIndexSave
);
4122 setConfigBatteryProfile(currentBatteryProfileIndexSave
);
4124 if (checkCommand(options
, "showdefaults")) {
4125 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
4128 #ifdef USE_CLI_BATCH
4129 bool batchModeEnabled
= false;
4132 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
4133 cliPrintHashLine("version");
4136 #ifdef USE_CLI_BATCH
4137 cliPrintHashLine("start the command batch");
4138 cliPrintLine("batch start");
4139 batchModeEnabled
= true;
4142 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
4143 #ifndef CLI_MINIMAL_VERBOSITY
4144 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
4146 cliPrintLinef("defaults noreboot");
4150 cliPrintHashLine("resources");
4151 //printResource(dumpMask, &defaultConfig);
4153 cliPrintHashLine("Timer overrides");
4154 printTimerOutputModes(dumpMask
, timerOverrides_CopyArray
, timerOverrides(0), -1);
4156 // print servo parameters
4157 cliPrintHashLine("Outputs [servo]");
4158 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0));
4160 #if defined(USE_SAFE_HOME)
4161 cliPrintHashLine("safehome");
4162 printSafeHomes(dumpMask
, safeHomeConfig_CopyArray
, safeHomeConfig(0));
4165 #ifdef USE_FW_AUTOLAND
4166 cliPrintHashLine("Fixed Wing Approach");
4167 printFwAutolandApproach(dumpMask
, fwAutolandApproachConfig_CopyArray
, fwAutolandApproachConfig(0));
4170 cliPrintHashLine("features");
4171 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig());
4173 #if defined(BEEPER) || defined(USE_DSHOT)
4174 cliPrintHashLine("beeper");
4175 printBeeper(dumpMask
, &beeperConfig_Copy
, beeperConfig());
4179 cliPrintHashLine("blackbox");
4180 printBlackbox(dumpMask
, &blackboxConfig_Copy
, blackboxConfig());
4183 cliPrintHashLine("Receiver: Channel map");
4184 printMap(dumpMask
, &rxConfig_Copy
, rxConfig());
4186 cliPrintHashLine("Ports");
4187 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig());
4189 #ifdef USE_LED_STRIP
4190 cliPrintHashLine("LEDs");
4191 printLed(dumpMask
, ledStripConfig_Copy
.ledConfigs
, ledStripConfig()->ledConfigs
);
4193 cliPrintHashLine("LED color");
4194 printColor(dumpMask
, ledStripConfig_Copy
.colors
, ledStripConfig()->colors
);
4196 cliPrintHashLine("LED mode_color");
4197 printModeColor(dumpMask
, &ledStripConfig_Copy
, ledStripConfig());
4200 cliPrintHashLine("Modes [aux]");
4201 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0));
4203 cliPrintHashLine("Adjustments [adjrange]");
4204 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0));
4206 cliPrintHashLine("Receiver rxrange");
4207 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0));
4209 #ifdef USE_TEMPERATURE_SENSOR
4210 cliPrintHashLine("temp_sensor");
4211 printTempSensor(dumpMask
, tempSensorConfig_CopyArray
, tempSensorConfig(0));
4214 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4215 cliPrintHashLine("Mission Control Waypoints [wp]");
4216 printWaypoints(dumpMask
, posControl
.waypointList
, nonVolatileWaypointList(0));
4220 cliPrintHashLine("OSD [osd_layout]");
4221 printOsdLayout(dumpMask
, &osdLayoutsConfig_Copy
, osdLayoutsConfig(), -1, -1);
4224 #ifdef USE_PROGRAMMING_FRAMEWORK
4225 cliPrintHashLine("Programming: logic");
4226 printLogic(dumpMask
, logicConditions_CopyArray
, logicConditions(0), -1);
4228 cliPrintHashLine("Programming: global variables");
4229 printGvar(dumpMask
, globalVariableConfigs_CopyArray
, globalVariableConfigs(0));
4231 cliPrintHashLine("Programming: PID controllers");
4232 printPid(dumpMask
, programmingPids_CopyArray
, programmingPids(0));
4234 #ifdef USE_PROGRAMMING_FRAMEWORK
4235 cliPrintHashLine("OSD: custom elements");
4236 printOsdCustomElements(dumpMask
, osdCustomElements_CopyArray
, osdCustomElements(0));
4239 cliPrintHashLine("master");
4240 dumpAllValues(MASTER_VALUE
, dumpMask
);
4242 if (dumpMask
& DUMP_ALL
) {
4243 // dump all profiles
4244 const int currentControlProfileIndexSave
= getConfigProfile();
4245 const int currentMixerProfileIndexSave
= getConfigMixerProfile();
4246 const int currentBatteryProfileIndexSave
= getConfigBatteryProfile();
4247 for (int ii
= 0; ii
< MAX_PROFILE_COUNT
; ++ii
) {
4248 cliDumpControlProfile(ii
, dumpMask
);
4250 for (int ii
= 0; ii
< MAX_MIXER_PROFILE_COUNT
; ++ii
) {
4251 cliDumpMixerProfile(ii
, dumpMask
);
4253 for (int ii
= 0; ii
< MAX_BATTERY_PROFILE_COUNT
; ++ii
) {
4254 cliDumpBatteryProfile(ii
, dumpMask
);
4256 setConfigProfile(currentControlProfileIndexSave
);
4257 setConfigMixerProfile(currentMixerProfileIndexSave
);
4258 setConfigBatteryProfile(currentBatteryProfileIndexSave
);
4260 cliPrintHashLine("restore original profile selection");
4261 cliPrintLinef("control_profile %d", currentControlProfileIndexSave
+ 1);
4262 cliPrintLinef("mixer_profile %d", currentMixerProfileIndexSave
+ 1);
4263 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave
+ 1);
4265 #ifdef USE_CLI_BATCH
4266 batchModeEnabled
= false;
4269 // dump just the current profiles
4270 cliDumpControlProfile(getConfigProfile(), dumpMask
);
4271 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask
);
4272 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask
);
4276 if (dumpMask
& DUMP_CONTROL_PROFILE
) {
4277 cliDumpControlProfile(getConfigProfile(), dumpMask
);
4280 if (dumpMask
& DUMP_MIXER_PROFILE
) {
4281 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask
);
4284 if (dumpMask
& DUMP_BATTERY_PROFILE
) {
4285 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask
);
4288 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
4289 cliPrintHashLine("save configuration\r\nsave");
4292 #ifdef USE_CLI_BATCH
4293 if (batchModeEnabled
) {
4294 cliPrintHashLine("end the command batch");
4295 cliPrintLine("batch end");
4299 // restore configs from copies
4303 static void cliDump(char *cmdline
)
4305 printConfig(cmdline
, false);
4308 static void cliDiff(char *cmdline
)
4310 printConfig(cmdline
, true);
4314 static void cliMsc(char *cmdline
)
4320 || sdcard_isFunctional()
4323 || flashfsGetSize() > 0
4326 cliPrintHashLine("restarting in mass storage mode");
4327 cliPrint("\r\nRebooting");
4328 bufWriterFlush(cliWriter
);
4330 waitForSerialPortToFinishTransmitting(cliPort
);
4332 systemResetRequest(RESET_MSC_REQUEST
);
4334 cliPrint("\r\nStorage not present or failed to initialize!");
4335 bufWriterFlush(cliWriter
);
4343 #ifndef SKIP_CLI_COMMAND_HELP
4344 const char *description
;
4347 void (*func
)(char *cmdline
);
4350 #ifndef SKIP_CLI_COMMAND_HELP
4351 #define CLI_COMMAND_DEF(name, description, args, method) \
4359 #define CLI_COMMAND_DEF(name, description, args, method) \
4366 static void cliCmdDebug(char *arg
)
4369 if (debugMode
!= DEBUG_NONE
) {
4370 cliPrintLinef("Debug fields: [%s (%i)]", debugMode
< DEBUG_COUNT
? debugModeNames
[debugMode
] : "unknown", debugMode
);
4371 for (int i
= 0; i
< DEBUG32_VALUE_COUNT
; i
++) {
4372 cliPrintLinef("debug[%d] = %d", i
, debug
[i
]);
4375 cliPrintLine("Debug mode is disabled");
4380 #if defined(USE_GPS) && defined(USE_GPS_PROTO_UBLOX)
4382 static const char* _ubloxGetSigId(uint8_t gnssId
, uint8_t sigId
)
4386 case 0: return "GPS L1C/A";
4387 case 3: return "GPS L2 CL";
4388 case 4: return "GPS L2 CM";
4389 case 6: return "GPS L5 I";
4390 case 7: return "GPS L5 Q";
4391 default: return "GPS Unknown";
4393 } else if(gnssId
== 1) {
4395 case 0: return "SBAS L1C/A";
4396 default: return "SBAS Unknown";
4398 } else if(gnssId
== 2) {
4400 case 0: return "Galileo E1 C";
4401 case 1: return "Galileo E1 B";
4402 case 3: return "Galileo E5 al";
4403 case 4: return "Galileo E5 aQ";
4404 case 5: return "Galileo E5 bl";
4405 case 6: return "Galileo E5 bQ";
4406 default: return "Galileo Unknown";
4408 } else if(gnssId
== 3) {
4410 case 0: return "BeiDou B1I D1";
4411 case 1: return "BeiDou B1I D2";
4412 case 2: return "BeiDou B2I D1";
4413 case 3: return "BeiDou B2I D2";
4414 case 5: return "BeiDou B1C";
4415 case 7: return "BeiDou B2a";
4416 default: return "BeiDou Unknown";
4418 } else if(gnssId
== 5) {
4420 case 0: return "QZSS L1C/A";
4421 case 1: return "QZSS L1S";
4422 case 4: return "QZSS L2 CM";
4423 case 5: return "QZSS L2 CL";
4424 case 8: return "QZSS L5 I";
4425 case 9: return "QZSS L5 Q";
4426 default: return "QZSS Unknown";
4428 } else if(gnssId
== 6) {
4430 case 0: return "GLONASS L1 OF";
4431 case 2: return "GLONASS L2 OF";
4432 default: return "GLONASS Unknown";
4436 return "Unknown GNSS/SigId";
4439 static const char *_ubloxGetQuality(uint8_t quality
)
4442 case UBLOX_SIG_QUALITY_NOSIGNAL
: return "No signal";
4443 case UBLOX_SIG_QUALITY_SEARCHING
: return "Searching signal...";
4444 case UBLOX_SIG_QUALITY_ACQUIRED
: return "Signal acquired";
4445 case UBLOX_SIG_QUALITY_UNUSABLE
: return "Signal detected but unusable";
4446 case UBLOX_SIG_QUALITY_CODE_LOCK_TIME_SYNC
: return "Code locked and time sync";
4447 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC
:
4448 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC2
:
4449 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC3
:
4450 return "Code and carrier locked and time sync";
4451 default: return "Unknown";
4455 static void cliUbloxPrintSatelites(char *arg
)
4458 if(!isGpsUblox() /*|| !(gpsState.flags.sig || gpsState.flags.sat)*/) {
4459 cliPrint("GPS is not UBLOX or does not report satelites.");
4463 cliPrintLine("UBLOX Satelites");
4465 for(int i
= 0; i
< UBLOX_MAX_SIGNALS
; ++i
)
4467 const ubx_nav_sig_info
*sat
= gpsGetUbloxSatelite(i
);
4472 cliPrintLinef("satelite[%d]: %d:%d", i
+1, sat
->gnssId
, sat
->svId
);
4473 cliPrintLinef("sigId: %d (%s)", sat
->sigId
, _ubloxGetSigId(sat
->gnssId
, sat
->sigId
));
4474 cliPrintLinef("signal strength: %i dbHz", sat
->cno
);
4475 cliPrintLinef("quality: %i (%s)", sat
->quality
, _ubloxGetQuality(sat
->quality
));
4476 //cliPrintLinef("Correlation: %i", sat->corrSource);
4477 //cliPrintLinef("Iono model: %i", sat->ionoModel);
4478 cliPrintLinef("signal flags: 0x%02X", sat
->sigFlags
);
4479 switch(sat
->sigFlags
& UBLOX_SIG_HEALTH_MASK
) {
4480 case UBLOX_SIG_HEALTH_HEALTHY
:
4481 cliPrintLine("signal: Healthy");
4483 case UBLOX_SIG_HEALTH_UNHEALTHY
:
4484 cliPrintLine("signal: Unhealthy");
4486 case UBLOX_SIG_HEALTH_UNKNOWN
:
4488 cliPrintLinef("signal: Unknown (0x%X)", sat
->sigFlags
& UBLOX_SIG_HEALTH_MASK
);
4496 static void cliHelp(char *cmdline
);
4498 // should be sorted a..z for bsearch()
4499 const clicmd_t cmdTable
[] = {
4500 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
4501 #if defined(USE_ASSERT)
4502 CLI_COMMAND_DEF("assert", "", NULL
, cliAssert
),
4504 CLI_COMMAND_DEF("aux", "configure modes", NULL
, cliAux
),
4505 #ifdef USE_CLI_BATCH
4506 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch
),
4508 #if defined(BEEPER) || defined(USE_DSHOT)
4509 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
4510 "\t<+|->[name]", cliBeeper
),
4512 #if defined (USE_SERIALRX_SRXL2)
4513 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL
, cliRxBind
),
4515 #if defined(USE_BOOTLOG)
4516 CLI_COMMAND_DEF("bootlog", "show boot events", NULL
, cliBootlog
),
4518 #ifdef USE_LED_STRIP
4519 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
4520 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
4522 CLI_COMMAND_DEF("cli_delay", "CLI Delay", "Delay in ms", cliDelay
),
4523 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL
, cliDefaults
),
4524 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL
, cliDfu
),
4525 CLI_COMMAND_DEF("diff", "list configuration changes from default",
4526 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDiff
),
4527 CLI_COMMAND_DEF("dump", "dump configuration",
4528 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDump
),
4529 #ifdef USE_RX_ELERES
4530 CLI_COMMAND_DEF("eleres_bind", NULL
, NULL
, cliEleresBind
),
4531 #endif // USE_RX_ELERES
4532 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
4533 CLI_COMMAND_DEF("feature", "configure features",
4535 "\t<+|->[name]", cliFeature
),
4537 CLI_COMMAND_DEF("blackbox", "configure blackbox fields",
4539 "\t<+|->[name]", cliBlackbox
),
4542 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
4543 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
4544 #ifdef USE_FLASH_TOOLS
4545 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
4546 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
4549 #ifdef USE_FW_AUTOLAND
4550 CLI_COMMAND_DEF("fwapproach", "Fixed Wing Approach Settings", NULL
, cliFwAutolandApproach
),
4552 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
4554 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
4555 CLI_COMMAND_DEF("gpssats", "show GPS satellites", NULL
, cliUbloxPrintSatelites
),
4557 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
4558 #ifdef USE_LED_STRIP
4559 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
4560 CLI_COMMAND_DEF("ledpinpwm", "start/stop PWM on LED pin, 0..100 duty ratio", "[<value>]\r\n", cliLedPinPWM
),
4562 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
4563 CLI_COMMAND_DEF("memory", "view memory usage", NULL
, cliMemory
),
4564 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
4565 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
4567 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
4569 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]\r\n", cliPlaySound
),
4570 CLI_COMMAND_DEF("control_profile", "change control profile", "[<index>]", cliControlProfile
),
4571 CLI_COMMAND_DEF("mixer_profile", "change mixer profile", "[<index>]", cliMixerProfile
),
4572 CLI_COMMAND_DEF("battery_profile", "change battery profile", "[<index>]", cliBatteryProfile
),
4573 CLI_COMMAND_DEF("resource", "view currently used resources", NULL
, cliResource
),
4574 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
4575 #if defined(USE_SAFE_HOME)
4576 CLI_COMMAND_DEF("safehome", "safe home list", NULL
, cliSafeHomes
),
4578 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
4579 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
4580 #ifdef USE_SERIAL_PASSTHROUGH
4581 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [options]: passthrough to serial", cliSerialPassthrough
),
4583 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
4584 #ifdef USE_PROGRAMMING_FRAMEWORK
4585 CLI_COMMAND_DEF("logic", "configure logic conditions",
4586 "<rule> <enabled> <activatorId> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
4587 "\treset\r\n", cliLogic
),
4589 CLI_COMMAND_DEF("gvar", "configure global variables",
4590 "<gvar> <default> <min> <max>\r\n"
4591 "\treset\r\n", cliGvar
),
4593 CLI_COMMAND_DEF("pid", "configurable PID controllers",
4594 "<#> <enabled> <setpoint type> <setpoint value> <measurement type> <measurement value> <P gain> <I gain> <D gain> <FF gain>\r\n"
4595 "\treset\r\n", cliPid
),
4597 CLI_COMMAND_DEF("osd_custom_elements", "configurable OSD custom elements",
4598 "<#> <part0 type> <part0 value> <part1 type> <part1 value> <part2 type> <part2 value> <visibility type> <visibility value> <text>\r\n"
4601 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
4602 CLI_COMMAND_DEF("smix", "servo mixer",
4603 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
4604 "\treset\r\n", cliServoMix
),
4606 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
4608 CLI_COMMAND_DEF("showdebug", "Show debug fields.", NULL
, cliCmdDebug
),
4609 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
4610 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
4611 #ifdef USE_TEMPERATURE_SENSOR
4612 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL
, cliTempSensor
),
4614 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
4615 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4616 CLI_COMMAND_DEF("wp", "waypoint list", NULL
, cliWaypoints
),
4619 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout
),
4621 CLI_COMMAND_DEF("timer_output_mode", "get or set the outputmode for a given timer.", "[<timer> [<AUTO|MOTORS|SERVOS>]]", cliTimerOutputMode
),
4624 static void cliHelp(char *cmdline
)
4628 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
4629 cliPrint(cmdTable
[i
].name
);
4630 #ifndef SKIP_CLI_COMMAND_HELP
4631 if (cmdTable
[i
].description
) {
4632 cliPrintf(" - %s", cmdTable
[i
].description
);
4634 if (cmdTable
[i
].args
) {
4635 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
4642 void cliProcess(void)
4648 // Be a little bit tricky. Flush the last inputs buffer, if any.
4649 bufWriterFlush(cliWriter
);
4651 while (serialRxBytesWaiting(cliPort
)) {
4652 uint8_t c
= serialRead(cliPort
);
4653 if (c
== '\t' || c
== '?') {
4654 // do tab completion
4655 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
4656 uint32_t i
= bufferIndex
;
4657 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4658 if (bufferIndex
&& (sl_strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
4664 if (pstart
) { /* Buffer matches one or more commands */
4665 for (; ; bufferIndex
++) {
4666 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
4668 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
4669 /* Unambiguous -- append a space */
4670 cliBuffer
[bufferIndex
++] = ' ';
4671 cliBuffer
[bufferIndex
] = '\0';
4674 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
4677 if (!bufferIndex
|| pstart
!= pend
) {
4678 /* Print list of ambiguous matches */
4679 cliPrint("\r\033[K");
4680 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
4681 cliPrint(cmd
->name
);
4685 i
= 0; /* Redraw prompt */
4687 for (; i
< bufferIndex
; i
++)
4688 cliWrite(cliBuffer
[i
]);
4689 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
4692 } else if (c
== 12) { // NewPage / CTRL-L
4694 cliPrint("\033[2J\033[1;1H");
4696 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
4700 // Strip comment starting with # from line
4701 char *p
= cliBuffer
;
4704 bufferIndex
= (uint32_t)(p
- cliBuffer
);
4707 // Strip trailing whitespace
4708 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
4712 // Process non-empty lines
4713 if (bufferIndex
> 0) {
4714 cliBuffer
[bufferIndex
] = 0; // null terminate
4716 const clicmd_t
*cmd
;
4717 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4718 if (!sl_strncasecmp(cliBuffer
, cmd
->name
, strlen(cmd
->name
)) // command names match
4719 && !sl_isalnum((unsigned)cliBuffer
[strlen(cmd
->name
)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
4722 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
))
4723 cmd
->func(cliBuffer
+ strlen(cmd
->name
) + 1);
4725 cliPrintError("Unknown command, try 'help'");
4729 ZERO_FARRAY(cliBuffer
);
4731 // 'exit' will reset this flag, so we don't need to print prompt again
4736 } else if (c
== 127) {
4739 cliBuffer
[--bufferIndex
] = 0;
4740 cliPrint("\010 \010");
4742 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
4743 if (!bufferIndex
&& c
== ' ')
4744 continue; // Ignore leading spaces
4745 cliBuffer
[bufferIndex
++] = c
;
4751 void cliEnter(serialPort_t
*serialPort
)
4758 cliPort
= serialPort
;
4759 setPrintfSerialPort(cliPort
);
4760 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
4762 #ifndef CLI_MINIMAL_VERBOSITY
4763 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4765 cliPrintLine("\r\nCLI");
4769 #ifdef USE_CLI_BATCH
4770 resetCommandBatch();
4773 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI
);
4776 void cliInit(const serialConfig_t
*serialConfig
)
4778 UNUSED(serialConfig
);