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
917 static void cliSerialPassthrough(char *cmdline
)
921 if (isEmpty(cmdline
)) {
929 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
932 while (tok
!= NULL
) {
941 if (strstr(tok
, "rx") || strstr(tok
, "RX"))
943 if (strstr(tok
, "tx") || strstr(tok
, "TX"))
948 tok
= strtok_r(NULL
, " ", &saveptr
);
951 serialPort_t
*passThroughPort
;
952 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
953 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
955 tfp_printf("Port %d is closed, must specify baud.\r\n", id
);
961 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
, NULL
,
963 SERIAL_NOT_INVERTED
);
964 if (!passThroughPort
) {
965 tfp_printf("Port %d could not be opened.\r\n", id
);
968 tfp_printf("Port %d opened, baud = %u.\r\n", id
, (unsigned)baud
);
970 passThroughPort
= passThroughPortUsage
->serialPort
;
971 // If the user supplied a mode, override the port's mode, otherwise
972 // leave the mode unchanged. serialPassthrough() handles one-way ports.
973 tfp_printf("Port %d already open.\r\n", id
);
974 if (mode
&& passThroughPort
->mode
!= mode
) {
975 tfp_printf("Adjusting mode from %d to %d.\r\n",
976 passThroughPort
->mode
, mode
);
977 serialSetMode(passThroughPort
, mode
);
979 // If this port has a rx callback associated we need to remove it now.
980 // Otherwise no data will be pushed in the serial port buffer!
981 if (passThroughPort
->rxCallback
) {
982 tfp_printf("Removing rxCallback\r\n");
983 passThroughPort
->rxCallback
= 0;
987 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id
);
989 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
993 static void printAdjustmentRange(uint8_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
)
995 const char *format
= "adjrange %u %u %u %u %u %u %u";
996 // print out adjustment ranges channel settings
997 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
998 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
999 bool equalsDefault
= false;
1000 if (defaultAdjustmentRanges
) {
1001 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
1002 equalsDefault
= ar
->auxChannelIndex
== arDefault
->auxChannelIndex
1003 && ar
->range
.startStep
== arDefault
->range
.startStep
1004 && ar
->range
.endStep
== arDefault
->range
.endStep
1005 && ar
->adjustmentFunction
== arDefault
->adjustmentFunction
1006 && ar
->auxSwitchChannelIndex
== arDefault
->auxSwitchChannelIndex
1007 && ar
->adjustmentIndex
== arDefault
->adjustmentIndex
;
1008 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1010 arDefault
->adjustmentIndex
,
1011 arDefault
->auxChannelIndex
,
1012 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1013 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1014 arDefault
->adjustmentFunction
,
1015 arDefault
->auxSwitchChannelIndex
1018 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1020 ar
->adjustmentIndex
,
1021 ar
->auxChannelIndex
,
1022 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1023 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1024 ar
->adjustmentFunction
,
1025 ar
->auxSwitchChannelIndex
1030 static void cliAdjustmentRange(char *cmdline
)
1035 if (isEmpty(cmdline
)) {
1036 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
);
1040 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1041 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1042 uint8_t validArgumentCount
= 0;
1047 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
1048 ar
->adjustmentIndex
= val
;
1049 validArgumentCount
++;
1055 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1056 ar
->auxChannelIndex
= val
;
1057 validArgumentCount
++;
1061 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1066 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1067 ar
->adjustmentFunction
= val
;
1068 validArgumentCount
++;
1074 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1075 ar
->auxSwitchChannelIndex
= val
;
1076 validArgumentCount
++;
1080 if (validArgumentCount
!= 6) {
1081 memset(ar
, 0, sizeof(adjustmentRange_t
));
1082 cliShowParseError();
1085 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1090 static void printMotorMix(uint8_t dumpMask
, const motorMixer_t
*primaryMotorMixer
, const motorMixer_t
*defaultprimaryMotorMixer
)
1092 const char *format
= "mmix %d %s %s %s %s";
1093 char buf0
[FTOA_BUFFER_SIZE
];
1094 char buf1
[FTOA_BUFFER_SIZE
];
1095 char buf2
[FTOA_BUFFER_SIZE
];
1096 char buf3
[FTOA_BUFFER_SIZE
];
1097 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1098 if (primaryMotorMixer
[i
].throttle
== 0.0f
)
1100 const float thr
= primaryMotorMixer
[i
].throttle
;
1101 const float roll
= primaryMotorMixer
[i
].roll
;
1102 const float pitch
= primaryMotorMixer
[i
].pitch
;
1103 const float yaw
= primaryMotorMixer
[i
].yaw
;
1104 bool equalsDefault
= false;
1105 if (defaultprimaryMotorMixer
) {
1106 const float thrDefault
= defaultprimaryMotorMixer
[i
].throttle
;
1107 const float rollDefault
= defaultprimaryMotorMixer
[i
].roll
;
1108 const float pitchDefault
= defaultprimaryMotorMixer
[i
].pitch
;
1109 const float yawDefault
= defaultprimaryMotorMixer
[i
].yaw
;
1110 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1112 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1114 ftoa(thrDefault
, buf0
),
1115 ftoa(rollDefault
, buf1
),
1116 ftoa(pitchDefault
, buf2
),
1117 ftoa(yawDefault
, buf3
));
1119 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1128 static void cliMotorMix(char *cmdline
)
1133 if (isEmpty(cmdline
)) {
1134 printMotorMix(DUMP_MASTER
, primaryMotorMixer(0), NULL
);
1135 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
1136 // erase custom mixer
1137 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1138 primaryMotorMixerMutable(i
)->throttle
= 0.0f
;
1142 uint32_t i
= fastA2I(ptr
); // get motor number
1143 if (i
< MAX_SUPPORTED_MOTORS
) {
1146 primaryMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1151 primaryMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1156 primaryMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1161 primaryMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1165 cliShowParseError();
1167 printMotorMix(DUMP_MASTER
, primaryMotorMixer(0), NULL
);
1170 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
1175 static void printRxRange(uint8_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
)
1177 const char *format
= "rxrange %u %u %u";
1178 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1179 bool equalsDefault
= false;
1180 if (defaultChannelRangeConfigs
) {
1181 equalsDefault
= channelRangeConfigs
[i
].min
== defaultChannelRangeConfigs
[i
].min
1182 && channelRangeConfigs
[i
].max
== defaultChannelRangeConfigs
[i
].max
;
1183 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1185 defaultChannelRangeConfigs
[i
].min
,
1186 defaultChannelRangeConfigs
[i
].max
1189 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1191 channelRangeConfigs
[i
].min
,
1192 channelRangeConfigs
[i
].max
1197 static void cliRxRange(char *cmdline
)
1199 int i
, validArgumentCount
= 0;
1202 if (isEmpty(cmdline
)) {
1203 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
);
1204 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1205 resetAllRxChannelRangeConfigurations();
1209 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1210 int rangeMin
= 0, rangeMax
= 0;
1214 rangeMin
= fastA2I(ptr
);
1215 validArgumentCount
++;
1220 rangeMax
= fastA2I(ptr
);
1221 validArgumentCount
++;
1224 if (validArgumentCount
!= 2) {
1225 cliShowParseError();
1226 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1227 cliShowParseError();
1229 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1230 channelRangeConfig
->min
= rangeMin
;
1231 channelRangeConfig
->max
= rangeMax
;
1234 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT
- 1);
1239 #ifdef USE_TEMPERATURE_SENSOR
1240 static void printTempSensor(uint8_t dumpMask
, const tempSensorConfig_t
*tempSensorConfigs
, const tempSensorConfig_t
*defaultTempSensorConfigs
)
1242 const char *format
= "temp_sensor %u %u %s %d %d %u %s";
1243 for (uint8_t i
= 0; i
< MAX_TEMP_SENSORS
; i
++) {
1244 bool equalsDefault
= false;
1245 char label
[5], hex_address
[17];
1246 strncpy(label
, tempSensorConfigs
[i
].label
, TEMPERATURE_LABEL_LEN
);
1248 tempSensorAddressToString(tempSensorConfigs
[i
].address
, hex_address
);
1249 if (defaultTempSensorConfigs
) {
1250 equalsDefault
= tempSensorConfigs
[i
].type
== defaultTempSensorConfigs
[i
].type
1251 && tempSensorConfigs
[i
].address
== defaultTempSensorConfigs
[i
].address
1252 && tempSensorConfigs
[i
].osdSymbol
== defaultTempSensorConfigs
[i
].osdSymbol
1253 && !memcmp(tempSensorConfigs
[i
].label
, defaultTempSensorConfigs
[i
].label
, TEMPERATURE_LABEL_LEN
)
1254 && tempSensorConfigs
[i
].alarm_min
== defaultTempSensorConfigs
[i
].alarm_min
1255 && tempSensorConfigs
[i
].alarm_max
== defaultTempSensorConfigs
[i
].alarm_max
;
1256 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1258 defaultTempSensorConfigs
[i
].type
,
1260 defaultTempSensorConfigs
[i
].alarm_min
,
1261 defaultTempSensorConfigs
[i
].alarm_max
,
1266 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1268 tempSensorConfigs
[i
].type
,
1270 tempSensorConfigs
[i
].alarm_min
,
1271 tempSensorConfigs
[i
].alarm_max
,
1272 tempSensorConfigs
[i
].osdSymbol
,
1278 static void cliTempSensor(char *cmdline
)
1280 if (isEmpty(cmdline
)) {
1281 printTempSensor(DUMP_MASTER
, tempSensorConfig(0), NULL
);
1282 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1283 resetTempSensorConfig();
1286 const char *ptr
= cmdline
, *label
;
1287 int16_t type
=0, alarm_min
=0, alarm_max
=0;
1288 bool addressValid
= false;
1291 uint8_t validArgumentCount
= 0;
1293 if (i
>= 0 && i
< MAX_TEMP_SENSORS
) {
1297 type
= fastA2I(ptr
);
1298 validArgumentCount
++;
1303 addressValid
= tempSensorStringToAddress(ptr
, &address
);
1304 validArgumentCount
++;
1309 alarm_min
= fastA2I(ptr
);
1310 validArgumentCount
++;
1315 alarm_max
= fastA2I(ptr
);
1316 validArgumentCount
++;
1321 osdSymbol
= fastA2I(ptr
);
1322 validArgumentCount
++;
1325 label
= nextArg(ptr
);
1327 ++validArgumentCount
;
1331 if (validArgumentCount
< 4) {
1332 cliShowParseError();
1333 } 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
) {
1334 cliShowParseError();
1336 tempSensorConfig_t
*sensorConfig
= tempSensorConfigMutable(i
);
1337 sensorConfig
->type
= type
;
1338 sensorConfig
->address
= address
;
1339 sensorConfig
->alarm_min
= alarm_min
;
1340 sensorConfig
->alarm_max
= alarm_max
;
1341 sensorConfig
->osdSymbol
= osdSymbol
;
1342 for (uint8_t index
= 0; index
< TEMPERATURE_LABEL_LEN
; ++index
) {
1343 sensorConfig
->label
[index
] = toupper(label
[index
]);
1344 if (label
[index
] == '\0') break;
1348 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS
- 1);
1354 #ifdef USE_FW_AUTOLAND
1355 static void printFwAutolandApproach(uint8_t dumpMask
, const navFwAutolandApproach_t
*navFwAutolandApproach
, const navFwAutolandApproach_t
*defaultFwAutolandApproach
)
1357 const char *format
= "fwapproach %u %d %d %u %d %d %u";
1358 for (uint8_t i
= 0; i
< MAX_FW_LAND_APPOACH_SETTINGS
; i
++) {
1359 bool equalsDefault
= false;
1360 if (defaultFwAutolandApproach
) {
1361 equalsDefault
= navFwAutolandApproach
[i
].approachDirection
== defaultFwAutolandApproach
[i
].approachDirection
1362 && navFwAutolandApproach
[i
].approachAlt
== defaultFwAutolandApproach
[i
].approachAlt
1363 && navFwAutolandApproach
[i
].landAlt
== defaultFwAutolandApproach
[i
].landAlt
1364 && navFwAutolandApproach
[i
].landApproachHeading1
== defaultFwAutolandApproach
[i
].landApproachHeading1
1365 && navFwAutolandApproach
[i
].landApproachHeading2
== defaultFwAutolandApproach
[i
].landApproachHeading2
1366 && navFwAutolandApproach
[i
].isSeaLevelRef
== defaultFwAutolandApproach
[i
].isSeaLevelRef
;
1367 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1368 defaultFwAutolandApproach
[i
].approachAlt
, defaultFwAutolandApproach
[i
].landAlt
, defaultFwAutolandApproach
[i
].approachDirection
, defaultFwAutolandApproach
[i
].landApproachHeading1
, defaultFwAutolandApproach
[i
].landApproachHeading2
, defaultFwAutolandApproach
[i
].isSeaLevelRef
);
1370 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1371 navFwAutolandApproach
[i
].approachAlt
, navFwAutolandApproach
[i
].landAlt
, navFwAutolandApproach
[i
].approachDirection
, navFwAutolandApproach
[i
].landApproachHeading1
, navFwAutolandApproach
[i
].landApproachHeading2
, navFwAutolandApproach
[i
].isSeaLevelRef
);
1375 static void cliFwAutolandApproach(char * cmdline
)
1377 if (isEmpty(cmdline
)) {
1378 printFwAutolandApproach(DUMP_MASTER
, fwAutolandApproachConfig(0), NULL
);
1379 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1380 resetFwAutolandApproach(-1);
1382 int32_t approachAlt
= 0, heading1
= 0, heading2
= 0, landDirection
= 0, landAlt
= 0;
1383 bool isSeaLevelRef
= false;
1384 uint8_t validArgumentCount
= 0;
1385 const char *ptr
= cmdline
;
1386 int8_t i
= fastA2I(ptr
);
1387 if (i
< 0 || i
>= MAX_FW_LAND_APPOACH_SETTINGS
) {
1388 cliShowArgumentRangeError("fwapproach index", 0, MAX_FW_LAND_APPOACH_SETTINGS
- 1);
1390 if ((ptr
= nextArg(ptr
))) {
1391 approachAlt
= fastA2I(ptr
);
1392 validArgumentCount
++;
1395 if ((ptr
= nextArg(ptr
))) {
1396 landAlt
= fastA2I(ptr
);
1397 validArgumentCount
++;
1400 if ((ptr
= nextArg(ptr
))) {
1401 landDirection
= fastA2I(ptr
);
1403 if (landDirection
!= 0 && landDirection
!= 1) {
1404 cliShowParseError();
1408 validArgumentCount
++;
1411 if ((ptr
= nextArg(ptr
))) {
1412 heading1
= fastA2I(ptr
);
1414 if (heading1
< -360 || heading1
> 360) {
1415 cliShowParseError();
1419 validArgumentCount
++;
1422 if ((ptr
= nextArg(ptr
))) {
1423 heading2
= fastA2I(ptr
);
1425 if (heading2
< -360 || heading2
> 360) {
1426 cliShowParseError();
1430 validArgumentCount
++;
1433 if ((ptr
= nextArg(ptr
))) {
1434 isSeaLevelRef
= fastA2I(ptr
);
1435 validArgumentCount
++;
1438 if ((ptr
= nextArg(ptr
))) {
1439 // check for too many arguments
1440 validArgumentCount
++;
1443 if (validArgumentCount
!= 6) {
1444 cliShowParseError();
1446 fwAutolandApproachConfigMutable(i
)->approachAlt
= approachAlt
;
1447 fwAutolandApproachConfigMutable(i
)->landAlt
= landAlt
;
1448 fwAutolandApproachConfigMutable(i
)->approachDirection
= (fwAutolandApproachDirection_e
)landDirection
;
1449 fwAutolandApproachConfigMutable(i
)->landApproachHeading1
= (int16_t)heading1
;
1450 fwAutolandApproachConfigMutable(i
)->landApproachHeading2
= (int16_t)heading2
;
1451 fwAutolandApproachConfigMutable(i
)->isSeaLevelRef
= isSeaLevelRef
;
1458 #if defined(USE_SAFE_HOME)
1459 static void printSafeHomes(uint8_t dumpMask
, const navSafeHome_t
*navSafeHome
, const navSafeHome_t
*defaultSafeHome
)
1461 const char *format
= "safehome %u %u %d %d"; // uint8_t enabled, int32_t lat; int32_t lon
1462 for (uint8_t i
= 0; i
< MAX_SAFE_HOMES
; i
++) {
1463 bool equalsDefault
= false;
1464 if (defaultSafeHome
) {
1465 equalsDefault
= navSafeHome
[i
].enabled
== defaultSafeHome
[i
].enabled
1466 && navSafeHome
[i
].lat
== defaultSafeHome
[i
].lat
1467 && navSafeHome
[i
].lon
== defaultSafeHome
[i
].lon
;
1468 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1469 defaultSafeHome
[i
].enabled
, defaultSafeHome
[i
].lat
, defaultSafeHome
[i
].lon
);
1471 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
,
1472 navSafeHome
[i
].enabled
, navSafeHome
[i
].lat
, navSafeHome
[i
].lon
);
1476 static void cliSafeHomes(char *cmdline
)
1478 if (isEmpty(cmdline
)) {
1479 printSafeHomes(DUMP_MASTER
, safeHomeConfig(0), NULL
);
1480 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1483 int32_t lat
=0, lon
=0;
1485 uint8_t validArgumentCount
= 0;
1486 const char *ptr
= cmdline
;
1487 int8_t i
= fastA2I(ptr
);
1488 if (i
< 0 || i
>= MAX_SAFE_HOMES
) {
1489 cliShowArgumentRangeError("safehome index", 0, MAX_SAFE_HOMES
- 1);
1491 if ((ptr
= nextArg(ptr
))) {
1492 enabled
= fastA2I(ptr
);
1493 validArgumentCount
++;
1495 if ((ptr
= nextArg(ptr
))) {
1497 validArgumentCount
++;
1499 if ((ptr
= nextArg(ptr
))) {
1501 validArgumentCount
++;
1503 if ((ptr
= nextArg(ptr
))) {
1504 // check for too many arguments
1505 validArgumentCount
++;
1507 if (validArgumentCount
!= 3) {
1508 cliShowParseError();
1510 safeHomeConfigMutable(i
)->enabled
= enabled
;
1511 safeHomeConfigMutable(i
)->lat
= lat
;
1512 safeHomeConfigMutable(i
)->lon
= lon
;
1519 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1520 static void printWaypoints(uint8_t dumpMask
, const navWaypoint_t
*navWaypoint
, const navWaypoint_t
*defaultNavWaypoint
)
1522 cliPrintLinef("#wp %d %svalid", posControl
.waypointCount
, posControl
.waypointListValid
? "" : "in"); //int8_t bool
1523 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
1524 for (uint8_t i
= 0; i
< NAV_MAX_WAYPOINTS
; i
++) {
1525 bool equalsDefault
= false;
1526 if (defaultNavWaypoint
) {
1527 equalsDefault
= navWaypoint
[i
].action
== defaultNavWaypoint
[i
].action
1528 && navWaypoint
[i
].lat
== defaultNavWaypoint
[i
].lat
1529 && navWaypoint
[i
].lon
== defaultNavWaypoint
[i
].lon
1530 && navWaypoint
[i
].alt
== defaultNavWaypoint
[i
].alt
1531 && navWaypoint
[i
].p1
== defaultNavWaypoint
[i
].p1
1532 && navWaypoint
[i
].p2
== defaultNavWaypoint
[i
].p2
1533 && navWaypoint
[i
].p3
== defaultNavWaypoint
[i
].p3
1534 && navWaypoint
[i
].flag
== defaultNavWaypoint
[i
].flag
;
1535 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1537 defaultNavWaypoint
[i
].action
,
1538 defaultNavWaypoint
[i
].lat
,
1539 defaultNavWaypoint
[i
].lon
,
1540 defaultNavWaypoint
[i
].alt
,
1541 defaultNavWaypoint
[i
].p1
,
1542 defaultNavWaypoint
[i
].p2
,
1543 defaultNavWaypoint
[i
].p3
,
1544 defaultNavWaypoint
[i
].flag
1547 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1549 navWaypoint
[i
].action
,
1561 static void cliWaypoints(char *cmdline
)
1563 #ifdef USE_MULTI_MISSION
1564 static int8_t multiMissionWPCounter
= 0;
1566 if (isEmpty(cmdline
)) {
1567 printWaypoints(DUMP_MASTER
, posControl
.waypointList
, NULL
);
1568 } else if (sl_strcasecmp(cmdline
, "reset") == 0) {
1569 resetWaypointList();
1570 } else if (sl_strcasecmp(cmdline
, "load") == 0) {
1571 loadNonVolatileWaypointList(true);
1572 } else if (sl_strcasecmp(cmdline
, "save") == 0) {
1573 posControl
.waypointListValid
= false;
1574 for (int i
= 0; i
< NAV_MAX_WAYPOINTS
; i
++) {
1575 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;
1576 if (posControl
.waypointList
[i
].flag
== NAV_WP_FLAG_LAST
) {
1577 #ifdef USE_MULTI_MISSION
1578 if (posControl
.multiMissionCount
== 1) {
1579 posControl
.waypointCount
= i
+ 1;
1580 posControl
.waypointListValid
= true;
1581 multiMissionWPCounter
= 0;
1582 posControl
.multiMissionCount
= 0;
1585 posControl
.multiMissionCount
-= 1;
1588 posControl
.waypointCount
= i
+ 1;
1589 posControl
.waypointListValid
= true;
1594 if (posControl
.waypointListValid
) {
1595 saveNonVolatileWaypointList();
1597 cliShowParseError();
1600 int16_t i
, p1
=0,p2
=0,p3
=0,tmp
=0;
1601 uint8_t action
=0, flag
=0;
1602 int32_t lat
=0, lon
=0, alt
=0;
1603 uint8_t validArgumentCount
= 0;
1604 const char *ptr
= cmdline
;
1606 #ifdef USE_MULTI_MISSION
1607 if (i
+ multiMissionWPCounter
>= 0 && i
+ multiMissionWPCounter
< NAV_MAX_WAYPOINTS
) {
1609 if (i
>= 0 && i
< NAV_MAX_WAYPOINTS
) {
1613 action
= fastA2I(ptr
);
1614 validArgumentCount
++;
1619 validArgumentCount
++;
1624 validArgumentCount
++;
1629 validArgumentCount
++;
1634 validArgumentCount
++;
1639 validArgumentCount
++;
1641 /* We support pre-2.5 6 values (... p1,flags) or
1642 * 2.5 and later, 8 values (... p1,p2,p3,flags)
1648 validArgumentCount
++;
1651 flag
= fastA2I(ptr
);
1652 validArgumentCount
++;
1658 if (!(validArgumentCount
== 6 || validArgumentCount
== 8)) {
1659 cliShowParseError();
1660 } 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
)) {
1661 cliShowParseError();
1663 #ifdef USE_MULTI_MISSION
1664 if (i
+ multiMissionWPCounter
== 0) {
1665 posControl
.multiMissionCount
= 0;
1668 posControl
.waypointList
[i
+ multiMissionWPCounter
].action
= action
;
1669 posControl
.waypointList
[i
+ multiMissionWPCounter
].lat
= lat
;
1670 posControl
.waypointList
[i
+ multiMissionWPCounter
].lon
= lon
;
1671 posControl
.waypointList
[i
+ multiMissionWPCounter
].alt
= alt
;
1672 posControl
.waypointList
[i
+ multiMissionWPCounter
].p1
= p1
;
1673 posControl
.waypointList
[i
+ multiMissionWPCounter
].p2
= p2
;
1674 posControl
.waypointList
[i
+ multiMissionWPCounter
].p3
= p3
;
1675 posControl
.waypointList
[i
+ multiMissionWPCounter
].flag
= flag
;
1677 // Process WP entries made up of multiple successive WP missions (multiple NAV_WP_FLAG_LAST entries)
1678 // Individial missions loaded at runtime, mission selected nav_waypoint_multi_mission_index
1679 if (flag
== NAV_WP_FLAG_LAST
) {
1680 multiMissionWPCounter
+= i
+ 1;
1681 posControl
.multiMissionCount
+= 1;
1684 posControl
.waypointList
[i
].action
= action
;
1685 posControl
.waypointList
[i
].lat
= lat
;
1686 posControl
.waypointList
[i
].lon
= lon
;
1687 posControl
.waypointList
[i
].alt
= alt
;
1688 posControl
.waypointList
[i
].p1
= p1
;
1689 posControl
.waypointList
[i
].p2
= p2
;
1690 posControl
.waypointList
[i
].p3
= p3
;
1691 posControl
.waypointList
[i
].flag
= flag
;
1695 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS
- 1);
1702 #ifdef USE_LED_STRIP
1703 static void printLed(uint8_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
)
1705 const char *format
= "led %u %s";
1706 char ledConfigBuffer
[20];
1707 char ledConfigDefaultBuffer
[20];
1708 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1709 ledConfig_t ledConfig
= ledConfigs
[i
];
1710 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1711 bool equalsDefault
= false;
1712 if (defaultLedConfigs
) {
1713 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1714 equalsDefault
= !memcmp(&ledConfig
, &ledConfigDefault
, sizeof(ledConfig_t
));
1715 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1716 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1718 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1722 static void cliLed(char *cmdline
)
1727 if (isEmpty(cmdline
)) {
1728 printLed(DUMP_MASTER
, ledStripConfig()->ledConfigs
, NULL
);
1732 if (i
< LED_MAX_STRIP_LENGTH
) {
1733 ptr
= nextArg(cmdline
);
1734 if (!parseLedStripConfig(i
, ptr
)) {
1735 cliShowParseError();
1738 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH
- 1);
1743 static void printColor(uint8_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
)
1745 const char *format
= "color %u %d,%u,%u";
1746 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1747 const hsvColor_t
*color
= &colors
[i
];
1748 bool equalsDefault
= false;
1749 if (defaultColors
) {
1750 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1751 equalsDefault
= color
->h
== colorDefault
->h
1752 && color
->s
== colorDefault
->s
1753 && color
->v
== colorDefault
->v
;
1754 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1756 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1760 static void cliColor(char *cmdline
)
1762 if (isEmpty(cmdline
)) {
1763 printColor(DUMP_MASTER
, ledStripConfig()->colors
, NULL
);
1765 const char *ptr
= cmdline
;
1766 const int i
= fastA2I(ptr
);
1767 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1768 ptr
= nextArg(cmdline
);
1769 if (!parseColor(i
, ptr
)) {
1770 cliShowParseError();
1773 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1778 static void printModeColor(uint8_t dumpMask
, const ledStripConfig_t
*ledStripConfig
, const ledStripConfig_t
*defaultLedStripConfig
)
1780 const char *format
= "mode_color %u %u %u";
1781 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1782 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1783 int colorIndex
= ledStripConfig
->modeColors
[i
].color
[j
];
1784 bool equalsDefault
= false;
1785 if (defaultLedStripConfig
) {
1786 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1787 equalsDefault
= colorIndex
== colorIndexDefault
;
1788 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1790 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1794 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1795 const int colorIndex
= ledStripConfig
->specialColors
.color
[j
];
1796 bool equalsDefault
= false;
1797 if (defaultLedStripConfig
) {
1798 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1799 equalsDefault
= colorIndex
== colorIndexDefault
;
1800 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1802 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1806 static void cliModeColor(char *cmdline
)
1810 if (isEmpty(cmdline
)) {
1811 printModeColor(DUMP_MASTER
, ledStripConfig(), NULL
);
1813 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1814 int args
[ARGS_COUNT
];
1816 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1817 while (ptr
&& argNo
< ARGS_COUNT
) {
1818 args
[argNo
++] = fastA2I(ptr
);
1819 ptr
= strtok_r(NULL
, " ", &saveptr
);
1822 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1823 cliShowParseError();
1827 int modeIdx
= args
[MODE
];
1828 int funIdx
= args
[FUNCTION
];
1829 int color
= args
[COLOR
];
1830 if (!setModeColor(modeIdx
, funIdx
, color
)) {
1831 cliShowParseError();
1834 // values are validated
1835 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1839 static void cliLedPinPWM(char *cmdline
)
1843 if (isEmpty(cmdline
)) {
1845 cliPrintLine("PWM stopped");
1847 i
= fastA2I(cmdline
);
1849 cliPrintLinef("PWM started: %d%%",i
);
1854 static void cliDelay(char* cmdLine
) {
1856 if (isEmpty(cmdLine
)) {
1858 cliPrintLine("CLI delay deactivated");
1862 ms
= fastA2I(cmdLine
);
1865 cliPrintLinef("CLI delay set to %d ms", ms
);
1868 cliShowParseError();
1873 static void printServo(uint8_t dumpMask
, const servoParam_t
*servoParam
, const servoParam_t
*defaultServoParam
)
1875 // print out servo settings
1876 const char *format
= "servo %u %d %d %d %d";
1877 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1878 const servoParam_t
*servoConf
= &servoParam
[i
];
1879 bool equalsDefault
= false;
1880 if (defaultServoParam
) {
1881 const servoParam_t
*servoConfDefault
= &defaultServoParam
[i
];
1882 equalsDefault
= servoConf
->min
== servoConfDefault
->min
1883 && servoConf
->max
== servoConfDefault
->max
1884 && servoConf
->middle
== servoConfDefault
->middle
1885 && servoConf
->rate
== servoConfDefault
->rate
;
1886 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1888 servoConfDefault
->min
,
1889 servoConfDefault
->max
,
1890 servoConfDefault
->middle
,
1891 servoConfDefault
->rate
1894 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1904 static void cliServo(char *cmdline
)
1906 enum { SERVO_ARGUMENT_COUNT
= 5 };
1907 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1909 servoParam_t
*servo
;
1914 if (isEmpty(cmdline
)) {
1915 printServo(DUMP_MASTER
, servoParams(0), NULL
);
1917 int validArgumentCount
= 0;
1921 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1923 // If command line doesn't fit the format, don't modify the config
1925 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1926 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1927 cliShowParseError();
1931 arguments
[validArgumentCount
++] = fastA2I(ptr
);
1935 } while (*ptr
>= '0' && *ptr
<= '9');
1936 } else if (*ptr
== ' ') {
1939 cliShowParseError();
1944 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
};
1946 i
= arguments
[INDEX
];
1948 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1949 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1950 cliShowParseError();
1954 servo
= servoParamsMutable(i
);
1957 arguments
[MIN
] < SERVO_OUTPUT_MIN
|| arguments
[MIN
] > SERVO_OUTPUT_MAX
||
1958 arguments
[MAX
] < SERVO_OUTPUT_MIN
|| arguments
[MAX
] > SERVO_OUTPUT_MAX
||
1959 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
1960 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
1961 arguments
[RATE
] < -125 || arguments
[RATE
] > 125
1963 cliShowParseError();
1967 servo
->min
= arguments
[MIN
];
1968 servo
->max
= arguments
[MAX
];
1969 servo
->middle
= arguments
[MIDDLE
];
1970 servo
->rate
= arguments
[RATE
];
1974 static void printServoMix(uint8_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
)
1976 const char *format
= "smix %d %d %d %d %d %d";
1977 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1978 const servoMixer_t customServoMixer
= customServoMixers
[i
];
1979 if (customServoMixer
.rate
== 0) {
1983 bool equalsDefault
= false;
1984 if (defaultCustomServoMixers
) {
1985 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
1986 equalsDefault
= customServoMixer
.targetChannel
== customServoMixerDefault
.targetChannel
1987 && customServoMixer
.inputSource
== customServoMixerDefault
.inputSource
1988 && customServoMixer
.rate
== customServoMixerDefault
.rate
1989 && customServoMixer
.speed
== customServoMixerDefault
.speed
1990 #ifdef USE_PROGRAMMING_FRAMEWORK
1991 && customServoMixer
.conditionId
== customServoMixerDefault
.conditionId
1995 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1997 customServoMixerDefault
.targetChannel
,
1998 customServoMixerDefault
.inputSource
,
1999 customServoMixerDefault
.rate
,
2000 customServoMixerDefault
.speed
,
2001 #ifdef USE_PROGRAMMING_FRAMEWORK
2002 customServoMixer
.conditionId
2008 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2010 customServoMixer
.targetChannel
,
2011 customServoMixer
.inputSource
,
2012 customServoMixer
.rate
,
2013 customServoMixer
.speed
,
2014 #ifdef USE_PROGRAMMING_FRAMEWORK
2015 customServoMixer
.conditionId
2023 static void cliServoMix(char *cmdline
)
2026 int args
[6], check
= 0;
2027 uint8_t len
= strlen(cmdline
);
2030 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
);
2031 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2032 // erase custom mixer
2033 Reset_servoMixers(customServoMixersMutable(0));
2035 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, CONDITION
, ARGS_COUNT
};
2036 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2037 args
[CONDITION
] = -1;
2038 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2039 args
[check
++] = fastA2I(ptr
);
2040 ptr
= strtok_r(NULL
, " ", &saveptr
);
2043 if (ptr
!= NULL
|| (check
< ARGS_COUNT
- 1)) {
2044 cliShowParseError();
2048 int32_t i
= args
[RULE
];
2050 i
>= 0 && i
< MAX_SERVO_RULES
&&
2051 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
2052 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
2053 args
[RATE
] >= -1000 && args
[RATE
] <= 1000 &&
2054 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
2055 args
[CONDITION
] >= -1 && args
[CONDITION
] < MAX_LOGIC_CONDITIONS
2057 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
2058 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
2059 customServoMixersMutable(i
)->rate
= args
[RATE
];
2060 customServoMixersMutable(i
)->speed
= args
[SPEED
];
2061 #ifdef USE_PROGRAMMING_FRAMEWORK
2062 customServoMixersMutable(i
)->conditionId
= args
[CONDITION
];
2066 cliShowParseError();
2071 #ifdef USE_PROGRAMMING_FRAMEWORK
2073 static void printLogic(uint8_t dumpMask
, const logicCondition_t
*logicConditions
, const logicCondition_t
*defaultLogicConditions
, int16_t showLC
)
2075 const char *format
= "logic %d %d %d %d %d %d %d %d %d";
2076 for (uint8_t i
= 0; i
< MAX_LOGIC_CONDITIONS
; i
++) {
2077 if (showLC
== -1 || showLC
== i
) {
2078 const logicCondition_t logic
= logicConditions
[i
];
2080 bool equalsDefault
= false;
2081 if (defaultLogicConditions
) {
2082 logicCondition_t defaultValue
= defaultLogicConditions
[i
];
2084 logic
.enabled
== defaultValue
.enabled
&&
2085 logic
.activatorId
== defaultValue
.activatorId
&&
2086 logic
.operation
== defaultValue
.operation
&&
2087 logic
.operandA
.type
== defaultValue
.operandA
.type
&&
2088 logic
.operandA
.value
== defaultValue
.operandA
.value
&&
2089 logic
.operandB
.type
== defaultValue
.operandB
.type
&&
2090 logic
.operandB
.value
== defaultValue
.operandB
.value
&&
2091 logic
.flags
== defaultValue
.flags
;
2093 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2098 logic
.operandA
.type
,
2099 logic
.operandA
.value
,
2100 logic
.operandB
.type
,
2101 logic
.operandB
.value
,
2105 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2110 logic
.operandA
.type
,
2111 logic
.operandA
.value
,
2112 logic
.operandB
.type
,
2113 logic
.operandB
.value
,
2120 static void processCliLogic(char *cmdline
, int16_t lcIndex
) {
2122 int args
[9], check
= 0;
2123 uint8_t len
= strlen(cmdline
);
2126 if (!commandBatchActive
) {
2127 printLogic(DUMP_MASTER
, logicConditions(0), NULL
, -1);
2128 } else if (lcIndex
>= 0) {
2129 printLogic(DUMP_MASTER
, logicConditions(0), NULL
, lcIndex
);
2131 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2132 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS
);
2146 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2147 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2148 args
[check
++] = fastA2I(ptr
);
2149 ptr
= strtok_r(NULL
, " ", &saveptr
);
2152 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2153 cliShowParseError();
2157 int32_t i
= args
[INDEX
];
2159 i
>= 0 && i
< MAX_LOGIC_CONDITIONS
&&
2160 args
[ENABLED
] >= 0 && args
[ENABLED
] <= 1 &&
2161 args
[ACTIVATOR_ID
] >= -1 && args
[ACTIVATOR_ID
] < MAX_LOGIC_CONDITIONS
&&
2162 args
[OPERATION
] >= 0 && args
[OPERATION
] < LOGIC_CONDITION_LAST
&&
2163 args
[OPERAND_A_TYPE
] >= 0 && args
[OPERAND_A_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2164 args
[OPERAND_A_VALUE
] >= -1000000 && args
[OPERAND_A_VALUE
] <= 1000000 &&
2165 args
[OPERAND_B_TYPE
] >= 0 && args
[OPERAND_B_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2166 args
[OPERAND_B_VALUE
] >= -1000000 && args
[OPERAND_B_VALUE
] <= 1000000 &&
2167 args
[FLAGS
] >= 0 && args
[FLAGS
] <= 255
2170 logicConditionsMutable(i
)->enabled
= args
[ENABLED
];
2171 logicConditionsMutable(i
)->activatorId
= args
[ACTIVATOR_ID
];
2172 logicConditionsMutable(i
)->operation
= args
[OPERATION
];
2173 logicConditionsMutable(i
)->operandA
.type
= args
[OPERAND_A_TYPE
];
2174 logicConditionsMutable(i
)->operandA
.value
= args
[OPERAND_A_VALUE
];
2175 logicConditionsMutable(i
)->operandB
.type
= args
[OPERAND_B_TYPE
];
2176 logicConditionsMutable(i
)->operandB
.value
= args
[OPERAND_B_VALUE
];
2177 logicConditionsMutable(i
)->flags
= args
[FLAGS
];
2179 processCliLogic("", i
);
2181 cliShowParseError();
2186 static void cliLogic(char *cmdline
) {
2187 processCliLogic(cmdline
, -1);
2190 static void printGvar(uint8_t dumpMask
, const globalVariableConfig_t
*gvars
, const globalVariableConfig_t
*defaultGvars
)
2192 const char *format
= "gvar %d %d %d %d";
2193 for (uint32_t i
= 0; i
< MAX_GLOBAL_VARIABLES
; i
++) {
2194 const globalVariableConfig_t gvar
= gvars
[i
];
2196 bool equalsDefault
= false;
2198 globalVariableConfig_t defaultValue
= defaultGvars
[i
];
2200 gvar
.defaultValue
== defaultValue
.defaultValue
&&
2201 gvar
.min
== defaultValue
.min
&&
2202 gvar
.max
== defaultValue
.max
;
2204 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2211 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2220 static void cliGvar(char *cmdline
) {
2222 int args
[4], check
= 0;
2223 uint8_t len
= strlen(cmdline
);
2226 printGvar(DUMP_MASTER
, globalVariableConfigs(0), NULL
);
2227 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2228 pgResetCopy(globalVariableConfigsMutable(0), PG_GLOBAL_VARIABLE_CONFIG
);
2237 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2238 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2239 args
[check
++] = fastA2I(ptr
);
2240 ptr
= strtok_r(NULL
, " ", &saveptr
);
2243 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2244 cliShowParseError();
2248 int32_t i
= args
[INDEX
];
2250 i
>= 0 && i
< MAX_GLOBAL_VARIABLES
&&
2251 args
[DEFAULT
] >= INT32_MIN
&& args
[DEFAULT
] <= INT32_MAX
&&
2252 args
[MIN
] >= INT32_MIN
&& args
[MIN
] <= INT32_MAX
&&
2253 args
[MAX
] >= INT32_MIN
&& args
[MAX
] <= INT32_MAX
2255 globalVariableConfigsMutable(i
)->defaultValue
= args
[DEFAULT
];
2256 globalVariableConfigsMutable(i
)->min
= args
[MIN
];
2257 globalVariableConfigsMutable(i
)->max
= args
[MAX
];
2261 cliShowParseError();
2266 static void printPid(uint8_t dumpMask
, const programmingPid_t
*programmingPids
, const programmingPid_t
*defaultProgrammingPids
)
2268 const char *format
= "pid %d %d %d %d %d %d %d %d %d %d";
2269 for (uint32_t i
= 0; i
< MAX_PROGRAMMING_PID_COUNT
; i
++) {
2270 const programmingPid_t pid
= programmingPids
[i
];
2272 bool equalsDefault
= false;
2273 if (defaultProgrammingPids
) {
2274 programmingPid_t defaultValue
= defaultProgrammingPids
[i
];
2276 pid
.enabled
== defaultValue
.enabled
&&
2277 pid
.setpoint
.type
== defaultValue
.setpoint
.type
&&
2278 pid
.setpoint
.value
== defaultValue
.setpoint
.value
&&
2279 pid
.measurement
.type
== defaultValue
.measurement
.type
&&
2280 pid
.measurement
.value
== defaultValue
.measurement
.value
&&
2281 pid
.gains
.P
== defaultValue
.gains
.P
&&
2282 pid
.gains
.I
== defaultValue
.gains
.I
&&
2283 pid
.gains
.D
== defaultValue
.gains
.D
&&
2284 pid
.gains
.FF
== defaultValue
.gains
.FF
;
2286 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2291 pid
.measurement
.type
,
2292 pid
.measurement
.value
,
2299 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2304 pid
.measurement
.type
,
2305 pid
.measurement
.value
,
2314 static void cliPid(char *cmdline
) {
2316 int args
[10], check
= 0;
2317 uint8_t len
= strlen(cmdline
);
2320 printPid(DUMP_MASTER
, programmingPids(0), NULL
);
2321 } else if (sl_strncasecmp(cmdline
, "reset", 5) == 0) {
2322 pgResetCopy(programmingPidsMutable(0), PG_LOGIC_CONDITIONS
);
2337 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2338 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2339 args
[check
++] = fastA2I(ptr
);
2340 ptr
= strtok_r(NULL
, " ", &saveptr
);
2343 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2344 cliShowParseError();
2348 int32_t i
= args
[INDEX
];
2350 i
>= 0 && i
< MAX_PROGRAMMING_PID_COUNT
&&
2351 args
[ENABLED
] >= 0 && args
[ENABLED
] <= 1 &&
2352 args
[SETPOINT_TYPE
] >= 0 && args
[SETPOINT_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2353 args
[SETPOINT_VALUE
] >= -1000000 && args
[SETPOINT_VALUE
] <= 1000000 &&
2354 args
[MEASUREMENT_TYPE
] >= 0 && args
[MEASUREMENT_TYPE
] < LOGIC_CONDITION_OPERAND_TYPE_LAST
&&
2355 args
[MEASUREMENT_VALUE
] >= -1000000 && args
[MEASUREMENT_VALUE
] <= 1000000 &&
2356 args
[P_GAIN
] >= 0 && args
[P_GAIN
] <= INT16_MAX
&&
2357 args
[I_GAIN
] >= 0 && args
[I_GAIN
] <= INT16_MAX
&&
2358 args
[D_GAIN
] >= 0 && args
[D_GAIN
] <= INT16_MAX
&&
2359 args
[FF_GAIN
] >= 0 && args
[FF_GAIN
] <= INT16_MAX
2361 programmingPidsMutable(i
)->enabled
= args
[ENABLED
];
2362 programmingPidsMutable(i
)->setpoint
.type
= args
[SETPOINT_TYPE
];
2363 programmingPidsMutable(i
)->setpoint
.value
= args
[SETPOINT_VALUE
];
2364 programmingPidsMutable(i
)->measurement
.type
= args
[MEASUREMENT_TYPE
];
2365 programmingPidsMutable(i
)->measurement
.value
= args
[MEASUREMENT_VALUE
];
2366 programmingPidsMutable(i
)->gains
.P
= args
[P_GAIN
];
2367 programmingPidsMutable(i
)->gains
.I
= args
[I_GAIN
];
2368 programmingPidsMutable(i
)->gains
.D
= args
[D_GAIN
];
2369 programmingPidsMutable(i
)->gains
.FF
= args
[FF_GAIN
];
2373 cliShowParseError();
2378 static void printOsdCustomElements(uint8_t dumpMask
, const osdCustomElement_t
*osdCustomElements
, const osdCustomElement_t
*defaultosdCustomElements
)
2380 const char *format
= "osd_custom_elements %d %d %d %d %d %d %d %d %d \"%s\"";
2382 if(CUSTOM_ELEMENTS_PARTS
!= 3)
2384 cliPrintHashLine("Incompatible count of elements for custom OSD elements");
2387 for (uint8_t i
= 0; i
< MAX_CUSTOM_ELEMENTS
; i
++) {
2388 bool equalsDefault
= false;
2390 const osdCustomElement_t osdCustomElement
= osdCustomElements
[i
];
2391 if(defaultosdCustomElements
){
2392 const osdCustomElement_t defaultValue
= defaultosdCustomElements
[i
];
2394 osdCustomElement
.part
[0].type
== defaultValue
.part
[0].type
&&
2395 osdCustomElement
.part
[0].value
== defaultValue
.part
[0].value
&&
2396 osdCustomElement
.part
[1].type
== defaultValue
.part
[1].type
&&
2397 osdCustomElement
.part
[1].value
== defaultValue
.part
[1].value
&&
2398 osdCustomElement
.part
[2].type
== defaultValue
.part
[2].type
&&
2399 osdCustomElement
.part
[2].value
== defaultValue
.part
[2].value
&&
2400 osdCustomElement
.visibility
.type
== defaultValue
.visibility
.type
&&
2401 osdCustomElement
.visibility
.value
== defaultValue
.visibility
.value
&&
2402 strcmp(osdCustomElement
.osdCustomElementText
, defaultValue
.osdCustomElementText
) == 0;
2404 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2406 osdCustomElement
.part
[0].type
,
2407 osdCustomElement
.part
[0].value
,
2408 osdCustomElement
.part
[1].type
,
2409 osdCustomElement
.part
[1].value
,
2410 osdCustomElement
.part
[2].type
,
2411 osdCustomElement
.part
[2].value
,
2412 osdCustomElement
.visibility
.type
,
2413 osdCustomElement
.visibility
.value
,
2414 osdCustomElement
.osdCustomElementText
2418 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2420 osdCustomElement
.part
[0].type
,
2421 osdCustomElement
.part
[0].value
,
2422 osdCustomElement
.part
[1].type
,
2423 osdCustomElement
.part
[1].value
,
2424 osdCustomElement
.part
[2].type
,
2425 osdCustomElement
.part
[2].value
,
2426 osdCustomElement
.visibility
.type
,
2427 osdCustomElement
.visibility
.value
,
2428 osdCustomElement
.osdCustomElementText
2433 static void osdCustom(char *cmdline
){
2435 char * saveptrParams
;
2436 int args
[10], check
= 0;
2437 char text
[OSD_CUSTOM_ELEMENT_TEXT_SIZE
];
2438 uint8_t len
= strlen(cmdline
);
2441 printOsdCustomElements(DUMP_MASTER
, osdCustomElements(0), NULL
);
2443 //split by ", first are params second is text
2444 char *ptrMain
= strtok_r(cmdline
, "\"", &saveptrMain
);
2457 char *ptrParams
= strtok_r(ptrMain
, " ", &saveptrParams
);
2458 while (ptrParams
!= NULL
&& check
< ARGS_COUNT
) {
2459 args
[check
++] = fastA2I(ptrParams
);
2460 ptrParams
= strtok_r(NULL
, " ", &saveptrParams
);
2463 if (check
!= ARGS_COUNT
) {
2464 cliShowParseError();
2469 char *ptrText
= strtok_r(NULL
, "\"", &saveptrMain
);
2470 size_t copySize
= 0;
2471 if(ptrText
!= NULL
){
2472 copySize
= MIN(strlen(ptrText
), (size_t)(sizeof(text
) - 1));
2474 memcpy(text
, ptrText
, copySize
);
2477 text
[copySize
] = '\0';
2479 int32_t i
= args
[INDEX
];
2481 i
>= 0 && i
< MAX_CUSTOM_ELEMENTS
&&
2482 args
[PART0_TYPE
] >= 0 && args
[PART0_TYPE
] <= 7 &&
2483 args
[PART0_VALUE
] >= 0 && args
[PART0_VALUE
] <= UINT8_MAX
&&
2484 args
[PART1_TYPE
] >= 0 && args
[PART1_TYPE
] <= 7 &&
2485 args
[PART1_VALUE
] >= 0 && args
[PART1_VALUE
] <= UINT8_MAX
&&
2486 args
[PART2_TYPE
] >= 0 && args
[PART2_TYPE
] <= 7 &&
2487 args
[PART2_VALUE
] >= 0 && args
[PART2_VALUE
] <= UINT8_MAX
&&
2488 args
[VISIBILITY_TYPE
] >= 0 && args
[VISIBILITY_TYPE
] <= 2 &&
2489 args
[VISIBILITY_VALUE
] >= 0 && args
[VISIBILITY_VALUE
] <= UINT8_MAX
2491 osdCustomElementsMutable(i
)->part
[0].type
= args
[PART0_TYPE
];
2492 osdCustomElementsMutable(i
)->part
[0].value
= args
[PART0_VALUE
];
2493 osdCustomElementsMutable(i
)->part
[1].type
= args
[PART1_TYPE
];
2494 osdCustomElementsMutable(i
)->part
[1].value
= args
[PART1_VALUE
];
2495 osdCustomElementsMutable(i
)->part
[2].type
= args
[PART2_TYPE
];
2496 osdCustomElementsMutable(i
)->part
[2].value
= args
[PART2_VALUE
];
2497 osdCustomElementsMutable(i
)->visibility
.type
= args
[VISIBILITY_TYPE
];
2498 osdCustomElementsMutable(i
)->visibility
.value
= args
[VISIBILITY_VALUE
];
2499 memcpy(osdCustomElementsMutable(i
)->osdCustomElementText
, text
, OSD_CUSTOM_ELEMENT_TEXT_SIZE
);
2503 cliShowParseError();
2513 static void cliWriteBytes(const uint8_t *buffer
, int count
)
2522 static void cliSdInfo(char *cmdline
)
2526 cliPrint("SD card: ");
2528 if (!sdcard_isInserted()) {
2529 cliPrintLine("None inserted");
2533 if (!sdcard_isInitialized()) {
2534 cliPrintLine("Startup failed");
2538 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
2540 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2541 metadata
->manufacturerID
,
2542 metadata
->numBlocks
/ 2, /* One block is half a kB */
2543 metadata
->productionMonth
,
2544 metadata
->productionYear
,
2545 metadata
->productRevisionMajor
,
2546 metadata
->productRevisionMinor
2549 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
2551 cliPrint("'\r\n" "Filesystem: ");
2553 switch (afatfs_getFilesystemState()) {
2554 case AFATFS_FILESYSTEM_STATE_READY
:
2557 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
2558 cliPrint("Initializing");
2560 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
2561 case AFATFS_FILESYSTEM_STATE_FATAL
:
2564 switch (afatfs_getLastError()) {
2565 case AFATFS_ERROR_BAD_MBR
:
2566 cliPrint(" - no FAT MBR partitions");
2568 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
2569 cliPrint(" - bad FAT header");
2571 case AFATFS_ERROR_GENERIC
:
2572 case AFATFS_ERROR_NONE
:
2573 ; // Nothing more detailed to print
2585 static void cliFlashInfo(char *cmdline
)
2589 const flashGeometry_t
*layout
= flashGetGeometry();
2591 if (layout
->totalSize
== 0) {
2592 cliPrintLine("Flash not available");
2596 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2597 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
);
2599 for (uint8_t index
= 0; index
< FLASH_MAX_PARTITIONS
; index
++) {
2600 const flashPartition_t
*partition
;
2602 cliPrintLine("Paritions:");
2604 partition
= flashPartitionFindByIndex(index
);
2608 cliPrintLinef(" %d: %s %u %u", index
, flashPartitionGetTypeName(partition
->type
), partition
->startSector
, partition
->endSector
);
2611 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
2613 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2614 FLASH_PARTITION_SECTOR_COUNT(flashPartition
) * layout
->sectorSize
,
2620 static void cliFlashErase(char *cmdline
)
2624 const flashGeometry_t
*layout
= flashGetGeometry();
2626 if (layout
->totalSize
== 0) {
2627 cliPrintLine("Flash not available");
2631 cliPrintLine("Erasing...");
2632 flashfsEraseCompletely();
2634 while (!flashIsReady()) {
2638 cliPrintLine("Done.");
2641 #ifdef USE_FLASH_TOOLS
2643 static void cliFlashWrite(char *cmdline
)
2645 const uint32_t address
= fastA2I(cmdline
);
2646 const char *text
= strchr(cmdline
, ' ');
2649 cliShowParseError();
2651 flashfsSeekAbs(address
);
2652 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2655 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2659 static void cliFlashRead(char *cmdline
)
2661 uint32_t address
= fastA2I(cmdline
);
2663 const char *nextArg
= strchr(cmdline
, ' ');
2666 cliShowParseError();
2668 uint32_t length
= fastA2I(nextArg
);
2670 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2673 while (length
> 0) {
2674 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2676 for (int i
= 0; i
< bytesRead
; i
++) {
2677 cliWrite(buffer
[i
]);
2680 length
-= bytesRead
;
2681 address
+= bytesRead
;
2683 if (bytesRead
== 0) {
2684 //Assume we reached the end of the volume or something fatal happened
2696 static void printOsdLayout(uint8_t dumpMask
, const osdLayoutsConfig_t
*config
, const osdLayoutsConfig_t
*configDefault
, int layout
, int item
)
2698 // "<layout> <item> <col> <row> <visible>"
2699 const char *format
= "osd_layout %d %d %d %d %c";
2700 for (int ii
= 0; ii
< OSD_LAYOUT_COUNT
; ii
++) {
2701 if (layout
>= 0 && layout
!= ii
) {
2704 const uint16_t *layoutItems
= config
->item_pos
[ii
];
2705 const uint16_t *defaultLayoutItems
= configDefault
->item_pos
[ii
];
2706 for (int jj
= 0; jj
< OSD_ITEM_COUNT
; jj
++) {
2707 if (item
>= 0 && item
!= jj
) {
2710 bool equalsDefault
= layoutItems
[jj
] == defaultLayoutItems
[jj
];
2711 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2713 OSD_X(defaultLayoutItems
[jj
]),
2714 OSD_Y(defaultLayoutItems
[jj
]),
2715 OSD_VISIBLE(defaultLayoutItems
[jj
]) ? 'V' : 'H');
2717 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2719 OSD_X(layoutItems
[jj
]),
2720 OSD_Y(layoutItems
[jj
]),
2721 OSD_VISIBLE(layoutItems
[jj
]) ? 'V' : 'H');
2726 static void cliOsdLayout(char *cmdline
)
2734 bool visible
= false;
2735 char *tok
= strtok_r(cmdline
, " ", &saveptr
);
2739 for (ii
= 0; tok
!= NULL
; ii
++, tok
= strtok_r(NULL
, " ", &saveptr
)) {
2742 layout
= fastA2I(tok
);
2743 if (layout
< 0 || layout
>= OSD_LAYOUT_COUNT
) {
2744 cliShowParseError();
2749 item
= fastA2I(tok
);
2750 if (item
< 0 || item
>= OSD_ITEM_COUNT
) {
2751 cliShowParseError();
2757 if (col
< 0 || col
> OSD_X(OSD_POS_MAX
)) {
2758 cliShowParseError();
2764 if (row
< 0 || row
> OSD_Y(OSD_POS_MAX
)) {
2765 cliShowParseError();
2778 cliShowParseError();
2783 cliShowParseError();
2794 // No args, or just layout or layout and item. If any of them not provided,
2795 // it will be the -1 that we used during initialization, so printOsdLayout()
2796 // won't use them for filtering.
2797 printOsdLayout(DUMP_MASTER
, osdLayoutsConfig(), osdLayoutsConfig(), layout
, item
);
2800 // No visibility provided. Keep the previous one.
2801 visible
= OSD_VISIBLE(osdLayoutsConfig()->item_pos
[layout
][item
]);
2804 // Layout, item, pos and visibility. Set the item.
2805 osdLayoutsConfigMutable()->item_pos
[layout
][item
] = OSD_POS(col
, row
) | (visible
? OSD_VISIBLE_FLAG
: 0);
2809 cliShowParseError();
2816 static void printTimerOutputModes(dumpFlags_e dumpFlags
, const timerOverride_t
* to
, const timerOverride_t
* defaultTimerOverride
, int timer
)
2818 const char *format
= "timer_output_mode %d %s";
2820 for (int i
= 0; i
< HARDWARE_TIMER_DEFINITION_COUNT
; ++i
) {
2821 if (timer
< 0 || timer
== i
) {
2822 outputMode_e mode
= to
[i
].outputMode
;
2823 bool equalsDefault
= false;
2824 if(defaultTimerOverride
) {
2825 outputMode_e defaultMode
= defaultTimerOverride
[i
].outputMode
;
2826 equalsDefault
= mode
== defaultMode
;
2827 cliDefaultPrintLinef(dumpFlags
, equalsDefault
, format
, i
, outputModeNames
[defaultMode
]);
2829 cliDumpPrintLinef(dumpFlags
, equalsDefault
, format
, i
, outputModeNames
[mode
]);
2834 static void cliTimerOutputMode(char *cmdline
)
2840 char *tok
= strtok_r(cmdline
, " ", &saveptr
);
2844 for (ii
= 0; tok
!= NULL
; ii
++, tok
= strtok_r(NULL
, " ", &saveptr
)) {
2847 timer
= fastA2I(tok
);
2848 if (timer
< 0 || timer
>= HARDWARE_TIMER_DEFINITION_COUNT
) {
2849 cliShowParseError();
2854 if(!sl_strcasecmp("AUTO", tok
)) {
2855 mode
= OUTPUT_MODE_AUTO
;
2856 } else if(!sl_strcasecmp("MOTORS", tok
)) {
2857 mode
= OUTPUT_MODE_MOTORS
;
2858 } else if(!sl_strcasecmp("SERVOS", tok
)) {
2859 mode
= OUTPUT_MODE_SERVOS
;
2860 } else if(!sl_strcasecmp("LED", tok
)) {
2861 mode
= OUTPUT_MODE_LED
;
2863 cliShowParseError();
2868 cliShowParseError();
2877 // No args, or just timer. If any of them not provided,
2878 // it will be the -1 that we used during initialization, so printOsdLayout()
2879 // won't use them for filtering.
2880 printTimerOutputModes(DUMP_MASTER
, timerOverrides(0), NULL
, timer
);
2883 timerOverridesMutable(timer
)->outputMode
= mode
;
2884 printTimerOutputModes(DUMP_MASTER
, timerOverrides(0), NULL
, timer
);
2888 cliShowParseError();
2894 static void printFeature(uint8_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
)
2896 uint32_t mask
= featureConfig
->enabledFeatures
;
2897 uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
2898 for (uint32_t i
= 0; ; i
++) { // disable all feature first
2899 if (featureNames
[i
] == NULL
)
2901 if (featureNames
[i
][0] == '\0')
2903 const char *format
= "feature -%s";
2904 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2905 cliDumpPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2907 for (uint32_t i
= 0; ; i
++) { // reenable what we want.
2908 if (featureNames
[i
] == NULL
)
2910 if (featureNames
[i
][0] == '\0')
2912 const char *format
= "feature %s";
2913 if (defaultMask
& (1 << i
)) {
2914 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2916 if (mask
& (1 << i
)) {
2917 cliDumpPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2922 static void cliFeature(char *cmdline
)
2924 uint32_t len
= strlen(cmdline
);
2925 uint32_t mask
= featureMask();
2928 cliPrint("Enabled: ");
2929 for (uint32_t i
= 0; ; i
++) {
2930 if (featureNames
[i
] == NULL
)
2932 if (featureNames
[i
][0] == '\0')
2934 if (mask
& (1 << i
))
2935 cliPrintf("%s ", featureNames
[i
]);
2938 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
2939 cliPrint("Available: ");
2940 for (uint32_t i
= 0; ; i
++) {
2941 if (featureNames
[i
] == NULL
)
2943 if (featureNames
[i
][0] == '\0')
2945 cliPrintf("%s ", featureNames
[i
]);
2950 bool remove
= false;
2951 if (cmdline
[0] == '-') {
2954 cmdline
++; // skip over -
2958 for (uint32_t i
= 0; ; i
++) {
2959 if (featureNames
[i
] == NULL
) {
2960 cliPrintErrorLine("Invalid name");
2964 if (sl_strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
2968 if (mask
& FEATURE_GPS
) {
2969 cliPrintErrorLine("unavailable");
2975 cliPrint("Disabled");
2978 cliPrint("Enabled");
2980 cliPrintLinef(" %s", featureNames
[i
]);
2988 static void printBlackbox(uint8_t dumpMask
, const blackboxConfig_t
*config
, const blackboxConfig_t
*configDefault
)
2991 UNUSED(configDefault
);
2993 uint32_t mask
= config
->includeFlags
;
2995 for (uint8_t i
= 0; ; i
++) { // reenable what we want.
2996 if (blackboxIncludeFlagNames
[i
] == NULL
) {
3000 const char *formatOn
= "blackbox %s";
3001 const char *formatOff
= "blackbox -%s";
3003 if (mask
& (1 << i
)) {
3004 cliDumpPrintLinef(dumpMask
, false, formatOn
, blackboxIncludeFlagNames
[i
]);
3005 cliDefaultPrintLinef(dumpMask
, false, formatOn
, blackboxIncludeFlagNames
[i
]);
3007 cliDumpPrintLinef(dumpMask
, false, formatOff
, blackboxIncludeFlagNames
[i
]);
3008 cliDefaultPrintLinef(dumpMask
, false, formatOff
, blackboxIncludeFlagNames
[i
]);
3014 static void cliBlackbox(char *cmdline
)
3016 uint32_t len
= strlen(cmdline
);
3017 uint32_t mask
= blackboxConfig()->includeFlags
;
3020 cliPrint("Enabled: ");
3021 for (uint8_t i
= 0; ; i
++) {
3022 if (blackboxIncludeFlagNames
[i
] == NULL
) {
3026 if (mask
& (1 << i
))
3027 cliPrintf("%s ", blackboxIncludeFlagNames
[i
]);
3030 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
3031 cliPrint("Available: ");
3032 for (uint32_t i
= 0; ; i
++) {
3033 if (blackboxIncludeFlagNames
[i
] == NULL
) {
3037 cliPrintf("%s ", blackboxIncludeFlagNames
[i
]);
3042 bool remove
= false;
3043 if (cmdline
[0] == '-') {
3046 cmdline
++; // skip over -
3050 for (uint32_t i
= 0; ; i
++) {
3051 if (blackboxIncludeFlagNames
[i
] == NULL
) {
3052 cliPrintErrorLine("Invalid name");
3056 if (sl_strncasecmp(cmdline
, blackboxIncludeFlagNames
[i
], len
) == 0) {
3061 blackboxIncludeFlagClear(mask
);
3062 cliPrint("Disabled");
3064 blackboxIncludeFlagSet(mask
);
3065 cliPrint("Enabled");
3067 cliPrintLinef(" %s", blackboxIncludeFlagNames
[i
]);
3075 #if defined(BEEPER) || defined(USE_DSHOT)
3076 static void printBeeper(uint8_t dumpMask
, const beeperConfig_t
*beeperConfig
, const beeperConfig_t
*beeperConfigDefault
)
3078 const uint8_t beeperCount
= beeperTableEntryCount();
3079 const uint32_t mask
= beeperConfig
->beeper_off_flags
;
3080 const uint32_t defaultMask
= beeperConfigDefault
->beeper_off_flags
;
3081 for (int i
= 0; i
< beeperCount
- 2; i
++) {
3082 const char *formatOff
= "beeper -%s";
3083 const char *formatOn
= "beeper %s";
3084 cliDefaultPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOn
: formatOff
, beeperNameForTableIndex(i
));
3085 cliDumpPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOff
: formatOn
, beeperNameForTableIndex(i
));
3089 static void cliBeeper(char *cmdline
)
3091 uint32_t len
= strlen(cmdline
);
3092 uint8_t beeperCount
= beeperTableEntryCount();
3093 uint32_t mask
= getBeeperOffMask();
3096 cliPrintf("Disabled:");
3097 for (int32_t i
= 0; ; i
++) {
3098 if (i
== beeperCount
- 2){
3103 if (mask
& (1 << (beeperModeForTableIndex(i
) - 1)))
3104 cliPrintf(" %s", beeperNameForTableIndex(i
));
3107 } else if (sl_strncasecmp(cmdline
, "list", len
) == 0) {
3108 cliPrint("Available:");
3109 for (uint32_t i
= 0; i
< beeperCount
; i
++)
3110 cliPrintf(" %s", beeperNameForTableIndex(i
));
3114 bool remove
= false;
3115 if (cmdline
[0] == '-') {
3116 remove
= true; // this is for beeper OFF condition
3121 for (uint32_t i
= 0; ; i
++) {
3122 if (i
== beeperCount
) {
3123 cliPrintErrorLine("Invalid name");
3126 if (sl_strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0) {
3127 if (remove
) { // beeper off
3128 if (i
== BEEPER_ALL
-1)
3129 beeperOffSetAll(beeperCount
-2);
3131 if (i
== BEEPER_PREFERENCE
-1)
3132 setBeeperOffMask(getPreferredBeeperOffMask());
3134 mask
= 1 << (beeperModeForTableIndex(i
) - 1);
3137 cliPrint("Disabled");
3140 if (i
== BEEPER_ALL
-1)
3141 beeperOffClearAll();
3143 if (i
== BEEPER_PREFERENCE
-1)
3144 setPreferredBeeperOffMask(getBeeperOffMask());
3146 mask
= 1 << (beeperModeForTableIndex(i
) - 1);
3147 beeperOffClear(mask
);
3149 cliPrint("Enabled");
3151 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
3159 static void printMap(uint8_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
)
3161 bool equalsDefault
= true;
3163 char bufDefault
[16];
3166 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
3167 buf
[i
] = bufDefault
[i
] = 0;
3170 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
3171 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3172 if (defaultRxConfig
) {
3173 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3174 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
3179 const char *formatMap
= "map %s";
3180 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
3181 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
3184 static void cliMap(char *cmdline
)
3187 char out
[MAX_MAPPABLE_RX_INPUTS
+ 1];
3189 len
= strlen(cmdline
);
3191 if (len
== MAX_MAPPABLE_RX_INPUTS
) {
3193 for (uint32_t i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
3194 cmdline
[i
] = sl_toupper((unsigned char)cmdline
[i
]);
3196 for (uint32_t i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
3197 if (strchr(rcChannelLetters
, cmdline
[i
]) && !strchr(cmdline
+ i
+ 1, cmdline
[i
])) {
3200 cliShowParseError();
3203 parseRcChannels(cmdline
);
3204 } else if (len
!= 0) {
3205 cliShowParseError();
3209 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++){
3210 out
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
3213 cliPrintLinef("%s", out
);
3216 static const char *checkCommand(const char *cmdLine
, const char *command
)
3218 if (!sl_strncasecmp(cmdLine
, command
, strlen(command
)) // command names match
3219 && !sl_isalnum((unsigned)cmdLine
[strlen(command
)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
3220 return cmdLine
+ strlen(command
) + 1;
3226 static void cliRebootEx(bool bootLoader
)
3228 cliPrint("\r\nRebooting");
3229 bufWriterFlush(cliWriter
);
3230 waitForSerialPortToFinishTransmitting(cliPort
);
3232 fcReboot(bootLoader
);
3235 static void cliReboot(void)
3240 static void cliDfu(char *cmdline
)
3243 #ifndef CLI_MINIMAL_VERBOSITY
3244 cliPrint("\r\nRestarting in DFU mode");
3249 #if defined (USE_SERIALRX_SRXL2)
3250 void cliRxBind(char *cmdline
){
3252 if (rxConfig()->receiverType
== RX_TYPE_SERIAL
) {
3253 switch (rxConfig()->serialrx_provider
) {
3255 cliPrint("Not supported.");
3257 #if defined(USE_SERIALRX_SRXL2)
3258 case SERIALRX_SRXL2
:
3260 cliPrint("Binding SRXL2 receiver...");
3263 #if defined(USE_SERIALRX_CRSF)
3266 cliPrint("Binding CRSF receiver...");
3274 static void cliExit(char *cmdline
)
3278 #ifndef CLI_MINIMAL_VERBOSITY
3279 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
3281 bufWriterFlush(cliWriter
);
3286 // incase a motor was left running during motortest, clear it here
3287 mixerResetDisarmedMotors();
3294 static void cliGpsPassthrough(char *cmdline
)
3298 gpsEnablePassthrough(cliPort
);
3302 static void cliMotor(char *cmdline
)
3304 int motor_index
= 0;
3305 int motor_value
= 0;
3310 if (isEmpty(cmdline
)) {
3311 cliShowParseError();
3316 pch
= strtok_r(cmdline
, " ", &saveptr
);
3317 while (pch
!= NULL
) {
3320 motor_index
= fastA2I(pch
);
3323 motor_value
= fastA2I(pch
);
3327 pch
= strtok_r(NULL
, " ", &saveptr
);
3330 if (motor_index
< 0 || motor_index
>= MAX_SUPPORTED_MOTORS
) {
3331 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
3336 if (motor_value
< PWM_RANGE_MIN
|| motor_value
> PWM_RANGE_MAX
) {
3337 cliShowArgumentRangeError("value", 1000, 2000);
3340 motor_disarmed
[motor_index
] = motor_value
;
3344 cliPrintLinef("motor %d: %d", motor_index
, motor_disarmed
[motor_index
]);
3347 static void cliPlaySound(char *cmdline
)
3351 static int lastSoundIdx
= -1;
3353 if (isEmpty(cmdline
)) {
3354 i
= lastSoundIdx
+ 1; //next sound index
3355 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3356 while (true) { //no name for index; try next one
3357 if (++i
>= beeperTableEntryCount())
3358 i
= 0; //if end then wrap around to first entry
3359 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
3360 break; //if name OK then play sound below
3361 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
3362 cliPrintLine("Error playing sound");
3367 } else { //index value was given
3368 i
= fastA2I(cmdline
);
3369 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3370 cliPrintLinef("No sound for index %d", i
);
3376 cliPrintLinef("Playing sound %d: %s", i
, name
);
3377 beeper(beeperModeForTableIndex(i
));
3380 static void cliControlProfile(char *cmdline
)
3382 // CLI profile index is 1-based
3383 if (isEmpty(cmdline
)) {
3384 cliPrintLinef("control_profile %d", getConfigProfile() + 1);
3387 const int i
= fastA2I(cmdline
) - 1;
3388 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
3389 setConfigProfileAndWriteEEPROM(i
);
3390 cliControlProfile("");
3395 static void cliDumpControlProfile(uint8_t profileIndex
, uint8_t dumpMask
)
3397 if (profileIndex
>= MAX_PROFILE_COUNT
) {
3401 setConfigProfile(profileIndex
);
3402 cliPrintHashLine("control_profile");
3403 cliPrintLinef("control_profile %d\r\n", getConfigProfile() + 1);
3404 dumpAllValues(PROFILE_VALUE
, dumpMask
);
3405 dumpAllValues(CONTROL_RATE_VALUE
, dumpMask
);
3406 dumpAllValues(EZ_TUNE_VALUE
, dumpMask
);
3409 static void cliBatteryProfile(char *cmdline
)
3411 // CLI profile index is 1-based
3412 if (isEmpty(cmdline
)) {
3413 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
3416 const int i
= fastA2I(cmdline
) - 1;
3417 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
3418 setConfigBatteryProfileAndWriteEEPROM(i
);
3419 cliBatteryProfile("");
3424 static void cliDumpBatteryProfile(uint8_t profileIndex
, uint8_t dumpMask
)
3426 if (profileIndex
>= MAX_BATTERY_PROFILE_COUNT
) {
3430 setConfigBatteryProfile(profileIndex
);
3431 cliPrintHashLine("battery_profile");
3432 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
3433 dumpAllValues(BATTERY_CONFIG_VALUE
, dumpMask
);
3436 static void cliMixerProfile(char *cmdline
)
3438 // CLI profile index is 1-based
3439 if (isEmpty(cmdline
)) {
3440 cliPrintLinef("mixer_profile %d", getConfigMixerProfile() + 1);
3443 const int i
= fastA2I(cmdline
) - 1;
3444 if (i
>= 0 && i
< MAX_MIXER_PROFILE_COUNT
) {
3445 setConfigMixerProfileAndWriteEEPROM(i
);
3446 cliMixerProfile("");
3451 static void cliDumpMixerProfile(uint8_t profileIndex
, uint8_t dumpMask
)
3453 if (profileIndex
>= MAX_MIXER_PROFILE_COUNT
) {
3457 setConfigMixerProfile(profileIndex
);
3458 cliPrintHashLine("mixer_profile");
3459 cliPrintLinef("mixer_profile %d\r\n", getConfigMixerProfile() + 1);
3460 dumpAllValues(MIXER_CONFIG_VALUE
, dumpMask
);
3461 cliPrintHashLine("Mixer: motor mixer");
3462 cliDumpPrintLinef(dumpMask
, primaryMotorMixer_CopyArray()[0].throttle
== 0.0f
, "\r\nmmix reset\r\n");
3463 printMotorMix(dumpMask
, primaryMotorMixer_CopyArray(), primaryMotorMixer(0));
3464 cliPrintHashLine("Mixer: servo mixer");
3465 cliDumpPrintLinef(dumpMask
, customServoMixers_CopyArray()[0].rate
== 0, "smix reset\r\n");
3466 printServoMix(dumpMask
, customServoMixers_CopyArray(), customServoMixers(0));
3469 #ifdef USE_CLI_BATCH
3470 static void cliPrintCommandBatchWarning(const char *warning
)
3473 tfp_sprintf(errorBuf
, "%d ERRORS WERE DETECTED - Please review and fix before continuing!", commandBatchErrorCount
);
3475 cliPrintErrorLinef(errorBuf
);
3477 cliPrintErrorLinef(warning
);
3481 static void resetCommandBatch(void)
3483 commandBatchActive
= false;
3484 commandBatchError
= false;
3485 commandBatchErrorCount
= 0;
3488 static void cliBatch(char *cmdline
)
3490 if (strncasecmp(cmdline
, "start", 5) == 0) {
3491 if (!commandBatchActive
) {
3492 commandBatchActive
= true;
3493 commandBatchError
= false;
3494 commandBatchErrorCount
= 0;
3496 cliPrintLine("Command batch started");
3497 } else if (strncasecmp(cmdline
, "end", 3) == 0) {
3498 if (commandBatchActive
&& commandBatchError
) {
3499 cliPrintCommandBatchWarning(NULL
);
3501 cliPrintLine("Command batch ended");
3503 resetCommandBatch();
3505 cliPrintErrorLinef("Invalid option");
3510 static void cliSave(char *cmdline
)
3514 #ifdef USE_CLI_BATCH
3515 if (commandBatchActive
&& commandBatchError
) {
3516 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3517 resetCommandBatch();
3523 //copyCurrentProfileToProfileSlot(getConfigProfile();
3530 static void cliDefaults(char *cmdline
)
3534 cliPrint("Resetting to defaults");
3540 #ifdef USE_CLI_BATCH
3541 commandBatchError
= false;
3544 if (!checkCommand(cmdline
, "noreboot"))
3548 static void cliGet(char *cmdline
)
3550 const setting_t
*val
;
3551 int matchedCommands
= 0;
3552 char name
[SETTING_MAX_NAME_LENGTH
];
3554 while(*cmdline
== ' ') ++cmdline
; // ignore spaces
3556 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3557 val
= settingGet(i
);
3558 if (settingNameContains(val
, name
, cmdline
)) {
3559 cliPrintf("%s = ", name
);
3560 if (strcmp(name
, "name") == 0) {
3561 // if the craftname has a leading space, then enclose the name in quotes
3562 const char * v
= (const char *)settingGetValuePointer(val
);
3563 cliPrintf(v
[0] == ' ' ? "\"%s\"" : "%s", v
);
3565 cliPrintVar(val
, 0);
3568 cliPrintVarRange(val
);
3576 if (matchedCommands
) {
3580 cliPrintErrorLine("Invalid name");
3583 static void cliSet(char *cmdline
)
3586 const setting_t
*val
;
3588 char name
[SETTING_MAX_NAME_LENGTH
];
3590 while(*cmdline
== ' ') ++cmdline
; // ignore spaces
3592 len
= strlen(cmdline
);
3594 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
3595 cliPrintLine("Current settings:");
3596 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3597 val
= settingGet(i
);
3598 settingGetName(val
, name
);
3599 cliPrintf("%s = ", name
);
3600 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3603 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
3606 char *lastNonSpaceCharacter
= eqptr
;
3607 while (*(lastNonSpaceCharacter
- 1) == ' ') {
3608 lastNonSpaceCharacter
--;
3610 uint8_t variableNameLength
= lastNonSpaceCharacter
- cmdline
;
3612 // skip the '=' and any ' ' characters
3614 while (*(eqptr
) == ' ') {
3618 for (uint32_t i
= 0; i
< SETTINGS_TABLE_COUNT
; i
++) {
3619 val
= settingGet(i
);
3620 // ensure exact match when setting to prevent setting variables with shorter names
3621 if (settingNameIsExactMatch(val
, name
, cmdline
, variableNameLength
)) {
3622 const setting_type_e type
= SETTING_TYPE(val
);
3623 if (type
== VAR_STRING
) {
3624 // Convert strings to uppercase. Lower case is not supported by the OSD.
3625 sl_toupperptr(eqptr
);
3626 // if setting the craftname, remove any quotes around the name. This allows leading spaces in the name
3627 if ((strcmp(name
, "name") == 0 || strcmp(name
, "pilot_name") == 0) && (eqptr
[0] == '"' && eqptr
[strlen(eqptr
)-1] == '"')) {
3628 settingSetString(val
, eqptr
+ 1, strlen(eqptr
)-2);
3630 settingSetString(val
, eqptr
, strlen(eqptr
));
3634 const setting_mode_e mode
= SETTING_MODE(val
);
3635 bool changeValue
= false;
3636 int_float_value_t tmp
= {0};
3639 if (*eqptr
!= 0 && strspn(eqptr
, "0123456789.+-") == strlen(eqptr
)) {
3640 float valuef
= fastA2F(eqptr
);
3641 // note: compare float values
3642 if (valuef
>= (float)settingGetMin(val
) && valuef
<= (float)settingGetMax(val
)) {
3644 if (type
== VAR_FLOAT
)
3645 tmp
.float_value
= valuef
;
3646 else if (type
== VAR_UINT32
)
3647 tmp
.uint_value
= fastA2UL(eqptr
);
3649 tmp
.int_value
= fastA2I(eqptr
);
3657 const lookupTableEntry_t
*tableEntry
= settingLookupTable(val
);
3658 bool matched
= false;
3659 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
3660 matched
= sl_strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
3663 tmp
.int_value
= tableValueIndex
;
3672 cliSetIntFloatVar(val
, tmp
);
3674 cliPrintf("%s set to ", name
);
3675 cliPrintVar(val
, 0);
3677 cliPrintError("Invalid value. ");
3678 cliPrintVarRange(val
);
3685 cliPrintErrorLine("Invalid name");
3687 // no equals, check for matching variables.
3692 static const char * getBatteryStateString(void)
3694 static const char * const batteryStateStrings
[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
3696 return batteryStateStrings
[getBatteryState()];
3699 static void cliStatus(char *cmdline
)
3703 char buf
[MAX(FORMATTED_DATE_TIME_BUFSIZE
, SETTING_MAX_NAME_LENGTH
)];
3706 cliPrintLinef("%s/%s %s %s / %s (%s) %s",
3715 cliPrintLinef("GCC-%s",
3718 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3719 rtcGetDateTime(&dt
);
3720 dateTimeFormatLocal(buf
, &dt
);
3721 cliPrintLinef("Current Time: %s", buf
);
3722 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
3723 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock
/ 1000000));
3725 const uint32_t detectedSensorsMask
= sensorsMask();
3727 for (int i
= 0; i
< SENSOR_INDEX_COUNT
; i
++) {
3729 const uint32_t mask
= (1 << i
);
3730 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
3731 const int sensorHardwareIndex
= detectedSensors
[i
];
3732 if (sensorHardwareNames
[i
]) {
3733 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
3734 cliPrintf(", %s=%s", sensorTypeNames
[i
], sensorHardware
);
3739 #if !defined(SITL_BUILD)
3740 #if defined(AT32F43x)
3741 cliPrintLine("AT32 system clocks:");
3742 crm_clocks_freq_type clocks
;
3743 crm_clocks_freq_get(&clocks
);
3745 cliPrintLinef(" SYSCLK = %d MHz", clocks
.sclk_freq
/ 1000000);
3746 cliPrintLinef(" ABH = %d MHz", clocks
.ahb_freq
/ 1000000);
3747 cliPrintLinef(" ABP1 = %d MHz", clocks
.apb1_freq
/ 1000000);
3748 cliPrintLinef(" ABP2 = %d MHz", clocks
.apb2_freq
/ 1000000);
3750 cliPrintLine("STM32 system clocks:");
3751 #if defined(USE_HAL_DRIVER)
3752 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
3753 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
3754 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
3755 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
3757 RCC_ClocksTypeDef clocks
;
3758 RCC_GetClocksFreq(&clocks
);
3759 cliPrintLinef(" SYSCLK = %d MHz", clocks
.SYSCLK_Frequency
/ 1000000);
3760 cliPrintLinef(" HCLK = %d MHz", clocks
.HCLK_Frequency
/ 1000000);
3761 cliPrintLinef(" PCLK1 = %d MHz", clocks
.PCLK1_Frequency
/ 1000000);
3762 cliPrintLinef(" PCLK2 = %d MHz", clocks
.PCLK2_Frequency
/ 1000000);
3764 #endif // for if at32
3767 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
3768 hardwareSensorStatusNames
[getHwGyroStatus()],
3769 hardwareSensorStatusNames
[getHwAccelerometerStatus()],
3770 hardwareSensorStatusNames
[getHwCompassStatus()],
3771 hardwareSensorStatusNames
[getHwBarometerStatus()],
3772 hardwareSensorStatusNames
[getHwRangefinderStatus()],
3773 hardwareSensorStatusNames
[getHwOpticalFlowStatus()],
3774 hardwareSensorStatusNames
[getHwGPSStatus()]
3777 #ifdef USE_ESC_SENSOR
3778 uint8_t motorCount
= getMotorCount();
3779 if (STATE(ESC_SENSOR_ENABLED
) && motorCount
> 0) {
3780 cliPrintLinef("ESC Temperature(s): Motor Count = %d", motorCount
);
3781 for (uint8_t i
= 0; i
< motorCount
; i
++) {
3782 const escSensorData_t
*escState
= getEscTelemetry(i
); //Get ESC telemetry
3783 cliPrintf("ESC %d: %d\260C, ", i
, escState
->temperature
);
3793 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
3794 #elif !defined(SITL_BUILD)
3795 const uint16_t i2cErrorCounter
= 0;
3799 cliPrintf("Stack used: %d, ", stackUsedSize());
3801 #if !defined(SITL_BUILD)
3802 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
3804 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter
, getEEPROMConfigSize(), &__config_end
- &__config_start
);
3806 #if defined(USE_ADC) && !defined(SITL_BUILD)
3807 static char * adcFunctions
[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
3808 cliPrintLine("ADC channel usage:");
3809 for (int i
= 0; i
< ADC_FUNCTION_COUNT
; i
++) {
3810 cliPrintf(" %8s :", adcFunctions
[i
]);
3812 cliPrint(" configured = ");
3813 if (adcChannelConfig()->adcFunctionChannel
[i
] == ADC_CHN_NONE
) {
3817 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel
[i
]);
3820 cliPrint(", used = ");
3821 if (adcGetFunctionChannelAllocation(i
) == ADC_CHN_NONE
) {
3822 cliPrintLine("none");
3825 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i
));
3830 cliPrintf("System load: %d", averageSystemLoadPercent
);
3831 const timeDelta_t pidTaskDeltaTime
= getTaskDeltaTime(TASK_PID
);
3832 const int pidRate
= pidTaskDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)pidTaskDeltaTime
));
3833 const int rxRate
= getTaskDeltaTime(TASK_RX
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_RX
)));
3834 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
3835 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime
, pidRate
, rxRate
, systemRate
);
3836 #if !defined(CLI_MINIMAL_VERBOSITY)
3837 cliPrint("Arming disabled flags:");
3838 uint32_t flags
= armingFlags
& ARMING_DISABLED_ALL_FLAGS
;
3840 int bitpos
= ffs(flags
) - 1;
3841 flags
&= ~(1 << bitpos
);
3842 if (bitpos
> 6) cliPrintf(" %s", armingDisableFlagNames
[bitpos
- 7]);
3845 if (armingFlags
& ARMING_DISABLED_INVALID_SETTING
) {
3846 unsigned invalidIndex
;
3847 if (!settingsValidate(&invalidIndex
)) {
3848 settingGetName(settingGet(invalidIndex
), buf
);
3849 cliPrintErrorLinef("Invalid setting: %s", buf
);
3853 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags
& ARMING_DISABLED_ALL_FLAGS
);
3856 #if !defined(CLI_MINIMAL_VERBOSITY)
3858 #if defined(USE_OSD)
3859 displayPort_t
*osdDisplayPort
= osdGetDisplayPort();
3860 if (osdDisplayPort
!= NULL
) {
3861 cliPrintf("%s [%u x %u]", osdDisplayPort
->displayPortType
, osdDisplayPort
->cols
, osdDisplayPort
->rows
);
3863 cliPrint("not enabled");
3866 cliPrint("not used");
3871 #if defined(USE_VTX_CONTROL)
3872 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
3873 vtxDeviceOsdInfo_t osdInfo
;
3874 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo
);
3875 cliPrintf("band: %c, chan: %s, power: %c", osdInfo
.bandLetter
, osdInfo
.channelName
, osdInfo
.powerIndexLetter
);
3877 if (osdInfo
.powerMilliwatt
) {
3878 cliPrintf(" (%d mW)", osdInfo
.powerMilliwatt
);
3881 if (osdInfo
.frequency
) {
3882 cliPrintf(", freq: %d MHz", osdInfo
.frequency
);
3886 cliPrint("not detected");
3889 cliPrint("no VTX control");
3895 if (featureConfigured(FEATURE_GPS
) && isGpsUblox()) {
3897 cliPrintf("HW Version: %s Proto: %d.%02d Baud: %d", getGpsHwVersion(), getGpsProtoMajorVersion(), getGpsProtoMinorVersion(), getGpsBaudrate());
3898 if(ubloxVersionLT(15, 0)) {
3899 cliPrintf(" (UBLOX Proto >= 15.0 required)");
3902 cliPrintLinef(" SATS: %i", gpsSol
.numSat
);
3903 cliPrintLinef(" HDOP: %f", (double)(gpsSol
.hdop
/ (float)HDOP_SCALE
));
3904 cliPrintLinef(" EPH : %f m", (double)(gpsSol
.eph
/ 100.0f
));
3905 cliPrintLinef(" EPV : %f m", (double)(gpsSol
.epv
/ 100.0f
));
3906 //cliPrintLinef(" GNSS Capabilities: %d", gpsUbloxCapLastUpdate());
3907 cliPrintLinef(" GNSS Capabilities:");
3908 cliPrintLine(" GNSS Provider active/default");
3909 cliPrintLine(" GPS 1/1");
3910 if(gpsUbloxHasGalileo())
3911 cliPrintLinef(" Galileo %d/%d", gpsUbloxGalileoEnabled(), gpsUbloxGalileoDefault());
3912 if(gpsUbloxHasBeidou())
3913 cliPrintLinef(" BeiDou %d/%d", gpsUbloxBeidouEnabled(), gpsUbloxBeidouDefault());
3914 if(gpsUbloxHasGlonass())
3915 cliPrintLinef(" Glonass %d/%d", gpsUbloxGlonassEnabled(), gpsUbloxGlonassDefault());
3916 cliPrintLinef(" Max concurrent: %d", gpsUbloxMaxGnss());
3919 // If we are blocked by PWM init - provide more information
3920 if (getPwmInitError() != PWM_INIT_ERROR_NONE
) {
3921 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3925 static void cliTasks(char *cmdline
)
3929 int averageLoadSum
= 0;
3930 cfCheckFuncInfo_t checkFuncInfo
;
3932 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3933 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
3934 cfTaskInfo_t taskInfo
;
3935 getTaskInfo(taskId
, &taskInfo
);
3936 if (taskInfo
.isEnabled
) {
3937 const int taskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3938 const int maxLoad
= (taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
3939 const int averageLoad
= (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
3940 if (taskId
!= TASK_SERIAL
) {
3941 maxLoadSum
+= maxLoad
;
3942 averageLoadSum
+= averageLoad
;
3944 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
3945 taskId
, taskInfo
.taskName
, taskFrequency
, (uint32_t)taskInfo
.maxExecutionTime
, (uint32_t)taskInfo
.averageExecutionTime
,
3946 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, (uint32_t)taskInfo
.totalExecutionTime
/ 1000);
3949 getCheckFuncInfo(&checkFuncInfo
);
3950 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo
.maxExecutionTime
, (uint32_t)checkFuncInfo
.averageExecutionTime
, (uint32_t)checkFuncInfo
.totalExecutionTime
/ 1000);
3951 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
3954 static void cliVersion(char *cmdline
)
3958 cliPrintLinef("# %s/%s %s %s / %s (%s) %s",
3967 cliPrintLinef("# GCC-%s",
3972 static void cliMemory(char *cmdline
)
3975 cliPrintLinef("Dynamic memory usage:");
3976 for (unsigned i
= 0; i
< OWNER_TOTAL_COUNT
; i
++) {
3977 const char * owner
= ownerNames
[i
];
3978 const uint32_t memUsed
= memGetUsedBytesByOwner(i
);
3981 cliPrintLinef("%s : %d bytes", owner
, memUsed
);
3986 static void cliResource(char *cmdline
)
3989 cliPrintLinef("IO:\r\n----------------------");
3990 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
3992 owner
= ownerNames
[ioRecs
[i
].owner
];
3994 const char* resource
;
3995 resource
= resourceNames
[ioRecs
[i
].resource
];
3997 if (ioRecs
[i
].index
> 0) {
3998 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
, ioRecs
[i
].index
, resource
);
4000 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
, resource
);
4005 static void backupConfigs(void)
4007 // make copies of configs to do differencing
4009 if (pgIsProfile(pg
)) {
4010 memcpy(pg
->copy
, pg
->address
, pgSize(pg
) * MAX_PROFILE_COUNT
);
4012 memcpy(pg
->copy
, pg
->address
, pgSize(pg
));
4017 static void restoreConfigs(void)
4020 if (pgIsProfile(pg
)) {
4021 memcpy(pg
->address
, pg
->copy
, pgSize(pg
) * MAX_PROFILE_COUNT
);
4023 memcpy(pg
->address
, pg
->copy
, pgSize(pg
));
4028 static void printConfig(const char *cmdline
, bool doDiff
)
4030 uint8_t dumpMask
= DUMP_MASTER
;
4031 const char *options
;
4032 if ((options
= checkCommand(cmdline
, "master"))) {
4033 dumpMask
= DUMP_MASTER
; // only
4034 } else if ((options
= checkCommand(cmdline
, "control_profile"))) {
4035 dumpMask
= DUMP_CONTROL_PROFILE
; // only
4036 } else if ((options
= checkCommand(cmdline
, "mixer_profile"))) {
4037 dumpMask
= DUMP_MIXER_PROFILE
; // only
4038 } else if ((options
= checkCommand(cmdline
, "battery_profile"))) {
4039 dumpMask
= DUMP_BATTERY_PROFILE
; // only
4040 } else if ((options
= checkCommand(cmdline
, "all"))) {
4041 dumpMask
= DUMP_ALL
; // all profiles and rates
4047 dumpMask
= dumpMask
| DO_DIFF
;
4050 const int currentControlProfileIndexSave
= getConfigProfile();
4051 const int currentMixerProfileIndexSave
= getConfigMixerProfile();
4052 const int currentBatteryProfileIndexSave
= getConfigBatteryProfile();
4054 // reset all configs to defaults to do differencing
4056 // restore the profile indices, since they should not be reset for proper comparison
4057 setConfigProfile(currentControlProfileIndexSave
);
4058 setConfigMixerProfile(currentMixerProfileIndexSave
);
4059 setConfigBatteryProfile(currentBatteryProfileIndexSave
);
4061 if (checkCommand(options
, "showdefaults")) {
4062 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
4065 #ifdef USE_CLI_BATCH
4066 bool batchModeEnabled
= false;
4069 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
4070 cliPrintHashLine("version");
4073 #ifdef USE_CLI_BATCH
4074 cliPrintHashLine("start the command batch");
4075 cliPrintLine("batch start");
4076 batchModeEnabled
= true;
4079 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
4080 #ifndef CLI_MINIMAL_VERBOSITY
4081 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
4083 cliPrintLinef("defaults noreboot");
4087 cliPrintHashLine("resources");
4088 //printResource(dumpMask, &defaultConfig);
4090 cliPrintHashLine("Timer overrides");
4091 printTimerOutputModes(dumpMask
, timerOverrides_CopyArray
, timerOverrides(0), -1);
4093 // print servo parameters
4094 cliPrintHashLine("Outputs [servo]");
4095 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0));
4097 #if defined(USE_SAFE_HOME)
4098 cliPrintHashLine("safehome");
4099 printSafeHomes(dumpMask
, safeHomeConfig_CopyArray
, safeHomeConfig(0));
4102 #ifdef USE_FW_AUTOLAND
4103 cliPrintHashLine("Fixed Wing Approach");
4104 printFwAutolandApproach(dumpMask
, fwAutolandApproachConfig_CopyArray
, fwAutolandApproachConfig(0));
4107 cliPrintHashLine("features");
4108 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig());
4110 #if defined(BEEPER) || defined(USE_DSHOT)
4111 cliPrintHashLine("beeper");
4112 printBeeper(dumpMask
, &beeperConfig_Copy
, beeperConfig());
4116 cliPrintHashLine("blackbox");
4117 printBlackbox(dumpMask
, &blackboxConfig_Copy
, blackboxConfig());
4120 cliPrintHashLine("Receiver: Channel map");
4121 printMap(dumpMask
, &rxConfig_Copy
, rxConfig());
4123 cliPrintHashLine("Ports");
4124 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig());
4126 #ifdef USE_LED_STRIP
4127 cliPrintHashLine("LEDs");
4128 printLed(dumpMask
, ledStripConfig_Copy
.ledConfigs
, ledStripConfig()->ledConfigs
);
4130 cliPrintHashLine("LED color");
4131 printColor(dumpMask
, ledStripConfig_Copy
.colors
, ledStripConfig()->colors
);
4133 cliPrintHashLine("LED mode_color");
4134 printModeColor(dumpMask
, &ledStripConfig_Copy
, ledStripConfig());
4137 cliPrintHashLine("Modes [aux]");
4138 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0));
4140 cliPrintHashLine("Adjustments [adjrange]");
4141 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0));
4143 cliPrintHashLine("Receiver rxrange");
4144 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0));
4146 #ifdef USE_TEMPERATURE_SENSOR
4147 cliPrintHashLine("temp_sensor");
4148 printTempSensor(dumpMask
, tempSensorConfig_CopyArray
, tempSensorConfig(0));
4151 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4152 cliPrintHashLine("Mission Control Waypoints [wp]");
4153 printWaypoints(dumpMask
, posControl
.waypointList
, nonVolatileWaypointList(0));
4157 cliPrintHashLine("OSD [osd_layout]");
4158 printOsdLayout(dumpMask
, &osdLayoutsConfig_Copy
, osdLayoutsConfig(), -1, -1);
4161 #ifdef USE_PROGRAMMING_FRAMEWORK
4162 cliPrintHashLine("Programming: logic");
4163 printLogic(dumpMask
, logicConditions_CopyArray
, logicConditions(0), -1);
4165 cliPrintHashLine("Programming: global variables");
4166 printGvar(dumpMask
, globalVariableConfigs_CopyArray
, globalVariableConfigs(0));
4168 cliPrintHashLine("Programming: PID controllers");
4169 printPid(dumpMask
, programmingPids_CopyArray
, programmingPids(0));
4171 #ifdef USE_PROGRAMMING_FRAMEWORK
4172 cliPrintHashLine("OSD: custom elements");
4173 printOsdCustomElements(dumpMask
, osdCustomElements_CopyArray
, osdCustomElements(0));
4176 cliPrintHashLine("master");
4177 dumpAllValues(MASTER_VALUE
, dumpMask
);
4179 if (dumpMask
& DUMP_ALL
) {
4180 // dump all profiles
4181 const int currentControlProfileIndexSave
= getConfigProfile();
4182 const int currentMixerProfileIndexSave
= getConfigMixerProfile();
4183 const int currentBatteryProfileIndexSave
= getConfigBatteryProfile();
4184 for (int ii
= 0; ii
< MAX_PROFILE_COUNT
; ++ii
) {
4185 cliDumpControlProfile(ii
, dumpMask
);
4187 for (int ii
= 0; ii
< MAX_MIXER_PROFILE_COUNT
; ++ii
) {
4188 cliDumpMixerProfile(ii
, dumpMask
);
4190 for (int ii
= 0; ii
< MAX_BATTERY_PROFILE_COUNT
; ++ii
) {
4191 cliDumpBatteryProfile(ii
, dumpMask
);
4193 setConfigProfile(currentControlProfileIndexSave
);
4194 setConfigMixerProfile(currentMixerProfileIndexSave
);
4195 setConfigBatteryProfile(currentBatteryProfileIndexSave
);
4197 cliPrintHashLine("restore original profile selection");
4198 cliPrintLinef("control_profile %d", currentControlProfileIndexSave
+ 1);
4199 cliPrintLinef("mixer_profile %d", currentMixerProfileIndexSave
+ 1);
4200 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave
+ 1);
4202 #ifdef USE_CLI_BATCH
4203 batchModeEnabled
= false;
4206 // dump just the current profiles
4207 cliDumpControlProfile(getConfigProfile(), dumpMask
);
4208 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask
);
4209 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask
);
4213 if (dumpMask
& DUMP_CONTROL_PROFILE
) {
4214 cliDumpControlProfile(getConfigProfile(), dumpMask
);
4217 if (dumpMask
& DUMP_MIXER_PROFILE
) {
4218 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask
);
4221 if (dumpMask
& DUMP_BATTERY_PROFILE
) {
4222 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask
);
4225 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
4226 cliPrintHashLine("save configuration\r\nsave");
4229 #ifdef USE_CLI_BATCH
4230 if (batchModeEnabled
) {
4231 cliPrintHashLine("end the command batch");
4232 cliPrintLine("batch end");
4236 // restore configs from copies
4240 static void cliDump(char *cmdline
)
4242 printConfig(cmdline
, false);
4245 static void cliDiff(char *cmdline
)
4247 printConfig(cmdline
, true);
4251 static void cliMsc(char *cmdline
)
4257 || sdcard_isFunctional()
4260 || flashfsGetSize() > 0
4263 cliPrintHashLine("restarting in mass storage mode");
4264 cliPrint("\r\nRebooting");
4265 bufWriterFlush(cliWriter
);
4267 waitForSerialPortToFinishTransmitting(cliPort
);
4269 systemResetRequest(RESET_MSC_REQUEST
);
4271 cliPrint("\r\nStorage not present or failed to initialize!");
4272 bufWriterFlush(cliWriter
);
4280 #ifndef SKIP_CLI_COMMAND_HELP
4281 const char *description
;
4284 void (*func
)(char *cmdline
);
4287 #ifndef SKIP_CLI_COMMAND_HELP
4288 #define CLI_COMMAND_DEF(name, description, args, method) \
4296 #define CLI_COMMAND_DEF(name, description, args, method) \
4303 static void cliCmdDebug(char *arg
)
4306 if (debugMode
!= DEBUG_NONE
) {
4307 cliPrintLinef("Debug fields: [%s (%i)]", debugMode
< DEBUG_COUNT
? debugModeNames
[debugMode
] : "unknown", debugMode
);
4308 for (int i
= 0; i
< DEBUG32_VALUE_COUNT
; i
++) {
4309 cliPrintLinef("debug[%d] = %d", i
, debug
[i
]);
4312 cliPrintLine("Debug mode is disabled");
4317 #if defined(USE_GPS) && defined(USE_GPS_PROTO_UBLOX)
4319 static const char* _ubloxGetSigId(uint8_t gnssId
, uint8_t sigId
)
4323 case 0: return "GPS L1C/A";
4324 case 3: return "GPS L2 CL";
4325 case 4: return "GPS L2 CM";
4326 case 6: return "GPS L5 I";
4327 case 7: return "GPS L5 Q";
4328 default: return "GPS Unknown";
4330 } else if(gnssId
== 1) {
4332 case 0: return "SBAS L1C/A";
4333 default: return "SBAS Unknown";
4335 } else if(gnssId
== 2) {
4337 case 0: return "Galileo E1 C";
4338 case 1: return "Galileo E1 B";
4339 case 3: return "Galileo E5 al";
4340 case 4: return "Galileo E5 aQ";
4341 case 5: return "Galileo E5 bl";
4342 case 6: return "Galileo E5 bQ";
4343 default: return "Galileo Unknown";
4345 } else if(gnssId
== 3) {
4347 case 0: return "BeiDou B1I D1";
4348 case 1: return "BeiDou B1I D2";
4349 case 2: return "BeiDou B2I D1";
4350 case 3: return "BeiDou B2I D2";
4351 case 5: return "BeiDou B1C";
4352 case 7: return "BeiDou B2a";
4353 default: return "BeiDou Unknown";
4355 } else if(gnssId
== 5) {
4357 case 0: return "QZSS L1C/A";
4358 case 1: return "QZSS L1S";
4359 case 4: return "QZSS L2 CM";
4360 case 5: return "QZSS L2 CL";
4361 case 8: return "QZSS L5 I";
4362 case 9: return "QZSS L5 Q";
4363 default: return "QZSS Unknown";
4365 } else if(gnssId
== 6) {
4367 case 0: return "GLONASS L1 OF";
4368 case 2: return "GLONASS L2 OF";
4369 default: return "GLONASS Unknown";
4373 return "Unknown GNSS/SigId";
4376 static const char *_ubloxGetQuality(uint8_t quality
)
4379 case UBLOX_SIG_QUALITY_NOSIGNAL
: return "No signal";
4380 case UBLOX_SIG_QUALITY_SEARCHING
: return "Searching signal...";
4381 case UBLOX_SIG_QUALITY_ACQUIRED
: return "Signal acquired";
4382 case UBLOX_SIG_QUALITY_UNUSABLE
: return "Signal detected but unusable";
4383 case UBLOX_SIG_QUALITY_CODE_LOCK_TIME_SYNC
: return "Code locked and time sync";
4384 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC
:
4385 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC2
:
4386 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC3
:
4387 return "Code and carrier locked and time sync";
4388 default: return "Unknown";
4392 static void cliUbloxPrintSatelites(char *arg
)
4395 if(!isGpsUblox() /*|| !(gpsState.flags.sig || gpsState.flags.sat)*/) {
4396 cliPrint("GPS is not UBLOX or does not report satelites.");
4400 cliPrintLine("UBLOX Satelites");
4402 for(int i
= 0; i
< UBLOX_MAX_SIGNALS
; ++i
)
4404 const ubx_nav_sig_info
*sat
= gpsGetUbloxSatelite(i
);
4409 cliPrintLinef("satelite[%d]: %d:%d", i
+1, sat
->gnssId
, sat
->svId
);
4410 cliPrintLinef("sigId: %d (%s)", sat
->sigId
, _ubloxGetSigId(sat
->gnssId
, sat
->sigId
));
4411 cliPrintLinef("signal strength: %i dbHz", sat
->cno
);
4412 cliPrintLinef("quality: %i (%s)", sat
->quality
, _ubloxGetQuality(sat
->quality
));
4413 //cliPrintLinef("Correlation: %i", sat->corrSource);
4414 //cliPrintLinef("Iono model: %i", sat->ionoModel);
4415 cliPrintLinef("signal flags: 0x%02X", sat
->sigFlags
);
4416 switch(sat
->sigFlags
& UBLOX_SIG_HEALTH_MASK
) {
4417 case UBLOX_SIG_HEALTH_HEALTHY
:
4418 cliPrintLine("signal: Healthy");
4420 case UBLOX_SIG_HEALTH_UNHEALTHY
:
4421 cliPrintLine("signal: Unhealthy");
4423 case UBLOX_SIG_HEALTH_UNKNOWN
:
4425 cliPrintLinef("signal: Unknown (0x%X)", sat
->sigFlags
& UBLOX_SIG_HEALTH_MASK
);
4433 static void cliHelp(char *cmdline
);
4435 // should be sorted a..z for bsearch()
4436 const clicmd_t cmdTable
[] = {
4437 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
4438 #if defined(USE_ASSERT)
4439 CLI_COMMAND_DEF("assert", "", NULL
, cliAssert
),
4441 CLI_COMMAND_DEF("aux", "configure modes", NULL
, cliAux
),
4442 #ifdef USE_CLI_BATCH
4443 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch
),
4445 #if defined(BEEPER) || defined(USE_DSHOT)
4446 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
4447 "\t<+|->[name]", cliBeeper
),
4449 #if defined (USE_SERIALRX_SRXL2)
4450 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL
, cliRxBind
),
4452 #if defined(USE_BOOTLOG)
4453 CLI_COMMAND_DEF("bootlog", "show boot events", NULL
, cliBootlog
),
4455 #ifdef USE_LED_STRIP
4456 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
4457 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
4459 CLI_COMMAND_DEF("cli_delay", "CLI Delay", "Delay in ms", cliDelay
),
4460 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL
, cliDefaults
),
4461 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL
, cliDfu
),
4462 CLI_COMMAND_DEF("diff", "list configuration changes from default",
4463 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDiff
),
4464 CLI_COMMAND_DEF("dump", "dump configuration",
4465 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDump
),
4466 #ifdef USE_RX_ELERES
4467 CLI_COMMAND_DEF("eleres_bind", NULL
, NULL
, cliEleresBind
),
4468 #endif // USE_RX_ELERES
4469 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
4470 CLI_COMMAND_DEF("feature", "configure features",
4472 "\t<+|->[name]", cliFeature
),
4474 CLI_COMMAND_DEF("blackbox", "configure blackbox fields",
4476 "\t<+|->[name]", cliBlackbox
),
4479 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
4480 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
4481 #ifdef USE_FLASH_TOOLS
4482 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
4483 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
4486 #ifdef USE_FW_AUTOLAND
4487 CLI_COMMAND_DEF("fwapproach", "Fixed Wing Approach Settings", NULL
, cliFwAutolandApproach
),
4489 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
4491 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
4492 CLI_COMMAND_DEF("gpssats", "show GPS satellites", NULL
, cliUbloxPrintSatelites
),
4494 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
4495 #ifdef USE_LED_STRIP
4496 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
4497 CLI_COMMAND_DEF("ledpinpwm", "start/stop PWM on LED pin, 0..100 duty ratio", "[<value>]\r\n", cliLedPinPWM
),
4499 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
4500 CLI_COMMAND_DEF("memory", "view memory usage", NULL
, cliMemory
),
4501 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
4502 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
4504 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
4506 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]\r\n", cliPlaySound
),
4507 CLI_COMMAND_DEF("control_profile", "change control profile", "[<index>]", cliControlProfile
),
4508 CLI_COMMAND_DEF("mixer_profile", "change mixer profile", "[<index>]", cliMixerProfile
),
4509 CLI_COMMAND_DEF("battery_profile", "change battery profile", "[<index>]", cliBatteryProfile
),
4510 CLI_COMMAND_DEF("resource", "view currently used resources", NULL
, cliResource
),
4511 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
4512 #if defined(USE_SAFE_HOME)
4513 CLI_COMMAND_DEF("safehome", "safe home list", NULL
, cliSafeHomes
),
4515 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
4516 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
4517 #ifdef USE_SERIAL_PASSTHROUGH
4518 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough
),
4520 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
4521 #ifdef USE_PROGRAMMING_FRAMEWORK
4522 CLI_COMMAND_DEF("logic", "configure logic conditions",
4523 "<rule> <enabled> <activatorId> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
4524 "\treset\r\n", cliLogic
),
4526 CLI_COMMAND_DEF("gvar", "configure global variables",
4527 "<gvar> <default> <min> <max>\r\n"
4528 "\treset\r\n", cliGvar
),
4530 CLI_COMMAND_DEF("pid", "configurable PID controllers",
4531 "<#> <enabled> <setpoint type> <setpoint value> <measurement type> <measurement value> <P gain> <I gain> <D gain> <FF gain>\r\n"
4532 "\treset\r\n", cliPid
),
4534 CLI_COMMAND_DEF("osd_custom_elements", "configurable OSD custom elements",
4535 "<#> <part0 type> <part0 value> <part1 type> <part1 value> <part2 type> <part2 value> <visibility type> <visibility value> <text>\r\n"
4538 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
4539 CLI_COMMAND_DEF("smix", "servo mixer",
4540 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
4541 "\treset\r\n", cliServoMix
),
4543 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
4545 CLI_COMMAND_DEF("showdebug", "Show debug fields.", NULL
, cliCmdDebug
),
4546 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
4547 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
4548 #ifdef USE_TEMPERATURE_SENSOR
4549 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL
, cliTempSensor
),
4551 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
4552 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4553 CLI_COMMAND_DEF("wp", "waypoint list", NULL
, cliWaypoints
),
4556 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout
),
4558 CLI_COMMAND_DEF("timer_output_mode", "get or set the outputmode for a given timer.", "[<timer> [<AUTO|MOTORS|SERVOS>]]", cliTimerOutputMode
),
4561 static void cliHelp(char *cmdline
)
4565 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
4566 cliPrint(cmdTable
[i
].name
);
4567 #ifndef SKIP_CLI_COMMAND_HELP
4568 if (cmdTable
[i
].description
) {
4569 cliPrintf(" - %s", cmdTable
[i
].description
);
4571 if (cmdTable
[i
].args
) {
4572 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
4579 void cliProcess(void)
4585 // Be a little bit tricky. Flush the last inputs buffer, if any.
4586 bufWriterFlush(cliWriter
);
4588 while (serialRxBytesWaiting(cliPort
)) {
4589 uint8_t c
= serialRead(cliPort
);
4590 if (c
== '\t' || c
== '?') {
4591 // do tab completion
4592 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
4593 uint32_t i
= bufferIndex
;
4594 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4595 if (bufferIndex
&& (sl_strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
4601 if (pstart
) { /* Buffer matches one or more commands */
4602 for (; ; bufferIndex
++) {
4603 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
4605 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
4606 /* Unambiguous -- append a space */
4607 cliBuffer
[bufferIndex
++] = ' ';
4608 cliBuffer
[bufferIndex
] = '\0';
4611 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
4614 if (!bufferIndex
|| pstart
!= pend
) {
4615 /* Print list of ambiguous matches */
4616 cliPrint("\r\033[K");
4617 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
4618 cliPrint(cmd
->name
);
4622 i
= 0; /* Redraw prompt */
4624 for (; i
< bufferIndex
; i
++)
4625 cliWrite(cliBuffer
[i
]);
4626 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
4629 } else if (c
== 12) { // NewPage / CTRL-L
4631 cliPrint("\033[2J\033[1;1H");
4633 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
4637 // Strip comment starting with # from line
4638 char *p
= cliBuffer
;
4641 bufferIndex
= (uint32_t)(p
- cliBuffer
);
4644 // Strip trailing whitespace
4645 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
4649 // Process non-empty lines
4650 if (bufferIndex
> 0) {
4651 cliBuffer
[bufferIndex
] = 0; // null terminate
4653 const clicmd_t
*cmd
;
4654 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4655 if (!sl_strncasecmp(cliBuffer
, cmd
->name
, strlen(cmd
->name
)) // command names match
4656 && !sl_isalnum((unsigned)cliBuffer
[strlen(cmd
->name
)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
4659 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
))
4660 cmd
->func(cliBuffer
+ strlen(cmd
->name
) + 1);
4662 cliPrintError("Unknown command, try 'help'");
4666 ZERO_FARRAY(cliBuffer
);
4668 // 'exit' will reset this flag, so we don't need to print prompt again
4673 } else if (c
== 127) {
4676 cliBuffer
[--bufferIndex
] = 0;
4677 cliPrint("\010 \010");
4679 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
4680 if (!bufferIndex
&& c
== ' ')
4681 continue; // Ignore leading spaces
4682 cliBuffer
[bufferIndex
++] = c
;
4688 void cliEnter(serialPort_t
*serialPort
)
4695 cliPort
= serialPort
;
4696 setPrintfSerialPort(cliPort
);
4697 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
4699 #ifndef CLI_MINIMAL_VERBOSITY
4700 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4702 cliPrintLine("\r\nCLI");
4706 #ifdef USE_CLI_BATCH
4707 resetCommandBatch();
4710 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI
);
4713 void cliInit(const serialConfig_t
*serialConfig
)
4715 UNUSED(serialConfig
);