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/>.
29 extern uint8_t __config_start
; // configured via linker script when building binaries.
30 extern uint8_t __config_end
;
32 #include "blackbox/blackbox.h"
34 #include "build/assert.h"
35 #include "build/build_config.h"
36 #include "build/version.h"
38 #include "common/axis.h"
39 #include "common/color.h"
40 #include "common/maths.h"
41 #include "common/printf.h"
42 #include "common/string_light.h"
43 #include "common/memory.h"
44 #include "common/time.h"
45 #include "common/typeconversion.h"
46 #include "common/global_functions.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/io.h"
59 #include "drivers/io_impl.h"
60 #include "drivers/osd_symbols.h"
61 #include "drivers/rx_pwm.h"
62 #include "drivers/sdcard/sdcard.h"
63 #include "drivers/sensor.h"
64 #include "drivers/serial.h"
65 #include "drivers/stack_check.h"
66 #include "drivers/system.h"
67 #include "drivers/time.h"
68 #include "drivers/timer.h"
69 #include "drivers/usb_msc.h"
70 #include "drivers/vtx_common.h"
72 #include "fc/fc_core.h"
74 #include "fc/config.h"
75 #include "fc/controlrate_profile.h"
76 #include "fc/rc_adjustments.h"
77 #include "fc/rc_controls.h"
78 #include "fc/rc_modes.h"
79 #include "fc/runtime_config.h"
80 #include "fc/settings.h"
82 #include "flight/failsafe.h"
83 #include "flight/imu.h"
84 #include "flight/mixer.h"
85 #include "flight/pid.h"
86 #include "flight/servos.h"
88 #include "io/asyncfatfs/asyncfatfs.h"
89 #include "io/beeper.h"
90 #include "io/flashfs.h"
92 #include "io/ledstrip.h"
94 #include "io/serial.h"
96 #include "navigation/navigation.h"
97 #include "navigation/navigation_private.h"
100 #include "rx/spektrum.h"
101 #include "rx/eleres.h"
103 #include "scheduler/scheduler.h"
105 #include "sensors/acceleration.h"
106 #include "sensors/barometer.h"
107 #include "sensors/battery.h"
108 #include "sensors/boardalignment.h"
109 #include "sensors/compass.h"
110 #include "sensors/diagnostics.h"
111 #include "sensors/gyro.h"
112 #include "sensors/pitotmeter.h"
113 #include "sensors/rangefinder.h"
114 #include "sensors/opflow.h"
115 #include "sensors/sensors.h"
116 #include "sensors/temperature.h"
118 #include "telemetry/frsky_d.h"
119 #include "telemetry/telemetry.h"
120 #include "build/debug.h"
126 extern timeDelta_t cycleTime
; // FIXME dependency on mw.c
127 extern uint8_t detectedSensors
[SENSOR_INDEX_COUNT
];
129 static serialPort_t
*cliPort
;
131 static bufWriter_t
*cliWriter
;
132 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + 128];
134 static char cliBuffer
[64];
135 static uint32_t bufferIndex
= 0;
137 #if defined(USE_ASSERT)
138 static void cliAssert(char *cmdline
);
142 static bool commandBatchActive
= false;
143 static bool commandBatchError
= false;
146 // sync this with features_e
147 static const char * const featureNames
[] = {
148 "THR_VBAT_COMP", "VBAT", "TX_PROF_SEL", "BAT_PROF_AUTOSWITCH", "MOTOR_STOP",
149 "DYNAMIC_FILTERS", "SOFTSERIAL", "GPS", "RPM_FILTERS",
150 "", "TELEMETRY", "CURRENT_METER", "REVERSIBLE_MOTORS", "",
151 "", "RSSI_ADC", "LED_STRIP", "DASHBOARD", "",
152 "BLACKBOX", "", "TRANSPONDER", "AIRMODE",
153 "SUPEREXPO", "VTX", "", "", "PWM_SERVO_DRIVER", "PWM_OUTPUT_ENABLE",
154 "OSD", "FW_LAUNCH", NULL
157 /* Sensor names (used in lookup tables for *_hardware settings and in status command output) */
158 // sync with gyroSensor_e
159 static const char * const gyroNames
[] = { "NONE", "AUTO", "MPU6050", "L3G4200D", "MPU3050", "L3GD20", "MPU6000", "MPU6500", "MPU9250", "BMI160", "ICM20689", "FAKE"};
161 // sync this with sensors_e
162 static const char * const sensorTypeNames
[] = {
163 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "PITOT", "OPFLOW", "GPS", "GPS+MAG", NULL
166 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER | SENSOR_PITOT | SENSOR_OPFLOW)
168 static const char * const hardwareSensorStatusNames
[] = {
169 "NONE", "OK", "UNAVAILABLE", "FAILING"
172 static const char * const *sensorHardwareNames
[] = {
185 #ifdef USE_RANGEFINDER
186 table_rangefinder_hardware
,
191 table_pitot_hardware
,
196 table_opflow_hardware
,
202 static void cliPrint(const char *str
)
205 bufWriterAppend(cliWriter
, *str
++);
209 static void cliPrintLinefeed(void)
214 static void cliPrintLine(const char *str
)
220 static void cliPrintError(const char *str
)
222 cliPrint("### ERROR: ");
225 if (commandBatchActive
) {
226 commandBatchError
= true;
231 static void cliPrintErrorLine(const char *str
)
233 cliPrint("### ERROR: ");
236 if (commandBatchActive
) {
237 commandBatchError
= true;
242 #ifdef CLI_MINIMAL_VERBOSITY
243 #define cliPrintHashLine(str)
245 static void cliPrintHashLine(const char *str
)
252 static void cliPutp(void *p
, char ch
)
254 bufWriterAppend(p
, ch
);
258 DUMP_MASTER
= (1 << 0),
259 DUMP_PROFILE
= (1 << 1),
260 DUMP_BATTERY_PROFILE
= (1 << 2),
261 DUMP_RATES
= (1 << 3),
264 SHOW_DEFAULTS
= (1 << 6),
265 HIDE_UNUSED
= (1 << 7)
268 static void cliPrintfva(const char *format
, va_list va
)
270 tfp_format(cliWriter
, cliPutp
, format
, va
);
271 bufWriterFlush(cliWriter
);
274 static void cliPrintLinefva(const char *format
, va_list va
)
276 tfp_format(cliWriter
, cliPutp
, format
, va
);
277 bufWriterFlush(cliWriter
);
281 static bool cliDumpPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
283 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
285 va_start(va
, format
);
286 cliPrintLinefva(format
, va
);
294 static void cliWrite(uint8_t ch
)
296 bufWriterAppend(cliWriter
, ch
);
299 static bool cliDefaultPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
301 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
305 va_start(va
, format
);
306 cliPrintLinefva(format
, va
);
314 static void cliPrintf(const char *format
, ...)
317 va_start(va
, format
);
318 cliPrintfva(format
, va
);
323 static void cliPrintLinef(const char *format
, ...)
326 va_start(va
, format
);
327 cliPrintLinefva(format
, va
);
331 static void cliPrintErrorVa(const char *format
, va_list va
)
333 cliPrint("### ERROR: ");
334 cliPrintfva(format
, va
);
338 if (commandBatchActive
) {
339 commandBatchError
= true;
344 static void cliPrintErrorLinef(const char *format
, ...)
347 va_start(va
, format
);
348 cliPrintErrorVa(format
, va
);
352 static void printValuePointer(const setting_t
*var
, const void *valuePointer
, uint32_t full
)
355 char buf
[SETTING_MAX_NAME_LENGTH
];
357 switch (SETTING_TYPE(var
)) {
359 value
= *(uint8_t *)valuePointer
;
363 value
= *(int8_t *)valuePointer
;
367 value
= *(uint16_t *)valuePointer
;
371 value
= *(int16_t *)valuePointer
;
375 value
= *(uint32_t *)valuePointer
;
379 cliPrintf("%s", ftoa(*(float *)valuePointer
, buf
));
381 if (SETTING_MODE(var
) == MODE_DIRECT
) {
382 cliPrintf(" %s", ftoa((float)settingGetMin(var
), buf
));
383 cliPrintf(" %s", ftoa((float)settingGetMax(var
), buf
));
386 return; // return from case for float only
389 cliPrintf("%s", (const char *)valuePointer
);
393 switch (SETTING_MODE(var
)) {
395 if (SETTING_TYPE(var
) == VAR_UINT32
)
396 cliPrintf("%u", value
);
398 cliPrintf("%d", value
);
400 if (SETTING_MODE(var
) == MODE_DIRECT
) {
401 cliPrintf(" %d %u", settingGetMin(var
), settingGetMax(var
));
407 const char *name
= settingLookupValueName(var
, value
);
411 settingGetName(var
, buf
);
412 cliPrintErrorLinef("VALUE %d OUT OF RANGE FOR %s", (int)value
, buf
);
419 static bool valuePtrEqualsDefault(const setting_t
*value
, const void *ptr
, const void *ptrDefault
)
422 switch (SETTING_TYPE(value
)) {
424 result
= *(uint8_t *)ptr
== *(uint8_t *)ptrDefault
;
428 result
= *(int8_t *)ptr
== *(int8_t *)ptrDefault
;
432 result
= *(uint16_t *)ptr
== *(uint16_t *)ptrDefault
;
436 result
= *(int16_t *)ptr
== *(int16_t *)ptrDefault
;
440 result
= *(uint32_t *)ptr
== *(uint32_t *)ptrDefault
;
444 result
= *(float *)ptr
== *(float *)ptrDefault
;
448 result
= strncmp(ptr
, ptrDefault
, settingGetStringMaxLength(value
) + 1) == 0;
454 static void dumpPgValue(const setting_t
*value
, uint8_t dumpMask
)
456 char name
[SETTING_MAX_NAME_LENGTH
];
457 const char *format
= "set %s = ";
458 const char *defaultFormat
= "#set %s = ";
459 // During a dump, the PGs have been backed up to their "copy"
460 // regions and the actual values have been reset to its
461 // defaults. This means that settingGetValuePointer() will
462 // return the default value while settingGetCopyValuePointer()
463 // will return the actual value.
464 const void *valuePointer
= settingGetCopyValuePointer(value
);
465 const void *defaultValuePointer
= settingGetValuePointer(value
);
466 const bool equalsDefault
= valuePtrEqualsDefault(value
, valuePointer
, defaultValuePointer
);
467 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
468 settingGetName(value
, name
);
469 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
470 cliPrintf(defaultFormat
, name
);
471 printValuePointer(value
, defaultValuePointer
, 0);
474 cliPrintf(format
, name
);
475 printValuePointer(value
, valuePointer
, 0);
480 static void dumpAllValues(uint16_t valueSection
, uint8_t dumpMask
)
482 for (unsigned i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
483 const setting_t
*value
= settingGet(i
);
484 bufWriterFlush(cliWriter
);
485 if (SETTING_SECTION(value
) == valueSection
) {
486 dumpPgValue(value
, dumpMask
);
491 static void cliPrintVar(const setting_t
*var
, uint32_t full
)
493 const void *ptr
= settingGetValuePointer(var
);
495 printValuePointer(var
, ptr
, full
);
498 static void cliPrintVarRange(const setting_t
*var
)
500 switch (SETTING_MODE(var
)) {
502 if (SETTING_TYPE(var
) == VAR_STRING
) {
503 cliPrintLinef("Max. length: %u", settingGetStringMaxLength(var
));
506 cliPrintLinef("Allowed range: %d - %u", settingGetMin(var
), settingGetMax(var
));
510 const lookupTableEntry_t
*tableEntry
= settingLookupTable(var
);
511 cliPrint("Allowed values:");
512 for (uint32_t i
= 0; i
< tableEntry
->valueCount
; i
++) {
515 cliPrintf(" %s", tableEntry
->values
[i
]);
529 static void cliSetIntFloatVar(const setting_t
*var
, const int_float_value_t value
)
531 void *ptr
= settingGetValuePointer(var
);
533 switch (SETTING_TYPE(var
)) {
536 *(int8_t *)ptr
= value
.int_value
;
541 *(int16_t *)ptr
= value
.int_value
;
545 *(uint32_t *)ptr
= value
.uint_value
;
549 *(float *)ptr
= (float)value
.float_value
;
553 // Handled by cliSet directly
558 static void cliPrompt(void)
561 bufWriterFlush(cliWriter
);
564 static void cliShowParseError(void)
566 cliPrintErrorLinef("Parse error");
569 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
571 cliPrintErrorLinef("%s must be between %d and %d", name
, min
, max
);
574 static const char *nextArg(const char *currentArg
)
576 const char *ptr
= strchr(currentArg
, ' ');
577 while (ptr
&& *ptr
== ' ') {
584 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
586 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
589 int val
= fastA2I(ptr
);
590 val
= CHANNEL_VALUE_TO_STEP(val
);
591 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
593 range
->startStep
= val
;
595 range
->endStep
= val
;
597 (*validArgumentCount
)++;
605 // Check if a string's length is zero
606 static bool isEmpty(const char *string
)
608 return (string
== NULL
|| *string
== '\0') ? true : false;
611 #if defined(USE_ASSERT)
612 static void cliAssert(char *cmdline
)
616 if (assertFailureLine
) {
617 if (assertFailureFile
) {
618 cliPrintErrorLinef("Assertion failed at line %d, file %s", assertFailureLine
, assertFailureFile
);
621 cliPrintErrorLinef("Assertion failed at line %d", assertFailureLine
);
624 if (commandBatchActive
) {
625 commandBatchError
= true;
630 cliPrintLine("No assert() failed");
635 static void printAux(uint8_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
)
637 const char *format
= "aux %u %u %u %u %u";
638 // print out aux channel settings
639 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
640 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
641 bool equalsDefault
= false;
642 if (defaultModeActivationConditions
) {
643 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
644 equalsDefault
= mac
->modeId
== macDefault
->modeId
645 && mac
->auxChannelIndex
== macDefault
->auxChannelIndex
646 && mac
->range
.startStep
== macDefault
->range
.startStep
647 && mac
->range
.endStep
== macDefault
->range
.endStep
;
648 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
651 macDefault
->auxChannelIndex
,
652 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
653 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
)
656 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
659 mac
->auxChannelIndex
,
660 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
661 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
)
666 static void cliAux(char *cmdline
)
671 if (isEmpty(cmdline
)) {
672 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
);
676 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
677 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
678 uint8_t validArgumentCount
= 0;
682 if (val
>= 0 && val
< CHECKBOX_ITEM_COUNT
) {
684 validArgumentCount
++;
690 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
691 mac
->auxChannelIndex
= val
;
692 validArgumentCount
++;
695 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
697 if (validArgumentCount
!= 4) {
698 memset(mac
, 0, sizeof(modeActivationCondition_t
));
701 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
706 static void printSerial(uint8_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
)
708 const char *format
= "serial %d %d %ld %ld %ld %ld";
709 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
710 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
713 bool equalsDefault
= false;
714 if (serialConfigDefault
) {
715 equalsDefault
= serialConfig
->portConfigs
[i
].identifier
== serialConfigDefault
->portConfigs
[i
].identifier
716 && serialConfig
->portConfigs
[i
].functionMask
== serialConfigDefault
->portConfigs
[i
].functionMask
717 && serialConfig
->portConfigs
[i
].msp_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
718 && serialConfig
->portConfigs
[i
].gps_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
719 && serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
720 && serialConfig
->portConfigs
[i
].peripheral_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].peripheral_baudrateIndex
;
721 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
722 serialConfigDefault
->portConfigs
[i
].identifier
,
723 serialConfigDefault
->portConfigs
[i
].functionMask
,
724 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
725 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
726 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
727 baudRates
[serialConfigDefault
->portConfigs
[i
].peripheral_baudrateIndex
]
730 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
731 serialConfig
->portConfigs
[i
].identifier
,
732 serialConfig
->portConfigs
[i
].functionMask
,
733 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
734 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
735 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
736 baudRates
[serialConfig
->portConfigs
[i
].peripheral_baudrateIndex
]
741 static void cliSerial(char *cmdline
)
743 if (isEmpty(cmdline
)) {
744 printSerial(DUMP_MASTER
, serialConfig(), NULL
);
747 serialPortConfig_t portConfig
;
749 serialPortConfig_t
*currentConfig
;
751 uint8_t validArgumentCount
= 0;
753 const char *ptr
= cmdline
;
755 int val
= fastA2I(ptr
++);
756 currentConfig
= serialFindPortConfiguration(val
);
757 if (!currentConfig
) {
759 cliPrintErrorLinef("Invalid port ID %d", val
);
762 memcpy(&portConfig
, currentConfig
, sizeof(portConfig
));
763 validArgumentCount
++;
772 portConfig
.functionMask
|= (1 << val
);
778 portConfig
.functionMask
&= 0xFFFFFFFF ^ (1 << val
);
783 portConfig
.functionMask
= val
& 0xFFFFFFFF;
786 validArgumentCount
++;
789 for (int i
= 0; i
< 4; i
++) {
797 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
798 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
804 if (baudRateIndex
< BAUD_1200
|| baudRateIndex
> BAUD_2470000
) {
807 portConfig
.msp_baudrateIndex
= baudRateIndex
;
810 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
813 portConfig
.gps_baudrateIndex
= baudRateIndex
;
816 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
819 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
822 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_250000
) {
825 portConfig
.peripheral_baudrateIndex
= baudRateIndex
;
829 validArgumentCount
++;
832 if (validArgumentCount
< 2) {
837 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
840 #ifdef USE_SERIAL_PASSTHROUGH
841 static void cliSerialPassthrough(char *cmdline
)
845 if (isEmpty(cmdline
)) {
853 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
856 while (tok
!= NULL
) {
865 if (strstr(tok
, "rx") || strstr(tok
, "RX"))
867 if (strstr(tok
, "tx") || strstr(tok
, "TX"))
872 tok
= strtok_r(NULL
, " ", &saveptr
);
875 serialPort_t
*passThroughPort
;
876 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
877 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
879 tfp_printf("Port %d is closed, must specify baud.\r\n", id
);
885 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
, NULL
,
887 SERIAL_NOT_INVERTED
);
888 if (!passThroughPort
) {
889 tfp_printf("Port %d could not be opened.\r\n", id
);
892 tfp_printf("Port %d opened, baud = %u.\r\n", id
, (unsigned)baud
);
894 passThroughPort
= passThroughPortUsage
->serialPort
;
895 // If the user supplied a mode, override the port's mode, otherwise
896 // leave the mode unchanged. serialPassthrough() handles one-way ports.
897 tfp_printf("Port %d already open.\r\n", id
);
898 if (mode
&& passThroughPort
->mode
!= mode
) {
899 tfp_printf("Adjusting mode from %d to %d.\r\n",
900 passThroughPort
->mode
, mode
);
901 serialSetMode(passThroughPort
, mode
);
903 // If this port has a rx callback associated we need to remove it now.
904 // Otherwise no data will be pushed in the serial port buffer!
905 if (passThroughPort
->rxCallback
) {
906 tfp_printf("Removing rxCallback\r\n");
907 passThroughPort
->rxCallback
= 0;
911 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id
);
913 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
917 static void printAdjustmentRange(uint8_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
)
919 const char *format
= "adjrange %u %u %u %u %u %u %u";
920 // print out adjustment ranges channel settings
921 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
922 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
923 bool equalsDefault
= false;
924 if (defaultAdjustmentRanges
) {
925 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
926 equalsDefault
= ar
->auxChannelIndex
== arDefault
->auxChannelIndex
927 && ar
->range
.startStep
== arDefault
->range
.startStep
928 && ar
->range
.endStep
== arDefault
->range
.endStep
929 && ar
->adjustmentFunction
== arDefault
->adjustmentFunction
930 && ar
->auxSwitchChannelIndex
== arDefault
->auxSwitchChannelIndex
931 && ar
->adjustmentIndex
== arDefault
->adjustmentIndex
;
932 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
934 arDefault
->adjustmentIndex
,
935 arDefault
->auxChannelIndex
,
936 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
937 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
938 arDefault
->adjustmentFunction
,
939 arDefault
->auxSwitchChannelIndex
942 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
946 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
947 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
948 ar
->adjustmentFunction
,
949 ar
->auxSwitchChannelIndex
954 static void cliAdjustmentRange(char *cmdline
)
959 if (isEmpty(cmdline
)) {
960 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
);
964 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
965 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
966 uint8_t validArgumentCount
= 0;
971 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
972 ar
->adjustmentIndex
= val
;
973 validArgumentCount
++;
979 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
980 ar
->auxChannelIndex
= val
;
981 validArgumentCount
++;
985 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
990 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
991 ar
->adjustmentFunction
= val
;
992 validArgumentCount
++;
998 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
999 ar
->auxSwitchChannelIndex
= val
;
1000 validArgumentCount
++;
1004 if (validArgumentCount
!= 6) {
1005 memset(ar
, 0, sizeof(adjustmentRange_t
));
1006 cliShowParseError();
1009 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1014 static void printMotorMix(uint8_t dumpMask
, const motorMixer_t
*primaryMotorMixer
, const motorMixer_t
*defaultprimaryMotorMixer
)
1016 const char *format
= "mmix %d %s %s %s %s";
1017 char buf0
[FTOA_BUFFER_SIZE
];
1018 char buf1
[FTOA_BUFFER_SIZE
];
1019 char buf2
[FTOA_BUFFER_SIZE
];
1020 char buf3
[FTOA_BUFFER_SIZE
];
1021 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1022 if (primaryMotorMixer
[i
].throttle
== 0.0f
)
1024 const float thr
= primaryMotorMixer
[i
].throttle
;
1025 const float roll
= primaryMotorMixer
[i
].roll
;
1026 const float pitch
= primaryMotorMixer
[i
].pitch
;
1027 const float yaw
= primaryMotorMixer
[i
].yaw
;
1028 bool equalsDefault
= false;
1029 if (defaultprimaryMotorMixer
) {
1030 const float thrDefault
= defaultprimaryMotorMixer
[i
].throttle
;
1031 const float rollDefault
= defaultprimaryMotorMixer
[i
].roll
;
1032 const float pitchDefault
= defaultprimaryMotorMixer
[i
].pitch
;
1033 const float yawDefault
= defaultprimaryMotorMixer
[i
].yaw
;
1034 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1036 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1038 ftoa(thrDefault
, buf0
),
1039 ftoa(rollDefault
, buf1
),
1040 ftoa(pitchDefault
, buf2
),
1041 ftoa(yawDefault
, buf3
));
1043 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1052 static void cliMotorMix(char *cmdline
)
1057 if (isEmpty(cmdline
)) {
1058 printMotorMix(DUMP_MASTER
, primaryMotorMixer(0), NULL
);
1059 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1060 // erase custom mixer
1061 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1062 primaryMotorMixerMutable(i
)->throttle
= 0.0f
;
1066 uint32_t i
= fastA2I(ptr
); // get motor number
1067 if (i
< MAX_SUPPORTED_MOTORS
) {
1070 primaryMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1075 primaryMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1080 primaryMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1085 primaryMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1089 cliShowParseError();
1091 printMotorMix(DUMP_MASTER
, primaryMotorMixer(0), NULL
);
1094 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
1099 static void printRxRange(uint8_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
)
1101 const char *format
= "rxrange %u %u %u";
1102 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1103 bool equalsDefault
= false;
1104 if (defaultChannelRangeConfigs
) {
1105 equalsDefault
= channelRangeConfigs
[i
].min
== defaultChannelRangeConfigs
[i
].min
1106 && channelRangeConfigs
[i
].max
== defaultChannelRangeConfigs
[i
].max
;
1107 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1109 defaultChannelRangeConfigs
[i
].min
,
1110 defaultChannelRangeConfigs
[i
].max
1113 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1115 channelRangeConfigs
[i
].min
,
1116 channelRangeConfigs
[i
].max
1121 static void cliRxRange(char *cmdline
)
1123 int i
, validArgumentCount
= 0;
1126 if (isEmpty(cmdline
)) {
1127 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
);
1128 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1129 resetAllRxChannelRangeConfigurations();
1133 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1134 int rangeMin
, rangeMax
;
1138 rangeMin
= fastA2I(ptr
);
1139 validArgumentCount
++;
1144 rangeMax
= fastA2I(ptr
);
1145 validArgumentCount
++;
1148 if (validArgumentCount
!= 2) {
1149 cliShowParseError();
1150 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1151 cliShowParseError();
1153 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1154 channelRangeConfig
->min
= rangeMin
;
1155 channelRangeConfig
->max
= rangeMax
;
1158 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT
- 1);
1163 #ifdef USE_TEMPERATURE_SENSOR
1164 static void printTempSensor(uint8_t dumpMask
, const tempSensorConfig_t
*tempSensorConfigs
, const tempSensorConfig_t
*defaultTempSensorConfigs
)
1166 const char *format
= "temp_sensor %u %u %s %d %d %u %s";
1167 for (uint8_t i
= 0; i
< MAX_TEMP_SENSORS
; i
++) {
1168 bool equalsDefault
= false;
1169 char label
[5], hex_address
[17];
1170 strncpy(label
, tempSensorConfigs
[i
].label
, TEMPERATURE_LABEL_LEN
);
1172 tempSensorAddressToString(tempSensorConfigs
[i
].address
, hex_address
);
1173 if (defaultTempSensorConfigs
) {
1174 equalsDefault
= tempSensorConfigs
[i
].type
== defaultTempSensorConfigs
[i
].type
1175 && tempSensorConfigs
[i
].address
== defaultTempSensorConfigs
[i
].address
1176 && tempSensorConfigs
[i
].osdSymbol
== defaultTempSensorConfigs
[i
].osdSymbol
1177 && !memcmp(tempSensorConfigs
[i
].label
, defaultTempSensorConfigs
[i
].label
, TEMPERATURE_LABEL_LEN
)
1178 && tempSensorConfigs
[i
].alarm_min
== defaultTempSensorConfigs
[i
].alarm_min
1179 && tempSensorConfigs
[i
].alarm_max
== defaultTempSensorConfigs
[i
].alarm_max
;
1180 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1182 defaultTempSensorConfigs
[i
].type
,
1184 defaultTempSensorConfigs
[i
].alarm_min
,
1185 defaultTempSensorConfigs
[i
].alarm_max
,
1190 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1192 tempSensorConfigs
[i
].type
,
1194 tempSensorConfigs
[i
].alarm_min
,
1195 tempSensorConfigs
[i
].alarm_max
,
1196 tempSensorConfigs
[i
].osdSymbol
,
1202 static void cliTempSensor(char *cmdline
)
1204 if (isEmpty(cmdline
)) {
1205 printTempSensor(DUMP_MASTER
, tempSensorConfig(0), NULL
);
1206 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1207 resetTempSensorConfig();
1210 const char *ptr
= cmdline
, *label
;
1211 int16_t type
, alarm_min
, alarm_max
;
1212 bool addressValid
= false;
1215 uint8_t validArgumentCount
= 0;
1217 if (i
>= 0 && i
< MAX_TEMP_SENSORS
) {
1221 type
= fastA2I(ptr
);
1222 validArgumentCount
++;
1227 addressValid
= tempSensorStringToAddress(ptr
, &address
);
1228 validArgumentCount
++;
1233 alarm_min
= fastA2I(ptr
);
1234 validArgumentCount
++;
1239 alarm_max
= fastA2I(ptr
);
1240 validArgumentCount
++;
1245 osdSymbol
= fastA2I(ptr
);
1246 validArgumentCount
++;
1249 label
= nextArg(ptr
);
1251 ++validArgumentCount
;
1255 if (validArgumentCount
< 4) {
1256 cliShowParseError();
1257 } 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
) {
1258 cliShowParseError();
1260 tempSensorConfig_t
*sensorConfig
= tempSensorConfigMutable(i
);
1261 sensorConfig
->type
= type
;
1262 sensorConfig
->address
= address
;
1263 sensorConfig
->alarm_min
= alarm_min
;
1264 sensorConfig
->alarm_max
= alarm_max
;
1265 sensorConfig
->osdSymbol
= osdSymbol
;
1266 for (uint8_t index
= 0; index
< TEMPERATURE_LABEL_LEN
; ++index
) {
1267 sensorConfig
->label
[index
] = toupper(label
[index
]);
1268 if (label
[index
] == '\0') break;
1272 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS
- 1);
1278 #if defined(USE_NAV) && defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1279 static void printWaypoints(uint8_t dumpMask
, const navWaypoint_t
*navWaypoint
, const navWaypoint_t
*defaultNavWaypoint
)
1281 cliPrintLinef("#wp %d %svalid", posControl
.waypointCount
, posControl
.waypointListValid
? "" : "in"); //int8_t bool
1282 const char *format
= "wp %u %u %d %d %d %d %u"; //uint8_t action; int32_t lat; int32_t lon; int32_t alt; int16_t p1; uint8_t flag
1283 for (uint8_t i
= 0; i
< NAV_MAX_WAYPOINTS
; i
++) {
1284 bool equalsDefault
= false;
1285 if (defaultNavWaypoint
) {
1286 equalsDefault
= navWaypoint
[i
].action
== defaultNavWaypoint
[i
].action
1287 && navWaypoint
[i
].lat
== defaultNavWaypoint
[i
].lat
1288 && navWaypoint
[i
].lon
== defaultNavWaypoint
[i
].lon
1289 && navWaypoint
[i
].alt
== defaultNavWaypoint
[i
].alt
1290 && navWaypoint
[i
].p1
== defaultNavWaypoint
[i
].p1
1291 && navWaypoint
[i
].flag
== defaultNavWaypoint
[i
].flag
;
1292 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1294 defaultNavWaypoint
[i
].action
,
1295 defaultNavWaypoint
[i
].lat
,
1296 defaultNavWaypoint
[i
].lon
,
1297 defaultNavWaypoint
[i
].alt
,
1298 defaultNavWaypoint
[i
].p1
,
1299 defaultNavWaypoint
[i
].flag
1302 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1304 navWaypoint
[i
].action
,
1314 static void cliWaypoints(char *cmdline
)
1316 if (isEmpty(cmdline
)) {
1317 printWaypoints(DUMP_MASTER
, posControl
.waypointList
, NULL
);
1318 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1319 resetWaypointList();
1320 } else if (sl_strcasecmp(cmdline
, "load") == 0) {
1321 loadNonVolatileWaypointList();
1322 } else if (sl_strcasecmp(cmdline
, "save") == 0) {
1323 posControl
.waypointListValid
= false;
1324 for (int i
= 0; i
< NAV_MAX_WAYPOINTS
; i
++) {
1325 if (!(posControl
.waypointList
[i
].action
== NAV_WP_ACTION_WAYPOINT
|| posControl
.waypointList
[i
].action
== NAV_WP_ACTION_RTH
)) break;
1326 if (posControl
.waypointList
[i
].flag
== NAV_WP_FLAG_LAST
) {
1327 posControl
.waypointCount
= i
+ 1;
1328 posControl
.waypointListValid
= true;
1332 if (posControl
.waypointListValid
) {
1333 saveNonVolatileWaypointList();
1335 cliShowParseError();
1339 uint8_t action
, flag
;
1340 int32_t lat
, lon
, alt
;
1341 uint8_t validArgumentCount
= 0;
1342 const char *ptr
= cmdline
;
1344 if (i
>= 0 && i
< NAV_MAX_WAYPOINTS
) {
1347 action
= fastA2I(ptr
);
1348 validArgumentCount
++;
1353 validArgumentCount
++;
1358 validArgumentCount
++;
1363 validArgumentCount
++;
1368 validArgumentCount
++;
1372 flag
= fastA2I(ptr
);
1373 validArgumentCount
++;
1375 if (validArgumentCount
< 4) {
1376 cliShowParseError();
1377 } else if (!(action
== 0 || action
== NAV_WP_ACTION_WAYPOINT
|| action
== NAV_WP_ACTION_RTH
) || (p1
< 0) || !(flag
== 0 || flag
== NAV_WP_FLAG_LAST
)) {
1378 cliShowParseError();
1380 posControl
.waypointList
[i
].action
= action
;
1381 posControl
.waypointList
[i
].lat
= lat
;
1382 posControl
.waypointList
[i
].lon
= lon
;
1383 posControl
.waypointList
[i
].alt
= alt
;
1384 posControl
.waypointList
[i
].p1
= p1
;
1385 posControl
.waypointList
[i
].flag
= flag
;
1388 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS
- 1);
1395 #ifdef USE_LED_STRIP
1396 static void printLed(uint8_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
)
1398 const char *format
= "led %u %s";
1399 char ledConfigBuffer
[20];
1400 char ledConfigDefaultBuffer
[20];
1401 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1402 ledConfig_t ledConfig
= ledConfigs
[i
];
1403 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1404 bool equalsDefault
= false;
1405 if (defaultLedConfigs
) {
1406 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1407 equalsDefault
= ledConfig
== ledConfigDefault
;
1408 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1409 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1411 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1415 static void cliLed(char *cmdline
)
1420 if (isEmpty(cmdline
)) {
1421 printLed(DUMP_MASTER
, ledStripConfig()->ledConfigs
, NULL
);
1425 if (i
< LED_MAX_STRIP_LENGTH
) {
1426 ptr
= nextArg(cmdline
);
1427 if (!parseLedStripConfig(i
, ptr
)) {
1428 cliShowParseError();
1431 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH
- 1);
1436 static void printColor(uint8_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
)
1438 const char *format
= "color %u %d,%u,%u";
1439 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1440 const hsvColor_t
*color
= &colors
[i
];
1441 bool equalsDefault
= false;
1442 if (defaultColors
) {
1443 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1444 equalsDefault
= color
->h
== colorDefault
->h
1445 && color
->s
== colorDefault
->s
1446 && color
->v
== colorDefault
->v
;
1447 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1449 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1453 static void cliColor(char *cmdline
)
1455 if (isEmpty(cmdline
)) {
1456 printColor(DUMP_MASTER
, ledStripConfig()->colors
, NULL
);
1458 const char *ptr
= cmdline
;
1459 const int i
= fastA2I(ptr
);
1460 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1461 ptr
= nextArg(cmdline
);
1462 if (!parseColor(i
, ptr
)) {
1463 cliShowParseError();
1466 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1471 static void printModeColor(uint8_t dumpMask
, const ledStripConfig_t
*ledStripConfig
, const ledStripConfig_t
*defaultLedStripConfig
)
1473 const char *format
= "mode_color %u %u %u";
1474 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1475 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1476 int colorIndex
= ledStripConfig
->modeColors
[i
].color
[j
];
1477 bool equalsDefault
= false;
1478 if (defaultLedStripConfig
) {
1479 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1480 equalsDefault
= colorIndex
== colorIndexDefault
;
1481 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1483 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1487 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1488 const int colorIndex
= ledStripConfig
->specialColors
.color
[j
];
1489 bool equalsDefault
= false;
1490 if (defaultLedStripConfig
) {
1491 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1492 equalsDefault
= colorIndex
== colorIndexDefault
;
1493 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1495 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1499 static void cliModeColor(char *cmdline
)
1503 if (isEmpty(cmdline
)) {
1504 printModeColor(DUMP_MASTER
, ledStripConfig(), NULL
);
1506 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1507 int args
[ARGS_COUNT
];
1509 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1510 while (ptr
&& argNo
< ARGS_COUNT
) {
1511 args
[argNo
++] = fastA2I(ptr
);
1512 ptr
= strtok_r(NULL
, " ", &saveptr
);
1515 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1516 cliShowParseError();
1520 int modeIdx
= args
[MODE
];
1521 int funIdx
= args
[FUNCTION
];
1522 int color
= args
[COLOR
];
1523 if (!setModeColor(modeIdx
, funIdx
, color
)) {
1524 cliShowParseError();
1527 // values are validated
1528 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1533 static void printServo(uint8_t dumpMask
, const servoParam_t
*servoParam
, const servoParam_t
*defaultServoParam
)
1535 // print out servo settings
1536 const char *format
= "servo %u %d %d %d %d";
1537 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1538 const servoParam_t
*servoConf
= &servoParam
[i
];
1539 bool equalsDefault
= false;
1540 if (defaultServoParam
) {
1541 const servoParam_t
*servoConfDefault
= &defaultServoParam
[i
];
1542 equalsDefault
= servoConf
->min
== servoConfDefault
->min
1543 && servoConf
->max
== servoConfDefault
->max
1544 && servoConf
->middle
== servoConfDefault
->middle
1545 && servoConf
->rate
== servoConfDefault
->rate
;
1546 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1548 servoConfDefault
->min
,
1549 servoConfDefault
->max
,
1550 servoConfDefault
->middle
,
1551 servoConfDefault
->rate
1554 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1564 static void cliServo(char *cmdline
)
1566 enum { SERVO_ARGUMENT_COUNT
= 5 };
1567 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1569 servoParam_t
*servo
;
1574 if (isEmpty(cmdline
)) {
1575 printServo(DUMP_MASTER
, servoParams(0), NULL
);
1577 int validArgumentCount
= 0;
1581 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1583 // If command line doesn't fit the format, don't modify the config
1585 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1586 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1587 cliShowParseError();
1591 arguments
[validArgumentCount
++] = fastA2I(ptr
);
1595 } while (*ptr
>= '0' && *ptr
<= '9');
1596 } else if (*ptr
== ' ') {
1599 cliShowParseError();
1604 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
};
1606 i
= arguments
[INDEX
];
1608 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1609 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1610 cliShowParseError();
1614 servo
= servoParamsMutable(i
);
1617 arguments
[MIN
] < SERVO_OUTPUT_MIN
|| arguments
[MIN
] > SERVO_OUTPUT_MAX
||
1618 arguments
[MAX
] < SERVO_OUTPUT_MIN
|| arguments
[MAX
] > SERVO_OUTPUT_MAX
||
1619 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
1620 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
1621 arguments
[RATE
] < -125 || arguments
[RATE
] > 125
1623 cliShowParseError();
1627 servo
->min
= arguments
[MIN
];
1628 servo
->max
= arguments
[MAX
];
1629 servo
->middle
= arguments
[MIDDLE
];
1630 servo
->rate
= arguments
[RATE
];
1634 static void printServoMix(uint8_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
)
1636 const char *format
= "smix %d %d %d %d %d %d";
1637 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1638 const servoMixer_t customServoMixer
= customServoMixers
[i
];
1639 if (customServoMixer
.rate
== 0) {
1643 bool equalsDefault
= false;
1644 if (defaultCustomServoMixers
) {
1645 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
1646 equalsDefault
= customServoMixer
.targetChannel
== customServoMixerDefault
.targetChannel
1647 && customServoMixer
.inputSource
== customServoMixerDefault
.inputSource
1648 && customServoMixer
.rate
== customServoMixerDefault
.rate
1649 && customServoMixer
.speed
== customServoMixerDefault
.speed
1650 #ifdef USE_LOGIC_CONDITIONS
1651 && customServoMixer
.conditionId
== customServoMixerDefault
.conditionId
1655 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1657 customServoMixerDefault
.targetChannel
,
1658 customServoMixerDefault
.inputSource
,
1659 customServoMixerDefault
.rate
,
1660 customServoMixerDefault
.speed
,
1661 #ifdef USE_LOGIC_CONDITIONS
1662 customServoMixer
.conditionId
1668 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1670 customServoMixer
.targetChannel
,
1671 customServoMixer
.inputSource
,
1672 customServoMixer
.rate
,
1673 customServoMixer
.speed
,
1674 #ifdef USE_LOGIC_CONDITIONS
1675 customServoMixer
.conditionId
1683 static void cliServoMix(char *cmdline
)
1686 int args
[6], check
= 0;
1687 uint8_t len
= strlen(cmdline
);
1690 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
);
1691 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1692 // erase custom mixer
1693 pgResetCopy(customServoMixersMutable(0), PG_SERVO_MIXER
);
1695 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, CONDITION
, ARGS_COUNT
};
1696 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1697 args
[CONDITION
] = -1;
1698 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1699 args
[check
++] = fastA2I(ptr
);
1700 ptr
= strtok_r(NULL
, " ", &saveptr
);
1703 if (ptr
!= NULL
|| (check
< ARGS_COUNT
- 1)) {
1704 cliShowParseError();
1708 int32_t i
= args
[RULE
];
1710 i
>= 0 && i
< MAX_SERVO_RULES
&&
1711 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
1712 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
1713 args
[RATE
] >= -1000 && args
[RATE
] <= 1000 &&
1714 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
1715 args
[CONDITION
] >= -1 && args
[CONDITION
] < MAX_LOGIC_CONDITIONS
1717 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
1718 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
1719 customServoMixersMutable(i
)->rate
= args
[RATE
];
1720 customServoMixersMutable(i
)->speed
= args
[SPEED
];
1721 #ifdef USE_LOGIC_CONDITIONS
1722 customServoMixersMutable(i
)->conditionId
= args
[CONDITION
];
1726 cliShowParseError();
1731 #ifdef USE_LOGIC_CONDITIONS
1733 static void printLogic(uint8_t dumpMask
, const logicCondition_t
*logicConditions
, const logicCondition_t
*defaultLogicConditions
)
1735 const char *format
= "logic %d %d %d %d %d %d %d %d";
1736 for (uint32_t i
= 0; i
< MAX_LOGIC_CONDITIONS
; i
++) {
1737 const logicCondition_t logic
= logicConditions
[i
];
1739 bool equalsDefault
= false;
1740 if (defaultLogicConditions
) {
1741 logicCondition_t defaultValue
= defaultLogicConditions
[i
];
1743 logic
.enabled
== defaultValue
.enabled
&&
1744 logic
.operation
== defaultValue
.operation
&&
1745 logic
.operandA
.type
== defaultValue
.operandA
.type
&&
1746 logic
.operandA
.value
== defaultValue
.operandA
.value
&&
1747 logic
.operandB
.type
== defaultValue
.operandB
.type
&&
1748 logic
.operandB
.value
== defaultValue
.operandB
.value
&&
1749 logic
.flags
== defaultValue
.flags
;
1751 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1755 logic
.operandA
.type
,
1756 logic
.operandA
.value
,
1757 logic
.operandB
.type
,
1758 logic
.operandB
.value
,
1762 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1766 logic
.operandA
.type
,
1767 logic
.operandA
.value
,
1768 logic
.operandB
.type
,
1769 logic
.operandB
.value
,
1775 static void cliLogic(char *cmdline
) {
1777 int args
[8], check
= 0;
1778 uint8_t len
= strlen(cmdline
);
1781 printLogic(DUMP_MASTER
, logicConditions(0), NULL
);
1782 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1783 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS
);
1796 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1797 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1798 args
[check
++] = fastA2I(ptr
);
1799 ptr
= strtok_r(NULL
, " ", &saveptr
);
1802 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
1803 cliShowParseError();
1807 int32_t i
= args
[INDEX
];
1809 i
>= 0 && i
< MAX_LOGIC_CONDITIONS
&&
1810 args
[ENABLED
] >= 0 && args
[ENABLED
] <= 1 &&
1811 args
[OPERATION
] >= 0 && args
[OPERATION
] < LOGIC_CONDITION_LAST
&&
1812 args
[OPERAND_A_TYPE
] >= 0 && args
[OPERAND_A_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
1813 args
[OPERAND_A_VALUE
] >= -1000000 && args
[OPERAND_A_VALUE
] <= 1000000 &&
1814 args
[OPERAND_B_TYPE
] >= 0 && args
[OPERAND_B_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
1815 args
[OPERAND_B_VALUE
] >= -1000000 && args
[OPERAND_B_VALUE
] <= 1000000 &&
1816 args
[FLAGS
] >= 0 && args
[FLAGS
] <= 255
1819 logicConditionsMutable(i
)->enabled
= args
[ENABLED
];
1820 logicConditionsMutable(i
)->operation
= args
[OPERATION
];
1821 logicConditionsMutable(i
)->operandA
.type
= args
[OPERAND_A_TYPE
];
1822 logicConditionsMutable(i
)->operandA
.value
= args
[OPERAND_A_VALUE
];
1823 logicConditionsMutable(i
)->operandB
.type
= args
[OPERAND_B_TYPE
];
1824 logicConditionsMutable(i
)->operandB
.value
= args
[OPERAND_B_VALUE
];
1825 logicConditionsMutable(i
)->flags
= args
[FLAGS
];
1829 cliShowParseError();
1835 #ifdef USE_GLOBAL_FUNCTIONS
1837 static void printGlobalFunctions(uint8_t dumpMask
, const globalFunction_t
*globalFunctions
, const globalFunction_t
*defaultGlobalFunctions
)
1839 const char *format
= "gf %d %d %d %d %d %d %d";
1840 for (uint32_t i
= 0; i
< MAX_GLOBAL_FUNCTIONS
; i
++) {
1841 const globalFunction_t gf
= globalFunctions
[i
];
1843 bool equalsDefault
= false;
1844 if (defaultGlobalFunctions
) {
1845 globalFunction_t defaultValue
= defaultGlobalFunctions
[i
];
1847 gf
.enabled
== defaultValue
.enabled
&&
1848 gf
.conditionId
== defaultValue
.conditionId
&&
1849 gf
.action
== defaultValue
.action
&&
1850 gf
.withValue
.type
== defaultValue
.withValue
.type
&&
1851 gf
.withValue
.value
== defaultValue
.withValue
.value
&&
1852 gf
.flags
== defaultValue
.flags
;
1854 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1864 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1876 static void cliGlobalFunctions(char *cmdline
) {
1878 int args
[7], check
= 0;
1879 uint8_t len
= strlen(cmdline
);
1882 printGlobalFunctions(DUMP_MASTER
, globalFunctions(0), NULL
);
1883 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1884 pgResetCopy(globalFunctionsMutable(0), PG_GLOBAL_FUNCTIONS
);
1896 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1897 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1898 args
[check
++] = fastA2I(ptr
);
1899 ptr
= strtok_r(NULL
, " ", &saveptr
);
1902 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
1903 cliShowParseError();
1907 int32_t i
= args
[INDEX
];
1909 i
>= 0 && i
< MAX_GLOBAL_FUNCTIONS
&&
1910 args
[ENABLED
] >= 0 && args
[ENABLED
] <= 1 &&
1911 args
[CONDITION_ID
] >= 0 && args
[CONDITION_ID
] < MAX_LOGIC_CONDITIONS
&&
1912 args
[ACTION
] >= 0 && args
[ACTION
] < GLOBAL_FUNCTION_ACTION_LAST
&&
1913 args
[VALUE_TYPE
] >= 0 && args
[VALUE_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
1914 args
[VALUE_VALUE
] >= -1000000 && args
[VALUE_VALUE
] <= 1000000 &&
1915 args
[FLAGS
] >= 0 && args
[FLAGS
] <= 255
1918 globalFunctionsMutable(i
)->enabled
= args
[ENABLED
];
1919 globalFunctionsMutable(i
)->conditionId
= args
[CONDITION_ID
];
1920 globalFunctionsMutable(i
)->action
= args
[ACTION
];
1921 globalFunctionsMutable(i
)->withValue
.type
= args
[VALUE_TYPE
];
1922 globalFunctionsMutable(i
)->withValue
.value
= args
[VALUE_VALUE
];
1923 globalFunctionsMutable(i
)->flags
= args
[FLAGS
];
1925 cliGlobalFunctions("");
1927 cliShowParseError();
1935 static void cliWriteBytes(const uint8_t *buffer
, int count
)
1944 static void cliSdInfo(char *cmdline
)
1948 cliPrint("SD card: ");
1950 if (!sdcard_isInserted()) {
1951 cliPrintLine("None inserted");
1955 if (!sdcard_isInitialized()) {
1956 cliPrintLine("Startup failed");
1960 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
1962 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1963 metadata
->manufacturerID
,
1964 metadata
->numBlocks
/ 2, /* One block is half a kB */
1965 metadata
->productionMonth
,
1966 metadata
->productionYear
,
1967 metadata
->productRevisionMajor
,
1968 metadata
->productRevisionMinor
1971 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
1973 cliPrint("'\r\n" "Filesystem: ");
1975 switch (afatfs_getFilesystemState()) {
1976 case AFATFS_FILESYSTEM_STATE_READY
:
1979 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
1980 cliPrint("Initializing");
1982 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
1983 case AFATFS_FILESYSTEM_STATE_FATAL
:
1986 switch (afatfs_getLastError()) {
1987 case AFATFS_ERROR_BAD_MBR
:
1988 cliPrint(" - no FAT MBR partitions");
1990 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
1991 cliPrint(" - bad FAT header");
1993 case AFATFS_ERROR_GENERIC
:
1994 case AFATFS_ERROR_NONE
:
1995 ; // Nothing more detailed to print
2007 static void cliFlashInfo(char *cmdline
)
2009 const flashGeometry_t
*layout
= flashfsGetGeometry();
2013 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
2014 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
, flashfsGetOffset());
2017 static void cliFlashErase(char *cmdline
)
2021 cliPrintLine("Erasing...");
2022 flashfsEraseCompletely();
2024 while (!flashfsIsReady()) {
2028 cliPrintLine("Done.");
2031 #ifdef USE_FLASH_TOOLS
2033 static void cliFlashWrite(char *cmdline
)
2035 const uint32_t address
= fastA2I(cmdline
);
2036 const char *text
= strchr(cmdline
, ' ');
2039 cliShowParseError();
2041 flashfsSeekAbs(address
);
2042 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2045 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2049 static void cliFlashRead(char *cmdline
)
2051 uint32_t address
= fastA2I(cmdline
);
2053 const char *nextArg
= strchr(cmdline
, ' ');
2056 cliShowParseError();
2058 uint32_t length
= fastA2I(nextArg
);
2060 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2063 while (length
> 0) {
2064 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2066 for (int i
= 0; i
< bytesRead
; i
++) {
2067 cliWrite(buffer
[i
]);
2070 length
-= bytesRead
;
2071 address
+= bytesRead
;
2073 if (bytesRead
== 0) {
2074 //Assume we reached the end of the volume or something fatal happened
2086 static void printOsdLayout(uint8_t dumpMask
, const osdConfig_t
*osdConfig
, const osdConfig_t
*osdConfigDefault
, int layout
, int item
)
2088 // "<layout> <item> <col> <row> <visible>"
2089 const char *format
= "osd_layout %d %d %d %d %c";
2090 for (int ii
= 0; ii
< OSD_LAYOUT_COUNT
; ii
++) {
2091 if (layout
>= 0 && layout
!= ii
) {
2094 const uint16_t *layoutItems
= osdConfig
->item_pos
[ii
];
2095 const uint16_t *defaultLayoutItems
= osdConfigDefault
->item_pos
[ii
];
2096 for (int jj
= 0; jj
< OSD_ITEM_COUNT
; jj
++) {
2097 if (item
>= 0 && item
!= jj
) {
2100 bool equalsDefault
= layoutItems
[jj
] == defaultLayoutItems
[jj
];
2101 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2103 OSD_X(defaultLayoutItems
[jj
]),
2104 OSD_Y(defaultLayoutItems
[jj
]),
2105 OSD_VISIBLE(defaultLayoutItems
[jj
]) ? 'V' : 'H');
2107 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2109 OSD_X(layoutItems
[jj
]),
2110 OSD_Y(layoutItems
[jj
]),
2111 OSD_VISIBLE(layoutItems
[jj
]) ? 'V' : 'H');
2116 static void cliOsdLayout(char *cmdline
)
2124 bool visible
= false;
2125 char *tok
= strtok_r(cmdline
, " ", &saveptr
);
2129 for (ii
= 0; tok
!= NULL
; ii
++, tok
= strtok_r(NULL
, " ", &saveptr
)) {
2132 layout
= fastA2I(tok
);
2133 if (layout
< 0 || layout
>= OSD_LAYOUT_COUNT
) {
2134 cliShowParseError();
2139 item
= fastA2I(tok
);
2140 if (item
< 0 || item
>= OSD_ITEM_COUNT
) {
2141 cliShowParseError();
2147 if (col
< 0 || col
> OSD_X(OSD_POS_MAX
)) {
2148 cliShowParseError();
2154 if (row
< 0 || row
> OSD_Y(OSD_POS_MAX
)) {
2155 cliShowParseError();
2168 cliShowParseError();
2173 cliShowParseError();
2184 // No args, or just layout or layout and item. If any of them not provided,
2185 // it will be the -1 that we used during initialization, so printOsdLayout()
2186 // won't use them for filtering.
2187 printOsdLayout(DUMP_MASTER
, osdConfig(), osdConfig(), layout
, item
);
2190 // No visibility provided. Keep the previous one.
2191 visible
= OSD_VISIBLE(osdConfig()->item_pos
[layout
][item
]);
2194 // Layout, item, pos and visibility. Set the item.
2195 osdConfigMutable()->item_pos
[layout
][item
] = OSD_POS(col
, row
) | (visible
? OSD_VISIBLE_FLAG
: 0);
2199 cliShowParseError();
2206 static void printFeature(uint8_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
)
2208 uint32_t mask
= featureConfig
->enabledFeatures
;
2209 uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
2210 for (uint32_t i
= 0; ; i
++) { // disable all feature first
2211 if (featureNames
[i
] == NULL
)
2213 if (featureNames
[i
][0] == '\0')
2215 const char *format
= "feature -%s";
2216 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2217 cliDumpPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2219 for (uint32_t i
= 0; ; i
++) { // reenable what we want.
2220 if (featureNames
[i
] == NULL
)
2222 if (featureNames
[i
][0] == '\0')
2224 const char *format
= "feature %s";
2225 if (defaultMask
& (1 << i
)) {
2226 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2228 if (mask
& (1 << i
)) {
2229 cliDumpPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2234 static void cliFeature(char *cmdline
)
2236 uint32_t len
= strlen(cmdline
);
2237 uint32_t mask
= featureMask();
2240 cliPrint("Enabled: ");
2241 for (uint32_t i
= 0; ; i
++) {
2242 if (featureNames
[i
] == NULL
)
2244 if (featureNames
[i
][0] == '\0')
2246 if (mask
& (1 << i
))
2247 cliPrintf("%s ", featureNames
[i
]);
2250 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
2251 cliPrint("Available: ");
2252 for (uint32_t i
= 0; ; i
++) {
2253 if (featureNames
[i
] == NULL
)
2255 if (featureNames
[i
][0] == '\0')
2257 cliPrintf("%s ", featureNames
[i
]);
2262 bool remove
= false;
2263 if (cmdline
[0] == '-') {
2266 cmdline
++; // skip over -
2270 for (uint32_t i
= 0; ; i
++) {
2271 if (featureNames
[i
] == NULL
) {
2272 cliPrintErrorLine("Invalid name");
2276 if (sl_strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
2280 if (mask
& FEATURE_GPS
) {
2281 cliPrintErrorLine("unavailable");
2287 cliPrint("Disabled");
2290 cliPrint("Enabled");
2292 cliPrintLinef(" %s", featureNames
[i
]);
2300 static void printBeeper(uint8_t dumpMask
, const beeperConfig_t
*beeperConfig
, const beeperConfig_t
*beeperConfigDefault
)
2302 const uint8_t beeperCount
= beeperTableEntryCount();
2303 const uint32_t mask
= beeperConfig
->beeper_off_flags
;
2304 const uint32_t defaultMask
= beeperConfigDefault
->beeper_off_flags
;
2305 for (int i
= 0; i
< beeperCount
- 2; i
++) {
2306 const char *formatOff
= "beeper -%s";
2307 const char *formatOn
= "beeper %s";
2308 cliDefaultPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOn
: formatOff
, beeperNameForTableIndex(i
));
2309 cliDumpPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOff
: formatOn
, beeperNameForTableIndex(i
));
2313 static void cliBeeper(char *cmdline
)
2315 uint32_t len
= strlen(cmdline
);
2316 uint8_t beeperCount
= beeperTableEntryCount();
2317 uint32_t mask
= getBeeperOffMask();
2320 cliPrintf("Disabled:");
2321 for (int32_t i
= 0; ; i
++) {
2322 if (i
== beeperCount
- 2){
2327 if (mask
& (1 << (beeperModeForTableIndex(i
) - 1)))
2328 cliPrintf(" %s", beeperNameForTableIndex(i
));
2331 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
2332 cliPrint("Available:");
2333 for (uint32_t i
= 0; i
< beeperCount
; i
++)
2334 cliPrintf(" %s", beeperNameForTableIndex(i
));
2338 bool remove
= false;
2339 if (cmdline
[0] == '-') {
2340 remove
= true; // this is for beeper OFF condition
2345 for (uint32_t i
= 0; ; i
++) {
2346 if (i
== beeperCount
) {
2347 cliPrintErrorLine("Invalid name");
2350 if (sl_strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0) {
2351 if (remove
) { // beeper off
2352 if (i
== BEEPER_ALL
-1)
2353 beeperOffSetAll(beeperCount
-2);
2355 if (i
== BEEPER_PREFERENCE
-1)
2356 setBeeperOffMask(getPreferredBeeperOffMask());
2358 mask
= 1 << (beeperModeForTableIndex(i
) - 1);
2361 cliPrint("Disabled");
2364 if (i
== BEEPER_ALL
-1)
2365 beeperOffClearAll();
2367 if (i
== BEEPER_PREFERENCE
-1)
2368 setPreferredBeeperOffMask(getBeeperOffMask());
2370 mask
= 1 << (beeperModeForTableIndex(i
) - 1);
2371 beeperOffClear(mask
);
2373 cliPrint("Enabled");
2375 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
2383 static void printMap(uint8_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
)
2385 bool equalsDefault
= true;
2387 char bufDefault
[16];
2390 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
2391 buf
[i
] = bufDefault
[i
] = 0;
2394 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
2395 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2396 if (defaultRxConfig
) {
2397 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2398 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
2403 const char *formatMap
= "map %s";
2404 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
2405 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
2408 static void cliMap(char *cmdline
)
2413 len
= strlen(cmdline
);
2417 for (uint32_t i
= 0; i
< 4; i
++)
2418 cmdline
[i
] = sl_toupper((unsigned char)cmdline
[i
]);
2419 for (uint32_t i
= 0; i
< 4; i
++) {
2420 if (strchr(rcChannelLetters
, cmdline
[i
]) && !strchr(cmdline
+ i
+ 1, cmdline
[i
]))
2422 cliShowParseError();
2425 parseRcChannels(cmdline
);
2426 } else if (len
!= 0)
2427 cliShowParseError();
2430 for (i
= 0; i
< 4; i
++)
2431 out
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
2433 cliPrintLinef("%s", out
);
2436 static const char *checkCommand(const char *cmdLine
, const char *command
)
2438 if (!sl_strncasecmp(cmdLine
, command
, strlen(command
)) // command names match
2439 && !sl_isalnum((unsigned)cmdLine
[strlen(command
)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
2440 return cmdLine
+ strlen(command
) + 1;
2446 static void cliRebootEx(bool bootLoader
)
2448 cliPrint("\r\nRebooting");
2449 bufWriterFlush(cliWriter
);
2450 waitForSerialPortToFinishTransmitting(cliPort
);
2452 fcReboot(bootLoader
);
2455 static void cliReboot(void)
2460 static void cliDfu(char *cmdline
)
2463 #ifndef CLI_MINIMAL_VERBOSITY
2464 cliPrint("\r\nRestarting in DFU mode");
2469 #ifdef USE_RX_ELERES
2470 static void cliEleresBind(char *cmdline
)
2474 if (!(rxConfig()->receiverType
== RX_TYPE_SPI
&& rxConfig()->rx_spi_protocol
== RFM22_ELERES
)) {
2475 cliPrintLine("Eleres not active. Please enable feature ELERES and restart IMU");
2479 cliPrintLine("Waiting for correct bind signature....");
2480 bufWriterFlush(cliWriter
);
2482 cliPrintLine("Bind timeout!");
2484 cliPrintLine("Bind OK!\r\nPlease restart your transmitter.");
2487 #endif // USE_RX_ELERES
2489 static void cliExit(char *cmdline
)
2493 #ifndef CLI_MINIMAL_VERBOSITY
2494 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
2496 bufWriterFlush(cliWriter
);
2501 // incase a motor was left running during motortest, clear it here
2502 mixerResetDisarmedMotors();
2509 static void cliGpsPassthrough(char *cmdline
)
2513 gpsEnablePassthrough(cliPort
);
2517 static void cliMotor(char *cmdline
)
2519 int motor_index
= 0;
2520 int motor_value
= 0;
2525 if (isEmpty(cmdline
)) {
2526 cliShowParseError();
2531 pch
= strtok_r(cmdline
, " ", &saveptr
);
2532 while (pch
!= NULL
) {
2535 motor_index
= fastA2I(pch
);
2538 motor_value
= fastA2I(pch
);
2542 pch
= strtok_r(NULL
, " ", &saveptr
);
2545 if (motor_index
< 0 || motor_index
>= MAX_SUPPORTED_MOTORS
) {
2546 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
2551 if (motor_value
< PWM_RANGE_MIN
|| motor_value
> PWM_RANGE_MAX
) {
2552 cliShowArgumentRangeError("value", 1000, 2000);
2555 motor_disarmed
[motor_index
] = motor_value
;
2559 cliPrintLinef("motor %d: %d", motor_index
, motor_disarmed
[motor_index
]);
2563 static void cliPlaySound(char *cmdline
)
2567 static int lastSoundIdx
= -1;
2569 if (isEmpty(cmdline
)) {
2570 i
= lastSoundIdx
+ 1; //next sound index
2571 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
2572 while (true) { //no name for index; try next one
2573 if (++i
>= beeperTableEntryCount())
2574 i
= 0; //if end then wrap around to first entry
2575 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
2576 break; //if name OK then play sound below
2577 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
2578 cliPrintLine("Error playing sound");
2583 } else { //index value was given
2584 i
= fastA2I(cmdline
);
2585 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
2586 cliPrintLinef("No sound for index %d", i
);
2592 cliPrintLinef("Playing sound %d: %s", i
, name
);
2593 beeper(beeperModeForTableIndex(i
));
2597 static void cliProfile(char *cmdline
)
2599 // CLI profile index is 1-based
2600 if (isEmpty(cmdline
)) {
2601 cliPrintLinef("profile %d", getConfigProfile() + 1);
2604 const int i
= fastA2I(cmdline
) - 1;
2605 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
2606 setConfigProfileAndWriteEEPROM(i
);
2612 static void cliDumpProfile(uint8_t profileIndex
, uint8_t dumpMask
)
2614 if (profileIndex
>= MAX_PROFILE_COUNT
) {
2618 setConfigProfile(profileIndex
);
2619 cliPrintHashLine("profile");
2620 cliPrintLinef("profile %d\r\n", getConfigProfile() + 1);
2621 dumpAllValues(PROFILE_VALUE
, dumpMask
);
2622 dumpAllValues(CONTROL_RATE_VALUE
, dumpMask
);
2625 static void cliBatteryProfile(char *cmdline
)
2627 // CLI profile index is 1-based
2628 if (isEmpty(cmdline
)) {
2629 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
2632 const int i
= fastA2I(cmdline
) - 1;
2633 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
2634 setConfigBatteryProfileAndWriteEEPROM(i
);
2635 cliBatteryProfile("");
2640 static void cliDumpBatteryProfile(uint8_t profileIndex
, uint8_t dumpMask
)
2642 if (profileIndex
>= MAX_BATTERY_PROFILE_COUNT
) {
2646 setConfigBatteryProfile(profileIndex
);
2647 cliPrintHashLine("battery_profile");
2648 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
2649 dumpAllValues(BATTERY_CONFIG_VALUE
, dumpMask
);
2652 #ifdef USE_CLI_BATCH
2653 static void cliPrintCommandBatchWarning(const char *warning
)
2655 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
2657 cliPrintErrorLinef(warning
);
2661 static void resetCommandBatch(void)
2663 commandBatchActive
= false;
2664 commandBatchError
= false;
2667 static void cliBatch(char *cmdline
)
2669 if (strncasecmp(cmdline
, "start", 5) == 0) {
2670 if (!commandBatchActive
) {
2671 commandBatchActive
= true;
2672 commandBatchError
= false;
2674 cliPrintLine("Command batch started");
2675 } else if (strncasecmp(cmdline
, "end", 3) == 0) {
2676 if (commandBatchActive
&& commandBatchError
) {
2677 cliPrintCommandBatchWarning(NULL
);
2679 cliPrintLine("Command batch ended");
2681 resetCommandBatch();
2683 cliPrintErrorLinef("Invalid option");
2688 static void cliSave(char *cmdline
)
2692 #ifdef USE_CLI_BATCH
2693 if (commandBatchActive
&& commandBatchError
) {
2694 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
2695 resetCommandBatch();
2701 //copyCurrentProfileToProfileSlot(getConfigProfile();
2706 static void cliDefaults(char *cmdline
)
2710 cliPrint("Resetting to defaults");
2713 #ifdef USE_CLI_BATCH
2714 commandBatchError
= false;
2717 if (!checkCommand(cmdline
, "noreboot"))
2721 static void cliGet(char *cmdline
)
2723 const setting_t
*val
;
2724 int matchedCommands
= 0;
2725 char name
[SETTING_MAX_NAME_LENGTH
];
2727 while(*cmdline
== ' ') ++cmdline
; // ignore spaces
2729 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
2730 val
= settingGet(i
);
2731 if (settingNameContains(val
, name
, cmdline
)) {
2732 cliPrintf("%s = ", name
);
2733 cliPrintVar(val
, 0);
2735 cliPrintVarRange(val
);
2743 if (matchedCommands
) {
2747 cliPrintErrorLine("Invalid name");
2750 static void cliSet(char *cmdline
)
2753 const setting_t
*val
;
2755 char name
[SETTING_MAX_NAME_LENGTH
];
2757 while(*cmdline
== ' ') ++cmdline
; // ignore spaces
2759 len
= strlen(cmdline
);
2761 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
2762 cliPrintLine("Current settings:");
2763 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
2764 val
= settingGet(i
);
2765 settingGetName(val
, name
);
2766 cliPrintf("%s = ", name
);
2767 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
2770 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
2773 char *lastNonSpaceCharacter
= eqptr
;
2774 while (*(lastNonSpaceCharacter
- 1) == ' ') {
2775 lastNonSpaceCharacter
--;
2777 uint8_t variableNameLength
= lastNonSpaceCharacter
- cmdline
;
2779 // skip the '=' and any ' ' characters
2781 while (*(eqptr
) == ' ') {
2785 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
2786 val
= settingGet(i
);
2787 // ensure exact match when setting to prevent setting variables with shorter names
2788 if (settingNameIsExactMatch(val
, name
, cmdline
, variableNameLength
)) {
2789 const setting_type_e type
= SETTING_TYPE(val
);
2790 if (type
== VAR_STRING
) {
2791 settingSetString(val
, eqptr
, strlen(eqptr
));
2794 const setting_mode_e mode
= SETTING_MODE(val
);
2795 bool changeValue
= false;
2796 int_float_value_t tmp
= {0};
2799 if (*eqptr
!= 0 && strspn(eqptr
, "0123456789.+-") == strlen(eqptr
)) {
2800 float valuef
= fastA2F(eqptr
);
2801 // note: compare float values
2802 if (valuef
>= (float)settingGetMin(val
) && valuef
<= (float)settingGetMax(val
)) {
2804 if (type
== VAR_FLOAT
)
2805 tmp
.float_value
= valuef
;
2806 else if (type
== VAR_UINT32
)
2807 tmp
.uint_value
= fastA2UL(eqptr
);
2809 tmp
.int_value
= fastA2I(eqptr
);
2817 const lookupTableEntry_t
*tableEntry
= settingLookupTable(val
);
2818 bool matched
= false;
2819 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
2820 matched
= sl_strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
2823 tmp
.int_value
= tableValueIndex
;
2832 cliSetIntFloatVar(val
, tmp
);
2834 cliPrintf("%s set to ", name
);
2835 cliPrintVar(val
, 0);
2837 cliPrintError("Invalid value. ");
2838 cliPrintVarRange(val
);
2845 cliPrintErrorLine("Invalid name");
2847 // no equals, check for matching variables.
2852 static const char * getBatteryStateString(void)
2854 static const char * const batteryStateStrings
[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
2856 return batteryStateStrings
[getBatteryState()];
2859 static void cliStatus(char *cmdline
)
2863 char buf
[MAX(FORMATTED_DATE_TIME_BUFSIZE
, SETTING_MAX_NAME_LENGTH
)];
2866 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
2867 rtcGetDateTime(&dt
);
2868 dateTimeFormatLocal(buf
, &dt
);
2869 cliPrintLinef("Current Time: %s", buf
);
2870 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
2871 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock
/ 1000000));
2873 const uint32_t detectedSensorsMask
= sensorsMask();
2875 for (int i
= 0; i
< SENSOR_INDEX_COUNT
; i
++) {
2877 const uint32_t mask
= (1 << i
);
2878 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
2879 const int sensorHardwareIndex
= detectedSensors
[i
];
2880 if (sensorHardwareNames
[i
]) {
2881 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
2882 cliPrintf(", %s=%s", sensorTypeNames
[i
], sensorHardware
);
2888 cliPrintLine("STM32 system clocks:");
2889 #if defined(USE_HAL_DRIVER)
2890 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
2891 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
2892 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
2893 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
2895 RCC_ClocksTypeDef clocks
;
2896 RCC_GetClocksFreq(&clocks
);
2897 cliPrintLinef(" SYSCLK = %d MHz", clocks
.SYSCLK_Frequency
/ 1000000);
2898 cliPrintLinef(" HCLK = %d MHz", clocks
.HCLK_Frequency
/ 1000000);
2899 cliPrintLinef(" PCLK1 = %d MHz", clocks
.PCLK1_Frequency
/ 1000000);
2900 cliPrintLinef(" PCLK2 = %d MHz", clocks
.PCLK2_Frequency
/ 1000000);
2903 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
2904 hardwareSensorStatusNames
[getHwGyroStatus()],
2905 hardwareSensorStatusNames
[getHwAccelerometerStatus()],
2906 hardwareSensorStatusNames
[getHwCompassStatus()],
2907 hardwareSensorStatusNames
[getHwBarometerStatus()],
2908 hardwareSensorStatusNames
[getHwRangefinderStatus()],
2909 hardwareSensorStatusNames
[getHwOpticalFlowStatus()],
2910 hardwareSensorStatusNames
[getHwGPSStatus()]
2917 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
2919 const uint16_t i2cErrorCounter
= 0;
2923 cliPrintf("Stack used: %d, ", stackUsedSize());
2925 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
2927 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter
, getEEPROMConfigSize(), &__config_end
- &__config_start
);
2930 static char * adcFunctions
[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
2931 cliPrintLine("ADC channel usage:");
2932 for (int i
= 0; i
< ADC_FUNCTION_COUNT
; i
++) {
2933 cliPrintf(" %8s :", adcFunctions
[i
]);
2935 cliPrint(" configured = ");
2936 if (adcChannelConfig()->adcFunctionChannel
[i
] == ADC_CHN_NONE
) {
2940 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel
[i
]);
2943 cliPrint(", used = ");
2944 if (adcGetFunctionChannelAllocation(i
) == ADC_CHN_NONE
) {
2945 cliPrintLine("none");
2948 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i
));
2953 cliPrintf("System load: %d", averageSystemLoadPercent
);
2954 const timeDelta_t pidTaskDeltaTime
= getTaskDeltaTime(TASK_GYROPID
);
2955 const int pidRate
= pidTaskDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)pidTaskDeltaTime
));
2956 const int rxRate
= getTaskDeltaTime(TASK_RX
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_RX
)));
2957 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
2958 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime
, pidRate
, rxRate
, systemRate
);
2959 #if !defined(CLI_MINIMAL_VERBOSITY)
2960 cliPrint("Arming disabled flags:");
2961 uint32_t flags
= armingFlags
& ARMING_DISABLED_ALL_FLAGS
;
2963 int bitpos
= ffs(flags
) - 1;
2964 flags
&= ~(1 << bitpos
);
2965 if (bitpos
> 6) cliPrintf(" %s", armingDisableFlagNames
[bitpos
- 7]);
2968 if (armingFlags
& ARMING_DISABLED_INVALID_SETTING
) {
2969 unsigned invalidIndex
;
2970 if (!settingsValidate(&invalidIndex
)) {
2971 settingGetName(settingGet(invalidIndex
), buf
);
2972 cliPrintErrorLinef("Invalid setting: %s", buf
);
2976 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags
& ARMING_DISABLED_ALL_FLAGS
);
2979 #if defined(USE_VTX_CONTROL) && !defined(CLI_MINIMAL_VERBOSITY)
2982 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
2983 vtxDeviceOsdInfo_t osdInfo
;
2984 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo
);
2985 cliPrintf("band: %c, chan: %s, power: %c", osdInfo
.bandLetter
, osdInfo
.channelName
, osdInfo
.powerIndexLetter
);
2987 if (osdInfo
.powerMilliwatt
) {
2988 cliPrintf(" (%d mW)", osdInfo
.powerMilliwatt
);
2991 if (osdInfo
.frequency
) {
2992 cliPrintf(", freq: %d MHz", osdInfo
.frequency
);
2996 cliPrint("not detected");
3002 // If we are blocked by PWM init - provide more information
3003 if (getPwmInitError() != PWM_INIT_ERROR_NONE
) {
3004 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3008 #ifndef SKIP_TASK_STATISTICS
3009 static void cliTasks(char *cmdline
)
3013 int averageLoadSum
= 0;
3014 cfCheckFuncInfo_t checkFuncInfo
;
3016 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3017 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
3018 cfTaskInfo_t taskInfo
;
3019 getTaskInfo(taskId
, &taskInfo
);
3020 if (taskInfo
.isEnabled
) {
3021 const int taskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3022 const int maxLoad
= (taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
3023 const int averageLoad
= (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
3024 if (taskId
!= TASK_SERIAL
) {
3025 maxLoadSum
+= maxLoad
;
3026 averageLoadSum
+= averageLoad
;
3028 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
3029 taskId
, taskInfo
.taskName
, taskFrequency
, (uint32_t)taskInfo
.maxExecutionTime
, (uint32_t)taskInfo
.averageExecutionTime
,
3030 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, (uint32_t)taskInfo
.totalExecutionTime
/ 1000);
3033 getCheckFuncInfo(&checkFuncInfo
);
3034 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo
.maxExecutionTime
, (uint32_t)checkFuncInfo
.averageExecutionTime
, (uint32_t)checkFuncInfo
.totalExecutionTime
/ 1000);
3035 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
3039 static void cliVersion(char *cmdline
)
3043 cliPrintLinef("# %s/%s %s %s / %s (%s)",
3051 cliPrintLinef("# GCC-%s",
3056 static void cliMemory(char *cmdline
)
3059 cliPrintLinef("Dynamic memory usage:");
3060 for (unsigned i
= 0; i
< OWNER_TOTAL_COUNT
; i
++) {
3061 const char * owner
= ownerNames
[i
];
3062 const uint32_t memUsed
= memGetUsedBytesByOwner(i
);
3065 cliPrintLinef("%s : %d bytes", owner
, memUsed
);
3070 #if !defined(SKIP_TASK_STATISTICS) && !defined(SKIP_CLI_RESOURCES)
3071 static void cliResource(char *cmdline
)
3074 cliPrintLinef("IO:\r\n----------------------");
3075 for (unsigned i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
3077 owner
= ownerNames
[ioRecs
[i
].owner
];
3079 const char* resource
;
3080 resource
= resourceNames
[ioRecs
[i
].resource
];
3082 if (ioRecs
[i
].index
> 0) {
3083 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
, ioRecs
[i
].index
, resource
);
3085 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
, resource
);
3091 static void backupConfigs(void)
3093 // make copies of configs to do differencing
3095 if (pgIsProfile(pg
)) {
3096 memcpy(pg
->copy
, pg
->address
, pgSize(pg
) * MAX_PROFILE_COUNT
);
3098 memcpy(pg
->copy
, pg
->address
, pgSize(pg
));
3103 static void restoreConfigs(void)
3106 if (pgIsProfile(pg
)) {
3107 memcpy(pg
->address
, pg
->copy
, pgSize(pg
) * MAX_PROFILE_COUNT
);
3109 memcpy(pg
->address
, pg
->copy
, pgSize(pg
));
3114 static void printConfig(const char *cmdline
, bool doDiff
)
3116 uint8_t dumpMask
= DUMP_MASTER
;
3117 const char *options
;
3118 if ((options
= checkCommand(cmdline
, "master"))) {
3119 dumpMask
= DUMP_MASTER
; // only
3120 } else if ((options
= checkCommand(cmdline
, "profile"))) {
3121 dumpMask
= DUMP_PROFILE
; // only
3122 } else if ((options
= checkCommand(cmdline
, "battery_profile"))) {
3123 dumpMask
= DUMP_BATTERY_PROFILE
; // only
3124 } else if ((options
= checkCommand(cmdline
, "all"))) {
3125 dumpMask
= DUMP_ALL
; // all profiles and rates
3131 dumpMask
= dumpMask
| DO_DIFF
;
3134 const int currentProfileIndexSave
= getConfigProfile();
3135 const int currentBatteryProfileIndexSave
= getConfigBatteryProfile();
3137 // reset all configs to defaults to do differencing
3139 // restore the profile indices, since they should not be reset for proper comparison
3140 setConfigProfile(currentProfileIndexSave
);
3141 setConfigBatteryProfile(currentBatteryProfileIndexSave
);
3143 if (checkCommand(options
, "showdefaults")) {
3144 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
3147 #ifdef USE_CLI_BATCH
3148 bool batchModeEnabled
= false;
3151 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
3152 cliPrintHashLine("version");
3155 #ifdef USE_CLI_BATCH
3156 cliPrintHashLine("start the command batch");
3157 cliPrintLine("batch start");
3158 batchModeEnabled
= true;
3161 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
3162 #ifndef CLI_MINIMAL_VERBOSITY
3163 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
3165 cliPrintLinef("defaults noreboot");
3169 cliPrintHashLine("resources");
3170 //printResource(dumpMask, &defaultConfig);
3172 cliPrintHashLine("mixer");
3173 cliDumpPrintLinef(dumpMask
, primaryMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
3175 printMotorMix(dumpMask
, primaryMotorMixer_CopyArray
, primaryMotorMixer(0));
3177 // print custom servo mixer if exists
3178 cliPrintHashLine("servo mix");
3179 cliDumpPrintLinef(dumpMask
, customServoMixers(0)->rate
== 0, "smix reset\r\n");
3180 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0));
3182 // print servo parameters
3183 cliPrintHashLine("servo");
3184 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0));
3186 #ifdef USE_LOGIC_CONDITIONS
3187 cliPrintHashLine("logic");
3188 printLogic(dumpMask
, logicConditions_CopyArray
, logicConditions(0));
3191 #ifdef USE_GLOBAL_FUNCTIONS
3192 cliPrintHashLine("gf");
3193 printGlobalFunctions(dumpMask
, globalFunctions_CopyArray
, globalFunctions(0));
3196 cliPrintHashLine("feature");
3197 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig());
3200 cliPrintHashLine("beeper");
3201 printBeeper(dumpMask
, &beeperConfig_Copy
, beeperConfig());
3204 cliPrintHashLine("map");
3205 printMap(dumpMask
, &rxConfig_Copy
, rxConfig());
3207 cliPrintHashLine("serial");
3208 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig());
3210 #ifdef USE_LED_STRIP
3211 cliPrintHashLine("led");
3212 printLed(dumpMask
, ledStripConfig_Copy
.ledConfigs
, ledStripConfig()->ledConfigs
);
3214 cliPrintHashLine("color");
3215 printColor(dumpMask
, ledStripConfig_Copy
.colors
, ledStripConfig()->colors
);
3217 cliPrintHashLine("mode_color");
3218 printModeColor(dumpMask
, &ledStripConfig_Copy
, ledStripConfig());
3221 cliPrintHashLine("aux");
3222 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0));
3224 cliPrintHashLine("adjrange");
3225 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0));
3227 cliPrintHashLine("rxrange");
3228 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0));
3230 #ifdef USE_TEMPERATURE_SENSOR
3231 cliPrintHashLine("temp_sensor");
3232 printTempSensor(dumpMask
, tempSensorConfig_CopyArray
, tempSensorConfig(0));
3235 #if defined(USE_NAV) && defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
3236 cliPrintHashLine("wp");
3237 printWaypoints(dumpMask
, posControl
.waypointList
, nonVolatileWaypointList(0));
3241 cliPrintHashLine("osd_layout");
3242 printOsdLayout(dumpMask
, &osdConfig_Copy
, osdConfig(), -1, -1);
3245 cliPrintHashLine("master");
3246 dumpAllValues(MASTER_VALUE
, dumpMask
);
3248 if (dumpMask
& DUMP_ALL
) {
3249 // dump all profiles
3250 const int currentProfileIndexSave
= getConfigProfile();
3251 const int currentBatteryProfileIndexSave
= getConfigBatteryProfile();
3252 for (int ii
= 0; ii
< MAX_PROFILE_COUNT
; ++ii
) {
3253 cliDumpProfile(ii
, dumpMask
);
3255 for (int ii
= 0; ii
< MAX_BATTERY_PROFILE_COUNT
; ++ii
) {
3256 cliDumpBatteryProfile(ii
, dumpMask
);
3258 setConfigProfile(currentProfileIndexSave
);
3259 setConfigBatteryProfile(currentBatteryProfileIndexSave
);
3261 cliPrintHashLine("restore original profile selection");
3262 cliPrintLinef("profile %d", currentProfileIndexSave
+ 1);
3263 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave
+ 1);
3265 cliPrintHashLine("save configuration\r\nsave");
3266 #ifdef USE_CLI_BATCH
3267 batchModeEnabled
= false;
3270 // dump just the current profiles
3271 cliDumpProfile(getConfigProfile(), dumpMask
);
3272 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask
);
3276 if (dumpMask
& DUMP_PROFILE
) {
3277 cliDumpProfile(getConfigProfile(), dumpMask
);
3280 if (dumpMask
& DUMP_BATTERY_PROFILE
) {
3281 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask
);
3284 #ifdef USE_CLI_BATCH
3285 if (batchModeEnabled
) {
3286 cliPrintHashLine("end the command batch");
3287 cliPrintLine("batch end");
3291 // restore configs from copies
3295 static void cliDump(char *cmdline
)
3297 printConfig(cmdline
, false);
3300 static void cliDiff(char *cmdline
)
3302 printConfig(cmdline
, true);
3306 static void cliMsc(char *cmdline
)
3312 || sdcard_isFunctional()
3315 || flashfsGetSize() > 0
3318 cliPrintHashLine("restarting in mass storage mode");
3319 cliPrint("\r\nRebooting");
3320 bufWriterFlush(cliWriter
);
3322 waitForSerialPortToFinishTransmitting(cliPort
);
3326 *((__IO
uint32_t*) BKPSRAM_BASE
+ 16) = MSC_MAGIC
;
3327 #elif defined(STM32F4)
3328 *((uint32_t *)0x2001FFF0) = MSC_MAGIC
;
3334 cliPrint("\r\nStorage not present or failed to initialize!");
3335 bufWriterFlush(cliWriter
);
3343 #ifndef SKIP_CLI_COMMAND_HELP
3344 const char *description
;
3347 void (*func
)(char *cmdline
);
3350 #ifndef SKIP_CLI_COMMAND_HELP
3351 #define CLI_COMMAND_DEF(name, description, args, method) \
3359 #define CLI_COMMAND_DEF(name, description, args, method) \
3366 static void cliHelp(char *cmdline
);
3368 // should be sorted a..z for bsearch()
3369 const clicmd_t cmdTable
[] = {
3370 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
3371 #if defined(USE_ASSERT)
3372 CLI_COMMAND_DEF("assert", "", NULL
, cliAssert
),
3374 CLI_COMMAND_DEF("aux", "configure modes", NULL
, cliAux
),
3375 #ifdef USE_CLI_BATCH
3376 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch
),
3379 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3380 "\t<+|->[name]", cliBeeper
),
3382 #if defined(USE_BOOTLOG)
3383 CLI_COMMAND_DEF("bootlog", "show boot events", NULL
, cliBootlog
),
3385 #ifdef USE_LED_STRIP
3386 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
3387 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
3389 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL
, cliDefaults
),
3390 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL
, cliDfu
),
3391 CLI_COMMAND_DEF("diff", "list configuration changes from default",
3392 "[master|battery_profile|profile|rates|all] {showdefaults}", cliDiff
),
3393 CLI_COMMAND_DEF("dump", "dump configuration",
3394 "[master|battery_profile|profile|rates|all] {showdefaults}", cliDump
),
3395 #ifdef USE_RX_ELERES
3396 CLI_COMMAND_DEF("eleres_bind", NULL
, NULL
, cliEleresBind
),
3397 #endif // USE_RX_ELERES
3398 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
3399 CLI_COMMAND_DEF("feature", "configure features",
3401 "\t<+|->[name]", cliFeature
),
3403 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
3404 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
3405 #ifdef USE_FLASH_TOOLS
3406 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
3407 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
3410 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
3412 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
3414 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
3415 #ifdef USE_LED_STRIP
3416 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
3418 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
3419 CLI_COMMAND_DEF("memory", "view memory usage", NULL
, cliMemory
),
3420 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
3421 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
3423 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
3426 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]\r\n", cliPlaySound
),
3428 CLI_COMMAND_DEF("profile", "change profile",
3429 "[<index>]", cliProfile
),
3430 CLI_COMMAND_DEF("battery_profile", "change battery profile",
3431 "[<index>]", cliBatteryProfile
),
3432 #if !defined(SKIP_TASK_STATISTICS) && !defined(SKIP_CLI_RESOURCES)
3433 CLI_COMMAND_DEF("resource", "view currently used resources", NULL
, cliResource
),
3435 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
3436 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
3437 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
3438 #ifdef USE_SERIAL_PASSTHROUGH
3439 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough
),
3441 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
3442 #ifdef USE_LOGIC_CONDITIONS
3443 CLI_COMMAND_DEF("logic", "configure logic conditions",
3444 "<rule> <enabled> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
3445 "\treset\r\n", cliLogic
),
3447 #ifdef USE_GLOBAL_FUNCTIONS
3448 CLI_COMMAND_DEF("gf", "configure global functions",
3449 "<rule> <enabled> <logic condition> <action> <operand type> <operand value> <flags>\r\n"
3450 "\treset\r\n", cliGlobalFunctions
),
3452 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
3453 CLI_COMMAND_DEF("smix", "servo mixer",
3454 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
3455 "\treset\r\n", cliServoMix
),
3457 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
3459 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
3460 #ifndef SKIP_TASK_STATISTICS
3461 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
3463 #ifdef USE_TEMPERATURE_SENSOR
3464 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL
, cliTempSensor
),
3466 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
3467 #if defined(USE_NAV) && defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
3468 CLI_COMMAND_DEF("wp", "waypoint list", NULL
, cliWaypoints
),
3471 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout
),
3475 static void cliHelp(char *cmdline
)
3479 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
3480 cliPrint(cmdTable
[i
].name
);
3481 #ifndef SKIP_CLI_COMMAND_HELP
3482 if (cmdTable
[i
].description
) {
3483 cliPrintf(" - %s", cmdTable
[i
].description
);
3485 if (cmdTable
[i
].args
) {
3486 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
3493 void cliProcess(void)
3499 // Be a little bit tricky. Flush the last inputs buffer, if any.
3500 bufWriterFlush(cliWriter
);
3502 while (serialRxBytesWaiting(cliPort
)) {
3503 uint8_t c
= serialRead(cliPort
);
3504 if (c
== '\t' || c
== '?') {
3505 // do tab completion
3506 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
3507 uint32_t i
= bufferIndex
;
3508 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
3509 if (bufferIndex
&& (sl_strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
3515 if (pstart
) { /* Buffer matches one or more commands */
3516 for (; ; bufferIndex
++) {
3517 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
3519 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
3520 /* Unambiguous -- append a space */
3521 cliBuffer
[bufferIndex
++] = ' ';
3522 cliBuffer
[bufferIndex
] = '\0';
3525 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
3528 if (!bufferIndex
|| pstart
!= pend
) {
3529 /* Print list of ambiguous matches */
3530 cliPrint("\r\033[K");
3531 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
3532 cliPrint(cmd
->name
);
3536 i
= 0; /* Redraw prompt */
3538 for (; i
< bufferIndex
; i
++)
3539 cliWrite(cliBuffer
[i
]);
3540 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
3543 } else if (c
== 12) { // NewPage / CTRL-L
3545 cliPrint("\033[2J\033[1;1H");
3547 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
3551 // Strip comment starting with # from line
3552 char *p
= cliBuffer
;
3555 bufferIndex
= (uint32_t)(p
- cliBuffer
);
3558 // Strip trailing whitespace
3559 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
3563 // Process non-empty lines
3564 if (bufferIndex
> 0) {
3565 cliBuffer
[bufferIndex
] = 0; // null terminate
3567 const clicmd_t
*cmd
;
3568 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
3569 if (!sl_strncasecmp(cliBuffer
, cmd
->name
, strlen(cmd
->name
)) // command names match
3570 && !sl_isalnum((unsigned)cliBuffer
[strlen(cmd
->name
)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
3573 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
))
3574 cmd
->func(cliBuffer
+ strlen(cmd
->name
) + 1);
3576 cliPrintError("Unknown command, try 'help'");
3580 memset(cliBuffer
, 0, sizeof(cliBuffer
));
3582 // 'exit' will reset this flag, so we don't need to print prompt again
3587 } else if (c
== 127) {
3590 cliBuffer
[--bufferIndex
] = 0;
3591 cliPrint("\010 \010");
3593 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
3594 if (!bufferIndex
&& c
== ' ')
3595 continue; // Ignore leading spaces
3596 cliBuffer
[bufferIndex
++] = c
;
3602 void cliEnter(serialPort_t
*serialPort
)
3609 cliPort
= serialPort
;
3610 setPrintfSerialPort(cliPort
);
3611 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
3613 #ifndef CLI_MINIMAL_VERBOSITY
3614 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
3616 cliPrintLine("\r\nCLI");
3620 #ifdef USE_CLI_BATCH
3621 resetCommandBatch();
3624 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI
);
3627 void cliInit(const serialConfig_t
*serialConfig
)
3629 UNUSED(serialConfig
);