2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
31 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
32 // signal that we're in cli mode
37 #include "blackbox/blackbox.h"
39 #include "build/build_config.h"
40 #include "build/debug.h"
41 #include "build/version.h"
43 #include "cli/settings.h"
47 #include "common/axis.h"
48 #include "common/color.h"
49 #include "common/maths.h"
50 #include "common/printf.h"
51 #include "common/printf_serial.h"
52 #include "common/strtol.h"
53 #include "common/time.h"
54 #include "common/typeconversion.h"
55 #include "common/utils.h"
57 #include "config/config.h"
58 #include "config/config_eeprom.h"
59 #include "config/feature.h"
61 #include "drivers/accgyro/accgyro.h"
62 #include "drivers/adc.h"
63 #include "drivers/buf_writer.h"
64 #include "drivers/bus_spi.h"
65 #include "drivers/dma.h"
66 #include "drivers/dma_reqmap.h"
67 #include "drivers/dshot.h"
68 #include "drivers/dshot_command.h"
69 #include "drivers/dshot_dpwm.h"
70 #include "drivers/pwm_output_dshot_shared.h"
71 #include "drivers/camera_control.h"
72 #include "drivers/compass/compass.h"
73 #include "drivers/display.h"
74 #include "drivers/dma.h"
75 #include "drivers/flash.h"
76 #include "drivers/inverter.h"
77 #include "drivers/io.h"
78 #include "drivers/io_impl.h"
79 #include "drivers/light_led.h"
80 #include "drivers/motor.h"
81 #include "drivers/rangefinder/rangefinder_hcsr04.h"
82 #include "drivers/resource.h"
83 #include "drivers/sdcard.h"
84 #include "drivers/sensor.h"
85 #include "drivers/serial.h"
86 #include "drivers/serial_escserial.h"
87 #include "drivers/sound_beeper.h"
88 #include "drivers/stack_check.h"
89 #include "drivers/system.h"
90 #include "drivers/time.h"
91 #include "drivers/timer.h"
92 #include "drivers/transponder_ir.h"
93 #include "drivers/usb_msc.h"
94 #include "drivers/vtx_common.h"
95 #include "drivers/vtx_table.h"
97 #include "fc/board_info.h"
98 #include "fc/controlrate_profile.h"
101 #include "fc/rc_adjustments.h"
102 #include "fc/rc_controls.h"
103 #include "fc/runtime_config.h"
105 #include "flight/failsafe.h"
106 #include "flight/imu.h"
107 #include "flight/mixer.h"
108 #include "flight/pid.h"
109 #include "flight/position.h"
110 #include "flight/servos.h"
112 #include "io/asyncfatfs/asyncfatfs.h"
113 #include "io/beeper.h"
114 #include "io/flashfs.h"
115 #include "io/gimbal.h"
117 #include "io/ledstrip.h"
118 #include "io/serial.h"
119 #include "io/transponder_ir.h"
120 #include "io/usb_msc.h"
121 #include "io/vtx_control.h"
125 #include "msp/msp_box.h"
126 #include "msp/msp_protocol.h"
131 #include "pg/beeper.h"
132 #include "pg/beeper_dev.h"
133 #include "pg/board.h"
134 #include "pg/bus_i2c.h"
135 #include "pg/bus_spi.h"
136 #include "pg/gyrodev.h"
137 #include "pg/max7456.h"
139 #include "pg/motor.h"
140 #include "pg/pinio.h"
141 #include "pg/pin_pull_up_down.h"
143 #include "pg/pg_ids.h"
145 #include "pg/rx_pwm.h"
146 #include "pg/rx_spi_cc2500.h"
147 #include "pg/serial_uart.h"
149 #include "pg/timerio.h"
150 #include "pg/timerup.h"
152 #include "pg/vtx_table.h"
154 #include "rx/rx_bind.h"
155 #include "rx/rx_spi.h"
157 #include "scheduler/scheduler.h"
159 #include "sensors/acceleration.h"
160 #include "sensors/adcinternal.h"
161 #include "sensors/barometer.h"
162 #include "sensors/battery.h"
163 #include "sensors/boardalignment.h"
164 #include "sensors/compass.h"
165 #include "sensors/esc_sensor.h"
166 #include "sensors/gyro.h"
167 #include "sensors/gyro_init.h"
168 #include "sensors/sensors.h"
170 #include "telemetry/frsky_hub.h"
171 #include "telemetry/telemetry.h"
175 static serialPort_t
*cliPort
= NULL
;
178 #define CLI_IN_BUFFER_SIZE 128
180 // Space required to set array parameters
181 #define CLI_IN_BUFFER_SIZE 256
183 #define CLI_OUT_BUFFER_SIZE 64
185 static bufWriter_t
*cliWriter
= NULL
;
186 static bufWriter_t
*cliErrorWriter
= NULL
;
187 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + CLI_OUT_BUFFER_SIZE
];
189 static char cliBuffer
[CLI_IN_BUFFER_SIZE
];
190 static uint32_t bufferIndex
= 0;
192 static bool configIsInCopy
= false;
194 #define CURRENT_PROFILE_INDEX -1
195 static int8_t pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
196 static int8_t rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
199 static bool commandBatchActive
= false;
200 static bool commandBatchError
= false;
203 #if defined(USE_BOARD_INFO)
204 static bool boardInformationUpdated
= false;
205 #if defined(USE_SIGNATURE)
206 static bool signatureUpdated
= false;
208 #endif // USE_BOARD_INFO
210 static const char* const emptyName
= "-";
211 static const char* const emptyString
= "";
213 #if !defined(USE_CUSTOM_DEFAULTS)
214 #define CUSTOM_DEFAULTS_START ((char*)0)
215 #define CUSTOM_DEFAULTS_END ((char *)0)
217 extern char __custom_defaults_start
;
218 extern char __custom_defaults_end
;
219 #define CUSTOM_DEFAULTS_START (&__custom_defaults_start)
220 #define CUSTOM_DEFAULTS_END (&__custom_defaults_end)
222 static bool processingCustomDefaults
= false;
223 static char cliBufferTemp
[CLI_IN_BUFFER_SIZE
];
226 #if defined(USE_CUSTOM_DEFAULTS_ADDRESS)
227 static char __attribute__ ((section(".custom_defaults_start_address"))) *customDefaultsStart
= CUSTOM_DEFAULTS_START
;
228 static char __attribute__ ((section(".custom_defaults_end_address"))) *customDefaultsEnd
= CUSTOM_DEFAULTS_END
;
231 #ifndef USE_QUAD_MIXER_ONLY
232 // sync this with mixerMode_e
233 static const char * const mixerNames
[] = {
234 "TRI", "QUADP", "QUADX", "BI",
235 "GIMBAL", "Y6", "HEX6",
236 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
237 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
238 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
239 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
243 // sync this with features_e
244 static const char * const featureNames
[] = {
245 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
246 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
247 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
248 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
249 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
250 "", "", "RX_SPI", "", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
253 // sync this with rxFailsafeChannelMode_e
254 static const char rxFailsafeModeCharacters
[] = "ahs";
256 static const rxFailsafeChannelMode_e rxFailsafeModesTable
[RX_FAILSAFE_TYPE_COUNT
][RX_FAILSAFE_MODE_COUNT
] = {
257 { RX_FAILSAFE_MODE_AUTO
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
},
258 { RX_FAILSAFE_MODE_INVALID
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
}
261 #if defined(USE_SENSOR_NAMES)
262 // sync this with sensors_e
263 static const char *const sensorTypeNames
[] = {
264 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
267 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
269 static const char * const *sensorHardwareNames
[] = {
270 lookupTableGyroHardware
, lookupTableAccHardware
, lookupTableBaroHardware
, lookupTableMagHardware
, lookupTableRangefinderHardware
272 #endif // USE_SENSOR_NAMES
274 // Needs to be aligned with mcuTypeId_e
275 static const char *mcuTypeNames
[] = {
287 "H743 (Rev Unknown)",
293 typedef enum dumpFlags_e
{
294 DUMP_MASTER
= (1 << 0),
295 DUMP_PROFILE
= (1 << 1),
296 DUMP_RATES
= (1 << 2),
299 SHOW_DEFAULTS
= (1 << 5),
300 HIDE_UNUSED
= (1 << 6),
301 HARDWARE_ONLY
= (1 << 7),
305 typedef bool printFn(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...);
308 REBOOT_TARGET_FIRMWARE
,
309 REBOOT_TARGET_BOOTLOADER_ROM
,
310 REBOOT_TARGET_BOOTLOADER_FLASH
,
313 typedef struct serialPassthroughPort_s
{
318 } serialPassthroughPort_t
;
320 static void cliWriterFlushInternal(bufWriter_t
*writer
)
323 bufWriterFlush(writer
);
327 void cliPrintInternal(bufWriter_t
*writer
, const char *str
)
331 bufWriterAppend(writer
, *str
++);
333 cliWriterFlushInternal(writer
);
337 static void cliWriterFlush()
339 cliWriterFlushInternal(cliWriter
);
342 void cliPrint(const char *str
)
344 cliPrintInternal(cliWriter
, str
);
347 void cliPrintLinefeed(void)
352 void cliPrintLine(const char *str
)
359 #define cliPrintHashLine(str)
361 static void cliPrintHashLine(const char *str
)
368 static void cliPutp(void *p
, char ch
)
370 bufWriterAppend(p
, ch
);
373 static void cliPrintfva(const char *format
, va_list va
)
376 tfp_format(cliWriter
, cliPutp
, format
, va
);
381 static bool cliDumpPrintLinef(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...)
383 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
385 va_start(va
, format
);
386 cliPrintfva(format
, va
);
395 static void cliWrite(uint8_t ch
)
398 bufWriterAppend(cliWriter
, ch
);
402 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...)
404 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
408 va_start(va
, format
);
409 cliPrintfva(format
, va
);
418 void cliPrintf(const char *format
, ...)
421 va_start(va
, format
);
422 cliPrintfva(format
, va
);
427 void cliPrintLinef(const char *format
, ...)
430 va_start(va
, format
);
431 cliPrintfva(format
, va
);
436 static void cliPrintErrorVa(const char *format
, va_list va
)
438 if (cliErrorWriter
) {
439 cliPrintInternal(cliErrorWriter
, "###ERROR: ");
441 tfp_format(cliErrorWriter
, cliPutp
, format
, va
);
444 cliPrintInternal(cliErrorWriter
, "###");
448 if (commandBatchActive
) {
449 commandBatchError
= true;
454 static void cliPrintError(const char *format
, ...)
457 va_start(va
, format
);
458 cliPrintErrorVa(format
, va
);
461 // Supply our own linefeed in case we are printing inside a custom defaults operation
462 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
463 // instead of expecting the directly following command to supply the line feed.
464 cliPrintInternal(cliErrorWriter
, "\r\n");
468 static void cliPrintErrorLinef(const char *format
, ...)
471 va_start(va
, format
);
472 cliPrintErrorVa(format
, va
);
473 cliPrintInternal(cliErrorWriter
, "\r\n");
476 static void getMinMax(const clivalue_t
*var
, int *min
, int *max
)
478 switch (var
->type
& VALUE_TYPE_MASK
) {
481 *min
= var
->config
.minmaxUnsigned
.min
;
482 *max
= var
->config
.minmaxUnsigned
.max
;
486 *min
= var
->config
.minmax
.min
;
487 *max
= var
->config
.minmax
.max
;
493 static void printValuePointer(const clivalue_t
*var
, const void *valuePointer
, bool full
)
495 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
496 for (int i
= 0; i
< var
->config
.array
.length
; i
++) {
497 switch (var
->type
& VALUE_TYPE_MASK
) {
501 cliPrintf("%d", ((uint8_t *)valuePointer
)[i
]);
506 cliPrintf("%d", ((int8_t *)valuePointer
)[i
]);
511 cliPrintf("%d", ((uint16_t *)valuePointer
)[i
]);
516 cliPrintf("%d", ((int16_t *)valuePointer
)[i
]);
521 cliPrintf("%u", ((uint32_t *)valuePointer
)[i
]);
525 if (i
< var
->config
.array
.length
- 1) {
532 switch (var
->type
& VALUE_TYPE_MASK
) {
534 value
= *(uint8_t *)valuePointer
;
538 value
= *(int8_t *)valuePointer
;
542 value
= *(uint16_t *)valuePointer
;
546 value
= *(int16_t *)valuePointer
;
550 value
= *(uint32_t *)valuePointer
;
555 bool valueIsCorrupted
= false;
556 switch (var
->type
& VALUE_MODE_MASK
) {
558 if ((var
->type
& VALUE_TYPE_MASK
) == VAR_UINT32
) {
559 cliPrintf("%u", (uint32_t)value
);
560 if ((uint32_t)value
> var
->config
.u32Max
) {
561 valueIsCorrupted
= true;
563 cliPrintf(" 0 %u", var
->config
.u32Max
);
568 getMinMax(var
, &min
, &max
);
570 cliPrintf("%d", value
);
571 if ((value
< min
) || (value
> max
)) {
572 valueIsCorrupted
= true;
574 cliPrintf(" %d %d", min
, max
);
579 if (value
< lookupTables
[var
->config
.lookup
.tableIndex
].valueCount
) {
580 cliPrint(lookupTables
[var
->config
.lookup
.tableIndex
].values
[value
]);
582 valueIsCorrupted
= true;
586 if (value
& 1 << var
->config
.bitpos
) {
593 cliPrintf("%s", (strlen((char *)valuePointer
) == 0) ? "-" : (char *)valuePointer
);
597 if (valueIsCorrupted
) {
599 cliPrintError("CORRUPTED CONFIG: %s = %d", var
->name
, value
);
605 static bool valuePtrEqualsDefault(const clivalue_t
*var
, const void *ptr
, const void *ptrDefault
)
608 int elementCount
= 1;
609 uint32_t mask
= 0xffffffff;
611 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
612 elementCount
= var
->config
.array
.length
;
614 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
615 mask
= 1 << var
->config
.bitpos
;
617 for (int i
= 0; i
< elementCount
; i
++) {
618 switch (var
->type
& VALUE_TYPE_MASK
) {
620 result
= result
&& (((uint8_t *)ptr
)[i
] & mask
) == (((uint8_t *)ptrDefault
)[i
] & mask
);
624 result
= result
&& ((int8_t *)ptr
)[i
] == ((int8_t *)ptrDefault
)[i
];
628 result
= result
&& (((uint16_t *)ptr
)[i
] & mask
) == (((uint16_t *)ptrDefault
)[i
] & mask
);
631 result
= result
&& ((int16_t *)ptr
)[i
] == ((int16_t *)ptrDefault
)[i
];
634 result
= result
&& (((uint32_t *)ptr
)[i
] & mask
) == (((uint32_t *)ptrDefault
)[i
] & mask
);
642 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask
, bool outputFlag
, const char *headingStr
)
644 if (headingStr
&& (!(dumpMask
& DO_DIFF
) || outputFlag
)) {
645 cliPrintHashLine(headingStr
);
652 static void backupPgConfig(const pgRegistry_t
*pg
)
654 memcpy(pg
->copy
, pg
->address
, pg
->size
);
657 static void restorePgConfig(const pgRegistry_t
*pg
)
659 memcpy(pg
->address
, pg
->copy
, pg
->size
);
662 static void backupConfigs(void)
664 if (configIsInCopy
) {
668 // make copies of configs to do differencing
673 configIsInCopy
= true;
676 static void restoreConfigs(void)
678 if (!configIsInCopy
) {
686 configIsInCopy
= false;
689 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
690 static bool isReadingConfigFromCopy()
692 return configIsInCopy
;
696 static bool isWritingConfigToCopy()
698 return configIsInCopy
699 #if defined(USE_CUSTOM_DEFAULTS)
700 && !processingCustomDefaults
705 #if defined(USE_CUSTOM_DEFAULTS)
706 static bool cliProcessCustomDefaults(bool quiet
);
709 static void backupAndResetConfigs(const bool useCustomDefaults
)
713 // reset all configs to defaults to do differencing
716 #if defined(USE_CUSTOM_DEFAULTS)
717 if (useCustomDefaults
) {
718 if (!cliProcessCustomDefaults(true)) {
719 cliPrintLine("###WARNING: NO CUSTOM DEFAULTS FOUND###");
723 UNUSED(useCustomDefaults
);
727 static uint8_t getPidProfileIndexToUse()
729 return pidProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentPidProfileIndex() : pidProfileIndexToUse
;
732 static uint8_t getRateProfileIndexToUse()
734 return rateProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentControlRateProfileIndex() : rateProfileIndexToUse
;
738 static uint16_t getValueOffset(const clivalue_t
*value
)
740 switch (value
->type
& VALUE_SECTION_MASK
) {
743 return value
->offset
;
745 return value
->offset
+ sizeof(pidProfile_t
) * getPidProfileIndexToUse();
746 case PROFILE_RATE_VALUE
:
747 return value
->offset
+ sizeof(controlRateConfig_t
) * getRateProfileIndexToUse();
752 STATIC_UNIT_TESTED
void *cliGetValuePointer(const clivalue_t
*value
)
754 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
755 if (isWritingConfigToCopy()) {
756 return CONST_CAST(void *, rec
->copy
+ getValueOffset(value
));
758 return CONST_CAST(void *, rec
->address
+ getValueOffset(value
));
762 static const char *dumpPgValue(const clivalue_t
*value
, dumpFlags_t dumpMask
, const char *headingStr
)
764 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
767 cliPrintLinef("VALUE %s ERROR", value
->name
);
768 return headingStr
; // if it's not found, the pgn shouldn't be in the value table!
772 const char *format
= "set %s = ";
773 const char *defaultFormat
= "#set %s = ";
774 const int valueOffset
= getValueOffset(value
);
775 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
777 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
778 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
779 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
780 cliPrintf(defaultFormat
, value
->name
);
781 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
784 cliPrintf(format
, value
->name
);
785 printValuePointer(value
, pg
->copy
+ valueOffset
, false);
791 static void dumpAllValues(uint16_t valueSection
, dumpFlags_t dumpMask
, const char *headingStr
)
793 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
795 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
796 const clivalue_t
*value
= &valueTable
[i
];
798 if ((value
->type
& VALUE_SECTION_MASK
) == valueSection
|| ((valueSection
== MASTER_VALUE
) && (value
->type
& VALUE_SECTION_MASK
) == HARDWARE_VALUE
)) {
799 headingStr
= dumpPgValue(value
, dumpMask
, headingStr
);
804 static void cliPrintVar(const clivalue_t
*var
, bool full
)
806 const void *ptr
= cliGetValuePointer(var
);
808 printValuePointer(var
, ptr
, full
);
811 static void cliPrintVarRange(const clivalue_t
*var
)
813 switch (var
->type
& VALUE_MODE_MASK
) {
814 case (MODE_DIRECT
): {
815 switch (var
->type
& VALUE_TYPE_MASK
) {
817 cliPrintLinef("Allowed range: 0 - %u", var
->config
.u32Max
);
822 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmaxUnsigned
.min
, var
->config
.minmaxUnsigned
.max
);
826 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
832 case (MODE_LOOKUP
): {
833 const lookupTableEntry_t
*tableEntry
= &lookupTables
[var
->config
.lookup
.tableIndex
];
834 cliPrint("Allowed values: ");
835 bool firstEntry
= true;
836 for (unsigned i
= 0; i
< tableEntry
->valueCount
; i
++) {
837 if (tableEntry
->values
[i
]) {
841 cliPrintf("%s", tableEntry
->values
[i
]);
849 cliPrintLinef("Array length: %d", var
->config
.array
.length
);
852 case (MODE_STRING
): {
853 cliPrintLinef("String length: %d - %d", var
->config
.string
.minlength
, var
->config
.string
.maxlength
);
856 case (MODE_BITSET
): {
857 cliPrintLinef("Allowed values: OFF, ON");
863 static void cliSetVar(const clivalue_t
*var
, const uint32_t value
)
865 void *ptr
= cliGetValuePointer(var
);
869 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
870 switch (var
->type
& VALUE_TYPE_MASK
) {
872 mask
= (1 << var
->config
.bitpos
) & 0xff;
874 workValue
= *(uint8_t *)ptr
| mask
;
876 workValue
= *(uint8_t *)ptr
& ~mask
;
878 *(uint8_t *)ptr
= workValue
;
882 mask
= (1 << var
->config
.bitpos
) & 0xffff;
884 workValue
= *(uint16_t *)ptr
| mask
;
886 workValue
= *(uint16_t *)ptr
& ~mask
;
888 *(uint16_t *)ptr
= workValue
;
892 mask
= 1 << var
->config
.bitpos
;
894 workValue
= *(uint32_t *)ptr
| mask
;
896 workValue
= *(uint32_t *)ptr
& ~mask
;
898 *(uint32_t *)ptr
= workValue
;
902 switch (var
->type
& VALUE_TYPE_MASK
) {
904 *(uint8_t *)ptr
= value
;
908 *(int8_t *)ptr
= value
;
912 *(uint16_t *)ptr
= value
;
916 *(int16_t *)ptr
= value
;
920 *(uint32_t *)ptr
= value
;
926 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
927 static void cliRepeat(char ch
, uint8_t len
)
930 for (int i
= 0; i
< len
; i
++) {
931 bufWriterAppend(cliWriter
, ch
);
938 static void cliPrompt(void)
940 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
941 if (processingCustomDefaults
) {
942 cliPrint("\r\nd: #");
950 static void cliShowParseError(void)
952 cliPrintErrorLinef("PARSE ERROR");
955 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
957 cliPrintErrorLinef("%s NOT BETWEEN %d AND %d", name
, min
, max
);
960 static const char *nextArg(const char *currentArg
)
962 const char *ptr
= strchr(currentArg
, ' ');
963 while (ptr
&& *ptr
== ' ') {
970 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
972 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
976 val
= CHANNEL_VALUE_TO_STEP(val
);
977 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
979 range
->startStep
= val
;
981 range
->endStep
= val
;
983 (*validArgumentCount
)++;
991 // Check if a string's length is zero
992 static bool isEmpty(const char *string
)
994 return (string
== NULL
|| *string
== '\0') ? true : false;
997 static void printRxFailsafe(dumpFlags_t dumpMask
, const rxFailsafeChannelConfig_t
*rxFailsafeChannelConfigs
, const rxFailsafeChannelConfig_t
*defaultRxFailsafeChannelConfigs
, const char *headingStr
)
999 // print out rxConfig failsafe settings
1000 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1001 for (uint32_t channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
1002 const rxFailsafeChannelConfig_t
*channelFailsafeConfig
= &rxFailsafeChannelConfigs
[channel
];
1003 const rxFailsafeChannelConfig_t
*defaultChannelFailsafeConfig
= &defaultRxFailsafeChannelConfigs
[channel
];
1004 const bool equalsDefault
= !memcmp(channelFailsafeConfig
, defaultChannelFailsafeConfig
, sizeof(*channelFailsafeConfig
));
1005 const bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
1006 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1008 const char *format
= "rxfail %u %c %d";
1009 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1011 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
],
1012 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig
->step
)
1014 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1016 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
],
1017 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
1020 const char *format
= "rxfail %u %c";
1021 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1023 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
]
1025 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1027 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
]
1033 static void cliRxFailsafe(char *cmdline
)
1038 if (isEmpty(cmdline
)) {
1039 // print out rxConfig failsafe settings
1040 for (channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
1041 cliRxFailsafe(itoa(channel
, buf
, 10));
1044 const char *ptr
= cmdline
;
1045 channel
= atoi(ptr
++);
1046 if ((channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
)) {
1048 rxFailsafeChannelConfig_t
*channelFailsafeConfig
= rxFailsafeChannelConfigsMutable(channel
);
1050 const rxFailsafeChannelType_e type
= (channel
< NON_AUX_CHANNEL_COUNT
) ? RX_FAILSAFE_TYPE_FLIGHT
: RX_FAILSAFE_TYPE_AUX
;
1051 rxFailsafeChannelMode_e mode
= channelFailsafeConfig
->mode
;
1052 bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
1056 const char *p
= strchr(rxFailsafeModeCharacters
, *(ptr
));
1058 const uint8_t requestedMode
= p
- rxFailsafeModeCharacters
;
1059 mode
= rxFailsafeModesTable
[type
][requestedMode
];
1061 mode
= RX_FAILSAFE_MODE_INVALID
;
1063 if (mode
== RX_FAILSAFE_MODE_INVALID
) {
1064 cliShowParseError();
1068 requireValue
= mode
== RX_FAILSAFE_MODE_SET
;
1072 if (!requireValue
) {
1073 cliShowParseError();
1076 uint16_t value
= atoi(ptr
);
1077 value
= CHANNEL_VALUE_TO_RXFAIL_STEP(value
);
1078 if (value
> MAX_RXFAIL_RANGE_STEP
) {
1079 cliPrintLine("Value out of range");
1083 channelFailsafeConfig
->step
= value
;
1084 } else if (requireValue
) {
1085 cliShowParseError();
1088 channelFailsafeConfig
->mode
= mode
;
1091 char modeCharacter
= rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
];
1093 // double use of cliPrintf below
1094 // 1. acknowledge interpretation on command,
1095 // 2. query current setting on single item,
1098 cliPrintLinef("rxfail %u %c %d",
1101 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
1104 cliPrintLinef("rxfail %u %c",
1110 cliShowArgumentRangeError("CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT
- 1);
1115 static void printAux(dumpFlags_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
, const char *headingStr
)
1117 const char *format
= "aux %u %u %u %u %u %u %u";
1118 // print out aux channel settings
1119 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1120 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
1121 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
1122 bool equalsDefault
= false;
1123 if (defaultModeActivationConditions
) {
1124 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
1125 equalsDefault
= !isModeActivationConditionConfigured(mac
, macDefault
);
1126 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1127 const box_t
*box
= findBoxByBoxId(macDefault
->modeId
);
1128 const box_t
*linkedTo
= findBoxByBoxId(macDefault
->linkedTo
);
1130 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1133 macDefault
->auxChannelIndex
,
1134 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
1135 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
),
1136 macDefault
->modeLogic
,
1137 linkedTo
? linkedTo
->permanentId
: 0
1141 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
1142 const box_t
*linkedTo
= findBoxByBoxId(mac
->linkedTo
);
1144 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1147 mac
->auxChannelIndex
,
1148 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
1149 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
1151 linkedTo
? linkedTo
->permanentId
: 0
1157 static void cliAux(char *cmdline
)
1162 if (isEmpty(cmdline
)) {
1163 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
, NULL
);
1167 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
1168 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
1169 uint8_t validArgumentCount
= 0;
1173 const box_t
*box
= findBoxByPermanentId(val
);
1175 mac
->modeId
= box
->boxId
;
1176 validArgumentCount
++;
1182 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1183 mac
->auxChannelIndex
= val
;
1184 validArgumentCount
++;
1187 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
1191 if (val
== MODELOGIC_OR
|| val
== MODELOGIC_AND
) {
1192 mac
->modeLogic
= val
;
1193 validArgumentCount
++;
1199 const box_t
*box
= findBoxByPermanentId(val
);
1201 mac
->linkedTo
= box
->boxId
;
1202 validArgumentCount
++;
1205 if (validArgumentCount
== 4) { // for backwards compatibility
1206 mac
->modeLogic
= MODELOGIC_OR
;
1208 } else if (validArgumentCount
== 5) { // for backwards compatibility
1210 } else if (validArgumentCount
!= 6) {
1211 memset(mac
, 0, sizeof(modeActivationCondition_t
));
1213 analyzeModeActivationConditions();
1214 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1216 findBoxByBoxId(mac
->modeId
)->permanentId
,
1217 mac
->auxChannelIndex
,
1218 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
1219 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
1221 findBoxByBoxId(mac
->linkedTo
)->permanentId
1224 cliShowArgumentRangeError("INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
1229 static void printSerial(dumpFlags_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
, const char *headingStr
)
1231 const char *format
= "serial %d %d %ld %ld %ld %ld";
1232 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1233 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
1234 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
1237 bool equalsDefault
= false;
1238 if (serialConfigDefault
) {
1239 equalsDefault
= !memcmp(&serialConfig
->portConfigs
[i
], &serialConfigDefault
->portConfigs
[i
], sizeof(serialConfig
->portConfigs
[i
]));
1240 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1241 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1242 serialConfigDefault
->portConfigs
[i
].identifier
,
1243 serialConfigDefault
->portConfigs
[i
].functionMask
,
1244 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
1245 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
1246 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
1247 baudRates
[serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
]
1250 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1251 serialConfig
->portConfigs
[i
].identifier
,
1252 serialConfig
->portConfigs
[i
].functionMask
,
1253 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
1254 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
1255 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
1256 baudRates
[serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
]
1261 static void cliSerial(char *cmdline
)
1263 const char *format
= "serial %d %d %ld %ld %ld %ld";
1264 if (isEmpty(cmdline
)) {
1265 printSerial(DUMP_MASTER
, serialConfig(), NULL
, NULL
);
1268 serialPortConfig_t portConfig
;
1269 memset(&portConfig
, 0 , sizeof(portConfig
));
1272 uint8_t validArgumentCount
= 0;
1274 const char *ptr
= cmdline
;
1276 int val
= atoi(ptr
++);
1277 serialPortConfig_t
*currentConfig
= serialFindPortConfigurationMutable(val
);
1279 if (currentConfig
) {
1280 portConfig
.identifier
= val
;
1281 validArgumentCount
++;
1286 val
= strtoul(ptr
, NULL
, 10);
1287 portConfig
.functionMask
= val
;
1288 validArgumentCount
++;
1291 for (int i
= 0; i
< 4; i
++) {
1299 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
1300 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
1306 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_1000000
) {
1309 portConfig
.msp_baudrateIndex
= baudRateIndex
;
1312 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
1315 portConfig
.gps_baudrateIndex
= baudRateIndex
;
1318 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
1321 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
1324 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_2470000
) {
1327 portConfig
.blackbox_baudrateIndex
= baudRateIndex
;
1331 validArgumentCount
++;
1334 if (validArgumentCount
< 6) {
1335 cliShowParseError();
1339 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
1341 cliDumpPrintLinef(0, false, format
,
1342 portConfig
.identifier
,
1343 portConfig
.functionMask
,
1344 baudRates
[portConfig
.msp_baudrateIndex
],
1345 baudRates
[portConfig
.gps_baudrateIndex
],
1346 baudRates
[portConfig
.telemetry_baudrateIndex
],
1347 baudRates
[portConfig
.blackbox_baudrateIndex
]
1352 #if defined(USE_SERIAL_PASSTHROUGH)
1353 static void cbCtrlLine(void *context
, uint16_t ctrl
)
1356 int contextValue
= (int)(long)context
;
1358 pinioSet(contextValue
- 1, !(ctrl
& CTRL_LINE_STATE_DTR
));
1360 #endif /* USE_PINIO */
1363 if (!(ctrl
& CTRL_LINE_STATE_DTR
)) {
1368 static int cliParseSerialMode(const char *tok
)
1372 if (strcasestr(tok
, "rx")) {
1375 if (strcasestr(tok
, "tx")) {
1382 static void cliSerialPassthrough(char *cmdline
)
1384 if (isEmpty(cmdline
)) {
1385 cliShowParseError();
1389 serialPassthroughPort_t ports
[2] = { {SERIAL_PORT_NONE
, 0, 0, NULL
}, {cliPort
->identifier
, 0, 0, cliPort
} };
1390 bool enableBaudCb
= false;
1391 int port1PinioDtr
= 0;
1392 bool port1ResetOnDtr
= false;
1393 bool escSensorPassthrough
= false;
1395 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
1398 while (tok
!= NULL
) {
1401 if (strcasestr(tok
, "esc_sensor")) {
1402 escSensorPassthrough
= true;
1403 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_ESC_SENSOR
);
1404 ports
[0].id
= portConfig
->identifier
;
1406 ports
[0].id
= atoi(tok
);
1410 ports
[0].baud
= atoi(tok
);
1413 ports
[0].mode
= cliParseSerialMode(tok
);
1416 if (strncasecmp(tok
, "reset", strlen(tok
)) == 0) {
1417 port1ResetOnDtr
= true;
1419 } else if (strncasecmp(tok
, "none", strlen(tok
)) == 0) {
1422 port1PinioDtr
= atoi(tok
);
1423 if (port1PinioDtr
< 0 || port1PinioDtr
> PINIO_COUNT
) {
1424 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr
);
1427 #endif /* USE_PINIO */
1431 ports
[1].id
= atoi(tok
);
1432 ports
[1].port
= NULL
;
1435 ports
[1].baud
= atoi(tok
);
1438 ports
[1].mode
= cliParseSerialMode(tok
);
1442 tok
= strtok_r(NULL
, " ", &saveptr
);
1446 if (ports
[0].id
== ports
[1].id
) {
1447 cliPrintLinef("Port1 and port2 are same");
1451 for (int i
= 0; i
< 2; i
++) {
1452 if (findSerialPortIndexByIdentifier(ports
[i
].id
) == -1) {
1453 cliPrintLinef("Invalid port%d %d", i
+ 1, ports
[i
].id
);
1456 cliPrintLinef("Port%d: %d ", i
+ 1, ports
[i
].id
);
1460 if (ports
[0].baud
== 0 && ports
[1].id
== SERIAL_PORT_USB_VCP
) {
1461 enableBaudCb
= true;
1464 for (int i
= 0; i
< 2; i
++) {
1465 serialPort_t
**port
= &(ports
[i
].port
);
1466 if (*port
!= NULL
) {
1470 int portIndex
= i
+ 1;
1471 serialPortUsage_t
*portUsage
= findSerialPortUsageByIdentifier(ports
[i
].id
);
1472 if (!portUsage
|| portUsage
->serialPort
== NULL
) {
1473 bool isUseDefaultBaud
= false;
1474 if (ports
[i
].baud
== 0) {
1476 ports
[i
].baud
= 57600;
1477 isUseDefaultBaud
= true;
1480 if (!ports
[i
].mode
) {
1481 ports
[i
].mode
= MODE_RXTX
;
1484 *port
= openSerialPort(ports
[i
].id
, FUNCTION_NONE
, NULL
, NULL
,
1485 ports
[i
].baud
, ports
[i
].mode
,
1486 SERIAL_NOT_INVERTED
);
1488 cliPrintLinef("Port%d could not be opened.", portIndex
);
1492 if (isUseDefaultBaud
) {
1493 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex
, ports
[i
].baud
);
1495 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex
, ports
[i
].baud
);
1498 *port
= portUsage
->serialPort
;
1499 // If the user supplied a mode, override the port's mode, otherwise
1500 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1501 // Set the baud rate if specified
1502 if (ports
[i
].baud
) {
1503 cliPrintf("Port%d is already open, setting baud = %d.\n\r", portIndex
, ports
[i
].baud
);
1504 serialSetBaudRate(*port
, ports
[i
].baud
);
1506 cliPrintf("Port%d is already open, baud = %d.\n\r", portIndex
, (*port
)->baudRate
);
1509 if (ports
[i
].mode
&& (*port
)->mode
!= ports
[i
].mode
) {
1510 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1511 portIndex
, (*port
)->mode
, ports
[i
].mode
);
1512 serialSetMode(*port
, ports
[i
].mode
);
1515 // If this port has a rx callback associated we need to remove it now.
1516 // Otherwise no data will be pushed in the serial port buffer!
1517 if ((*port
)->rxCallback
) {
1518 (*port
)->rxCallback
= NULL
;
1523 // If no baud rate is specified allow to be set via USB
1525 cliPrintLine("Port1 baud rate change over USB enabled.");
1526 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1527 // baud rate over USB without setting it using the serialpassthrough command
1528 serialSetBaudRateCb(ports
[0].port
, serialSetBaudRate
, ports
[1].port
);
1531 char *resetMessage
= "";
1532 if (port1ResetOnDtr
&& ports
[1].id
== SERIAL_PORT_USB_VCP
) {
1533 resetMessage
= "or drop DTR ";
1536 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage
);
1538 if ((ports
[1].id
== SERIAL_PORT_USB_VCP
) && (port1ResetOnDtr
1541 #endif /* USE_PINIO */
1543 // Register control line state callback
1544 serialSetCtrlLineStateCb(ports
[0].port
, cbCtrlLine
, (void *)(intptr_t)(port1PinioDtr
));
1547 // XXX Review ESC pass through under refactored motor handling
1548 #ifdef USE_PWM_OUTPUT
1549 if (escSensorPassthrough
) {
1550 // pwmDisableMotors();
1553 unsigned motorsCount
= getMotorCount();
1554 for (unsigned i
= 0; i
< motorsCount
; i
++) {
1555 const ioTag_t tag
= motorConfig()->dev
.ioTags
[i
];
1557 const timerHardware_t
*timerHardware
= timerGetByTag(tag
);
1558 if (timerHardware
) {
1559 IO_t io
= IOGetByTag(tag
);
1560 IOInit(io
, OWNER_MOTOR
, 0);
1561 IOConfigGPIO(io
, IOCFG_OUT_PP
);
1562 if (timerHardware
->output
& TIMER_OUTPUT_INVERTED
) {
1573 serialPassthrough(ports
[0].port
, ports
[1].port
, NULL
, NULL
);
1577 static void printAdjustmentRange(dumpFlags_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
, const char *headingStr
)
1579 const char *format
= "adjrange %u 0 %u %u %u %u %u %u %u";
1580 // print out adjustment ranges channel settings
1581 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1582 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
1583 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
1584 bool equalsDefault
= false;
1585 if (defaultAdjustmentRanges
) {
1586 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
1587 equalsDefault
= !memcmp(ar
, arDefault
, sizeof(*ar
));
1588 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1589 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1591 arDefault
->auxChannelIndex
,
1592 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1593 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1594 arDefault
->adjustmentConfig
,
1595 arDefault
->auxSwitchChannelIndex
,
1596 arDefault
->adjustmentCenter
,
1597 arDefault
->adjustmentScale
1600 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1602 ar
->auxChannelIndex
,
1603 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1604 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1605 ar
->adjustmentConfig
,
1606 ar
->auxSwitchChannelIndex
,
1607 ar
->adjustmentCenter
,
1613 static void cliAdjustmentRange(char *cmdline
)
1615 const char *format
= "adjrange %u 0 %u %u %u %u %u %u %u";
1619 if (isEmpty(cmdline
)) {
1620 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
, NULL
);
1624 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1625 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1626 uint8_t validArgumentCount
= 0;
1632 // Keeping the parameter to retain backwards compatibility for the command format.
1633 validArgumentCount
++;
1638 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1639 ar
->auxChannelIndex
= val
;
1640 validArgumentCount
++;
1644 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1649 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1650 ar
->adjustmentConfig
= val
;
1651 validArgumentCount
++;
1657 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1658 ar
->auxSwitchChannelIndex
= val
;
1659 validArgumentCount
++;
1663 if (validArgumentCount
!= 6) {
1664 memset(ar
, 0, sizeof(adjustmentRange_t
));
1665 cliShowParseError();
1669 // Optional arguments
1670 ar
->adjustmentCenter
= 0;
1671 ar
->adjustmentScale
= 0;
1676 ar
->adjustmentCenter
= val
;
1677 validArgumentCount
++;
1682 ar
->adjustmentScale
= val
;
1683 validArgumentCount
++;
1686 activeAdjustmentRangeReset();
1688 cliDumpPrintLinef(0, false, format
,
1690 ar
->auxChannelIndex
,
1691 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1692 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1693 ar
->adjustmentConfig
,
1694 ar
->auxSwitchChannelIndex
,
1695 ar
->adjustmentCenter
,
1700 cliShowArgumentRangeError("INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1705 #ifndef USE_QUAD_MIXER_ONLY
1706 static void printMotorMix(dumpFlags_t dumpMask
, const motorMixer_t
*customMotorMixer
, const motorMixer_t
*defaultCustomMotorMixer
, const char *headingStr
)
1708 const char *format
= "mmix %d %s %s %s %s";
1709 char buf0
[FTOA_BUFFER_LENGTH
];
1710 char buf1
[FTOA_BUFFER_LENGTH
];
1711 char buf2
[FTOA_BUFFER_LENGTH
];
1712 char buf3
[FTOA_BUFFER_LENGTH
];
1713 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1714 if (customMotorMixer
[i
].throttle
== 0.0f
)
1716 const float thr
= customMotorMixer
[i
].throttle
;
1717 const float roll
= customMotorMixer
[i
].roll
;
1718 const float pitch
= customMotorMixer
[i
].pitch
;
1719 const float yaw
= customMotorMixer
[i
].yaw
;
1720 bool equalsDefault
= false;
1721 if (defaultCustomMotorMixer
) {
1722 const float thrDefault
= defaultCustomMotorMixer
[i
].throttle
;
1723 const float rollDefault
= defaultCustomMotorMixer
[i
].roll
;
1724 const float pitchDefault
= defaultCustomMotorMixer
[i
].pitch
;
1725 const float yawDefault
= defaultCustomMotorMixer
[i
].yaw
;
1726 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1728 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1729 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1731 ftoa(thrDefault
, buf0
),
1732 ftoa(rollDefault
, buf1
),
1733 ftoa(pitchDefault
, buf2
),
1734 ftoa(yawDefault
, buf3
));
1736 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1744 #endif // USE_QUAD_MIXER_ONLY
1746 static void cliMotorMix(char *cmdline
)
1748 #ifdef USE_QUAD_MIXER_ONLY
1755 if (isEmpty(cmdline
)) {
1756 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
, NULL
);
1757 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1758 // erase custom mixer
1759 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1760 customMotorMixerMutable(i
)->throttle
= 0.0f
;
1762 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1763 ptr
= nextArg(cmdline
);
1766 for (uint32_t i
= 0; ; i
++) {
1767 if (mixerNames
[i
] == NULL
) {
1768 cliPrintErrorLinef("INVALID NAME");
1771 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1772 mixerLoadMix(i
, customMotorMixerMutable(0));
1773 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1781 uint32_t i
= atoi(ptr
); // get motor number
1782 if (i
< MAX_SUPPORTED_MOTORS
) {
1785 customMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1790 customMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1795 customMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1800 customMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1804 cliShowParseError();
1806 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
, NULL
);
1809 cliShowArgumentRangeError("INDEX", 0, MAX_SUPPORTED_MOTORS
- 1);
1815 static void printRxRange(dumpFlags_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
, const char *headingStr
)
1817 const char *format
= "rxrange %u %u %u";
1818 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1819 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1820 bool equalsDefault
= false;
1821 if (defaultChannelRangeConfigs
) {
1822 equalsDefault
= !memcmp(&channelRangeConfigs
[i
], &defaultChannelRangeConfigs
[i
], sizeof(channelRangeConfigs
[i
]));
1823 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1824 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1826 defaultChannelRangeConfigs
[i
].min
,
1827 defaultChannelRangeConfigs
[i
].max
1830 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1832 channelRangeConfigs
[i
].min
,
1833 channelRangeConfigs
[i
].max
1838 static void cliRxRange(char *cmdline
)
1840 const char *format
= "rxrange %u %u %u";
1841 int i
, validArgumentCount
= 0;
1844 if (isEmpty(cmdline
)) {
1845 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
, NULL
);
1846 } else if (strcasecmp(cmdline
, "reset") == 0) {
1847 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1851 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1852 int rangeMin
= PWM_PULSE_MIN
, rangeMax
= PWM_PULSE_MAX
;
1856 rangeMin
= atoi(ptr
);
1857 validArgumentCount
++;
1862 rangeMax
= atoi(ptr
);
1863 validArgumentCount
++;
1866 if (validArgumentCount
!= 2) {
1867 cliShowParseError();
1868 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1869 cliShowParseError();
1871 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1872 channelRangeConfig
->min
= rangeMin
;
1873 channelRangeConfig
->max
= rangeMax
;
1874 cliDumpPrintLinef(0, false, format
,
1876 channelRangeConfig
->min
,
1877 channelRangeConfig
->max
1882 cliShowArgumentRangeError("CHANNEL", 0, NON_AUX_CHANNEL_COUNT
- 1);
1887 #ifdef USE_LED_STRIP_STATUS_MODE
1888 static void printLed(dumpFlags_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
, const char *headingStr
)
1890 const char *format
= "led %u %s";
1891 char ledConfigBuffer
[20];
1892 char ledConfigDefaultBuffer
[20];
1893 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1894 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1895 ledConfig_t ledConfig
= ledConfigs
[i
];
1896 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1897 bool equalsDefault
= false;
1898 if (defaultLedConfigs
) {
1899 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1900 equalsDefault
= ledConfig
== ledConfigDefault
;
1901 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1902 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1903 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1905 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1909 static void cliLed(char *cmdline
)
1911 const char *format
= "led %u %s";
1912 char ledConfigBuffer
[20];
1916 if (isEmpty(cmdline
)) {
1917 printLed(DUMP_MASTER
, ledStripStatusModeConfig()->ledConfigs
, NULL
, NULL
);
1921 if (i
>= 0 && i
< LED_MAX_STRIP_LENGTH
) {
1922 ptr
= nextArg(cmdline
);
1923 if (parseLedStripConfig(i
, ptr
)) {
1924 generateLedConfig((ledConfig_t
*)&ledStripStatusModeConfig()->ledConfigs
[i
], ledConfigBuffer
, sizeof(ledConfigBuffer
));
1925 cliDumpPrintLinef(0, false, format
, i
, ledConfigBuffer
);
1927 cliShowParseError();
1930 cliShowArgumentRangeError("INDEX", 0, LED_MAX_STRIP_LENGTH
- 1);
1935 static void printColor(dumpFlags_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
, const char *headingStr
)
1937 const char *format
= "color %u %d,%u,%u";
1938 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1939 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1940 const hsvColor_t
*color
= &colors
[i
];
1941 bool equalsDefault
= false;
1942 if (defaultColors
) {
1943 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1944 equalsDefault
= !memcmp(color
, colorDefault
, sizeof(*color
));
1945 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1946 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1948 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1952 static void cliColor(char *cmdline
)
1954 const char *format
= "color %u %d,%u,%u";
1955 if (isEmpty(cmdline
)) {
1956 printColor(DUMP_MASTER
, ledStripStatusModeConfig()->colors
, NULL
, NULL
);
1958 const char *ptr
= cmdline
;
1959 const int i
= atoi(ptr
);
1960 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1961 ptr
= nextArg(cmdline
);
1962 if (parseColor(i
, ptr
)) {
1963 const hsvColor_t
*color
= &ledStripStatusModeConfig()->colors
[i
];
1964 cliDumpPrintLinef(0, false, format
, i
, color
->h
, color
->s
, color
->v
);
1966 cliShowParseError();
1969 cliShowArgumentRangeError("INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1974 static void printModeColor(dumpFlags_t dumpMask
, const ledStripStatusModeConfig_t
*ledStripStatusModeConfig
, const ledStripStatusModeConfig_t
*defaultLedStripConfig
, const char *headingStr
)
1976 const char *format
= "mode_color %u %u %u";
1977 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1978 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1979 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1980 int colorIndex
= ledStripStatusModeConfig
->modeColors
[i
].color
[j
];
1981 bool equalsDefault
= false;
1982 if (defaultLedStripConfig
) {
1983 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1984 equalsDefault
= colorIndex
== colorIndexDefault
;
1985 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1986 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1988 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1992 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1993 const int colorIndex
= ledStripStatusModeConfig
->specialColors
.color
[j
];
1994 bool equalsDefault
= false;
1995 if (defaultLedStripConfig
) {
1996 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1997 equalsDefault
= colorIndex
== colorIndexDefault
;
1998 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1999 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
2001 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
2004 const int ledStripAuxChannel
= ledStripStatusModeConfig
->ledstrip_aux_channel
;
2005 bool equalsDefault
= false;
2006 if (defaultLedStripConfig
) {
2007 const int ledStripAuxChannelDefault
= defaultLedStripConfig
->ledstrip_aux_channel
;
2008 equalsDefault
= ledStripAuxChannel
== ledStripAuxChannelDefault
;
2009 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2010 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannelDefault
);
2012 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannel
);
2015 static void cliModeColor(char *cmdline
)
2017 if (isEmpty(cmdline
)) {
2018 printModeColor(DUMP_MASTER
, ledStripStatusModeConfig(), NULL
, NULL
);
2020 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
2021 int args
[ARGS_COUNT
];
2024 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
2025 while (ptr
&& argNo
< ARGS_COUNT
) {
2026 args
[argNo
++] = atoi(ptr
);
2027 ptr
= strtok_r(NULL
, " ", &saveptr
);
2030 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
2031 cliShowParseError();
2035 int modeIdx
= args
[MODE
];
2036 int funIdx
= args
[FUNCTION
];
2037 int color
= args
[COLOR
];
2038 if (!setModeColor(modeIdx
, funIdx
, color
)) {
2039 cliShowParseError();
2042 // values are validated
2043 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
2049 static void printServo(dumpFlags_t dumpMask
, const servoParam_t
*servoParams
, const servoParam_t
*defaultServoParams
, const char *headingStr
)
2051 // print out servo settings
2052 const char *format
= "servo %u %d %d %d %d %d";
2053 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2054 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2055 const servoParam_t
*servoConf
= &servoParams
[i
];
2056 bool equalsDefault
= false;
2057 if (defaultServoParams
) {
2058 const servoParam_t
*defaultServoConf
= &defaultServoParams
[i
];
2059 equalsDefault
= !memcmp(servoConf
, defaultServoConf
, sizeof(*servoConf
));
2060 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2061 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2063 defaultServoConf
->min
,
2064 defaultServoConf
->max
,
2065 defaultServoConf
->middle
,
2066 defaultServoConf
->rate
,
2067 defaultServoConf
->forwardFromChannel
2070 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2076 servoConf
->forwardFromChannel
2079 // print servo directions
2080 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2081 const char *format
= "smix reverse %d %d r";
2082 const servoParam_t
*servoConf
= &servoParams
[i
];
2083 const servoParam_t
*servoConfDefault
= &defaultServoParams
[i
];
2084 if (defaultServoParams
) {
2085 bool equalsDefault
= servoConf
->reversedSources
== servoConfDefault
->reversedSources
;
2086 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
2087 equalsDefault
= ~(servoConf
->reversedSources
^ servoConfDefault
->reversedSources
) & (1 << channel
);
2088 if (servoConfDefault
->reversedSources
& (1 << channel
)) {
2089 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
2091 if (servoConf
->reversedSources
& (1 << channel
)) {
2092 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
2096 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
2097 if (servoConf
->reversedSources
& (1 << channel
)) {
2098 cliDumpPrintLinef(dumpMask
, true, format
, i
, channel
);
2105 static void cliServo(char *cmdline
)
2107 const char *format
= "servo %u %d %d %d %d %d";
2108 enum { SERVO_ARGUMENT_COUNT
= 6 };
2109 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
2111 servoParam_t
*servo
;
2116 if (isEmpty(cmdline
)) {
2117 printServo(DUMP_MASTER
, servoParams(0), NULL
, NULL
);
2119 int validArgumentCount
= 0;
2123 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2125 // If command line doesn't fit the format, don't modify the config
2127 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
2128 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
2129 cliShowParseError();
2133 arguments
[validArgumentCount
++] = atoi(ptr
);
2137 } while (*ptr
>= '0' && *ptr
<= '9');
2138 } else if (*ptr
== ' ') {
2141 cliShowParseError();
2146 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
, FORWARD
};
2148 i
= arguments
[INDEX
];
2150 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2151 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
2152 cliShowParseError();
2156 servo
= servoParamsMutable(i
);
2159 arguments
[MIN
] < PWM_PULSE_MIN
|| arguments
[MIN
] > PWM_PULSE_MAX
||
2160 arguments
[MAX
] < PWM_PULSE_MIN
|| arguments
[MAX
] > PWM_PULSE_MAX
||
2161 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
2162 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
2163 arguments
[RATE
] < -100 || arguments
[RATE
] > 100 ||
2164 arguments
[FORWARD
] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2166 cliShowParseError();
2170 servo
->min
= arguments
[MIN
];
2171 servo
->max
= arguments
[MAX
];
2172 servo
->middle
= arguments
[MIDDLE
];
2173 servo
->rate
= arguments
[RATE
];
2174 servo
->forwardFromChannel
= arguments
[FORWARD
];
2176 cliDumpPrintLinef(0, false, format
,
2182 servo
->forwardFromChannel
2190 static void printServoMix(dumpFlags_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
, const char *headingStr
)
2192 const char *format
= "smix %d %d %d %d %d %d %d %d";
2193 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2194 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
2195 const servoMixer_t customServoMixer
= customServoMixers
[i
];
2196 if (customServoMixer
.rate
== 0) {
2200 bool equalsDefault
= false;
2201 if (defaultCustomServoMixers
) {
2202 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
2203 equalsDefault
= !memcmp(&customServoMixer
, &customServoMixerDefault
, sizeof(customServoMixer
));
2205 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2206 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2208 customServoMixerDefault
.targetChannel
,
2209 customServoMixerDefault
.inputSource
,
2210 customServoMixerDefault
.rate
,
2211 customServoMixerDefault
.speed
,
2212 customServoMixerDefault
.min
,
2213 customServoMixerDefault
.max
,
2214 customServoMixerDefault
.box
2217 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2219 customServoMixer
.targetChannel
,
2220 customServoMixer
.inputSource
,
2221 customServoMixer
.rate
,
2222 customServoMixer
.speed
,
2223 customServoMixer
.min
,
2224 customServoMixer
.max
,
2225 customServoMixer
.box
2230 static void cliServoMix(char *cmdline
)
2232 int args
[8], check
= 0;
2233 int len
= strlen(cmdline
);
2236 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
, NULL
);
2237 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
2238 // erase custom mixer
2239 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2240 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2241 servoParamsMutable(i
)->reversedSources
= 0;
2243 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
2244 const char *ptr
= nextArg(cmdline
);
2247 for (uint32_t i
= 0; ; i
++) {
2248 if (mixerNames
[i
] == NULL
) {
2249 cliPrintErrorLinef("INVALID NAME");
2252 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
2253 servoMixerLoadMix(i
);
2254 cliPrintLinef("Loaded %s", mixerNames
[i
]);
2260 } else if (strncasecmp(cmdline
, "reverse", 7) == 0) {
2261 enum {SERVO
= 0, INPUT
, REVERSE
, ARGS_COUNT
};
2262 char *ptr
= strchr(cmdline
, ' ');
2266 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
2267 cliPrintf("\ti%d", inputSource
);
2270 for (uint32_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
2271 cliPrintf("%d", servoIndex
);
2272 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++) {
2273 cliPrintf("\t%s ", (servoParams(servoIndex
)->reversedSources
& (1 << inputSource
)) ? "r" : "n");
2281 ptr
= strtok_r(ptr
, " ", &saveptr
);
2282 while (ptr
!= NULL
&& check
< ARGS_COUNT
- 1) {
2283 args
[check
++] = atoi(ptr
);
2284 ptr
= strtok_r(NULL
, " ", &saveptr
);
2287 if (ptr
== NULL
|| check
!= ARGS_COUNT
- 1) {
2288 cliShowParseError();
2292 if (args
[SERVO
] >= 0 && args
[SERVO
] < MAX_SUPPORTED_SERVOS
2293 && args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
2294 && (*ptr
== 'r' || *ptr
== 'n')) {
2296 servoParamsMutable(args
[SERVO
])->reversedSources
|= 1 << args
[INPUT
];
2298 servoParamsMutable(args
[SERVO
])->reversedSources
&= ~(1 << args
[INPUT
]);
2301 cliShowParseError();
2305 cliServoMix("reverse");
2307 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, MIN
, MAX
, BOX
, ARGS_COUNT
};
2309 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2310 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2311 args
[check
++] = atoi(ptr
);
2312 ptr
= strtok_r(NULL
, " ", &saveptr
);
2315 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2316 cliShowParseError();
2320 int32_t i
= args
[RULE
];
2321 if (i
>= 0 && i
< MAX_SERVO_RULES
&&
2322 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
2323 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
2324 args
[RATE
] >= -100 && args
[RATE
] <= 100 &&
2325 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
2326 args
[MIN
] >= 0 && args
[MIN
] <= 100 &&
2327 args
[MAX
] >= 0 && args
[MAX
] <= 100 && args
[MIN
] < args
[MAX
] &&
2328 args
[BOX
] >= 0 && args
[BOX
] <= MAX_SERVO_BOXES
) {
2329 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
2330 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
2331 customServoMixersMutable(i
)->rate
= args
[RATE
];
2332 customServoMixersMutable(i
)->speed
= args
[SPEED
];
2333 customServoMixersMutable(i
)->min
= args
[MIN
];
2334 customServoMixersMutable(i
)->max
= args
[MAX
];
2335 customServoMixersMutable(i
)->box
= args
[BOX
];
2338 cliShowParseError();
2346 static void cliWriteBytes(const uint8_t *buffer
, int count
)
2355 static void cliSdInfo(char *cmdline
)
2359 cliPrint("SD card: ");
2361 if (sdcardConfig()->mode
== SDCARD_MODE_NONE
) {
2362 cliPrintLine("Not configured");
2367 if (!sdcard_isInserted()) {
2368 cliPrintLine("None inserted");
2372 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2373 cliPrintLine("Startup failed");
2377 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
2379 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2380 metadata
->manufacturerID
,
2381 metadata
->numBlocks
/ 2, /* One block is half a kB */
2382 metadata
->productionMonth
,
2383 metadata
->productionYear
,
2384 metadata
->productRevisionMajor
,
2385 metadata
->productRevisionMinor
2388 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
2390 cliPrint("'\r\n" "Filesystem: ");
2392 switch (afatfs_getFilesystemState()) {
2393 case AFATFS_FILESYSTEM_STATE_READY
:
2396 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
2397 cliPrint("Initializing");
2399 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
2400 case AFATFS_FILESYSTEM_STATE_FATAL
:
2403 switch (afatfs_getLastError()) {
2404 case AFATFS_ERROR_BAD_MBR
:
2405 cliPrint(" - no FAT MBR partitions");
2407 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
2408 cliPrint(" - bad FAT header");
2410 case AFATFS_ERROR_GENERIC
:
2411 case AFATFS_ERROR_NONE
:
2412 ; // Nothing more detailed to print
2422 #ifdef USE_FLASH_CHIP
2424 static void cliFlashInfo(char *cmdline
)
2426 const flashGeometry_t
*layout
= flashGetGeometry();
2430 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2431 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
);
2433 for (uint8_t index
= 0; index
< FLASH_MAX_PARTITIONS
; index
++) {
2434 const flashPartition_t
*partition
;
2436 cliPrintLine("Paritions:");
2438 partition
= flashPartitionFindByIndex(index
);
2442 cliPrintLinef(" %d: %s %u %u", index
, flashPartitionGetTypeName(partition
->type
), partition
->startSector
, partition
->endSector
);
2445 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
2447 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2448 FLASH_PARTITION_SECTOR_COUNT(flashPartition
) * layout
->sectorSize
,
2455 static void cliFlashErase(char *cmdline
)
2459 if (!flashfsIsSupported()) {
2465 cliPrintLine("Erasing, please wait ... ");
2467 cliPrintLine("Erasing,");
2471 flashfsEraseCompletely();
2473 while (!flashfsIsReady()) {
2485 beeper(BEEPER_BLACKBOX_ERASE
);
2487 cliPrintLine("Done.");
2490 #ifdef USE_FLASH_TOOLS
2492 static void cliFlashVerify(char *cmdline
)
2496 cliPrintLine("Verifying");
2497 if (flashfsVerifyEntireFlash()) {
2498 cliPrintLine("Success");
2500 cliPrintLine("Failed");
2504 static void cliFlashWrite(char *cmdline
)
2506 const uint32_t address
= atoi(cmdline
);
2507 const char *text
= strchr(cmdline
, ' ');
2510 cliShowParseError();
2512 flashfsSeekAbs(address
);
2513 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2516 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2520 static void cliFlashRead(char *cmdline
)
2522 uint32_t address
= atoi(cmdline
);
2524 const char *nextArg
= strchr(cmdline
, ' ');
2527 cliShowParseError();
2529 uint32_t length
= atoi(nextArg
);
2531 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2534 while (length
> 0) {
2535 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2537 for (int i
= 0; i
< bytesRead
; i
++) {
2538 cliWrite(buffer
[i
]);
2541 length
-= bytesRead
;
2542 address
+= bytesRead
;
2544 if (bytesRead
== 0) {
2545 //Assume we reached the end of the volume or something fatal happened
2556 #ifdef USE_VTX_CONTROL
2557 static void printVtx(dumpFlags_t dumpMask
, const vtxConfig_t
*vtxConfig
, const vtxConfig_t
*vtxConfigDefault
, const char *headingStr
)
2559 // print out vtx channel settings
2560 const char *format
= "vtx %u %u %u %u %u %u %u";
2561 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2562 bool equalsDefault
= false;
2563 for (uint32_t i
= 0; i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
; i
++) {
2564 const vtxChannelActivationCondition_t
*cac
= &vtxConfig
->vtxChannelActivationConditions
[i
];
2565 if (vtxConfigDefault
) {
2566 const vtxChannelActivationCondition_t
*cacDefault
= &vtxConfigDefault
->vtxChannelActivationConditions
[i
];
2567 equalsDefault
= !memcmp(cac
, cacDefault
, sizeof(*cac
));
2568 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2569 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2571 cacDefault
->auxChannelIndex
,
2573 cacDefault
->channel
,
2575 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.startStep
),
2576 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.endStep
)
2579 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2581 cac
->auxChannelIndex
,
2585 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2586 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2591 static void cliVtx(char *cmdline
)
2593 const char *format
= "vtx %u %u %u %u %u %u %u";
2597 if (isEmpty(cmdline
)) {
2598 printVtx(DUMP_MASTER
, vtxConfig(), NULL
, NULL
);
2600 #ifdef USE_VTX_TABLE
2601 const uint8_t maxBandIndex
= vtxTableConfig()->bands
;
2602 const uint8_t maxChannelIndex
= vtxTableConfig()->channels
;
2603 const uint8_t maxPowerIndex
= vtxTableConfig()->powerLevels
;
2605 const uint8_t maxBandIndex
= VTX_TABLE_MAX_BANDS
;
2606 const uint8_t maxChannelIndex
= VTX_TABLE_MAX_CHANNELS
;
2607 const uint8_t maxPowerIndex
= VTX_TABLE_MAX_POWER_LEVELS
;
2611 if (i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
) {
2612 vtxChannelActivationCondition_t
*cac
= &vtxConfigMutable()->vtxChannelActivationConditions
[i
];
2613 uint8_t validArgumentCount
= 0;
2617 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
2618 cac
->auxChannelIndex
= val
;
2619 validArgumentCount
++;
2625 if (val
>= 0 && val
<= maxBandIndex
) {
2627 validArgumentCount
++;
2633 if (val
>= 0 && val
<= maxChannelIndex
) {
2635 validArgumentCount
++;
2641 if (val
>= 0 && val
<= maxPowerIndex
) {
2643 validArgumentCount
++;
2646 ptr
= processChannelRangeArgs(ptr
, &cac
->range
, &validArgumentCount
);
2648 if (validArgumentCount
!= 6) {
2649 memset(cac
, 0, sizeof(vtxChannelActivationCondition_t
));
2650 cliShowParseError();
2652 cliDumpPrintLinef(0, false, format
,
2654 cac
->auxChannelIndex
,
2658 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2659 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2663 cliShowArgumentRangeError("INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
- 1);
2668 #endif // VTX_CONTROL
2670 #ifdef USE_VTX_TABLE
2672 static char *formatVtxTableBandFrequency(const bool isFactory
, const uint16_t *frequency
, int channels
)
2674 static char freqbuf
[5 * VTX_TABLE_MAX_CHANNELS
+ 8 + 1];
2675 char freqtmp
[5 + 1];
2677 strcat(freqbuf
, isFactory
? " FACTORY" : " CUSTOM ");
2678 for (int channel
= 0; channel
< channels
; channel
++) {
2679 tfp_sprintf(freqtmp
, " %4d", frequency
[channel
]);
2680 strcat(freqbuf
, freqtmp
);
2685 static const char *printVtxTableBand(dumpFlags_t dumpMask
, int band
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2687 char *fmt
= "vtxtable band %d %s %c%s";
2688 bool equalsDefault
= false;
2690 if (defaultConfig
) {
2691 equalsDefault
= true;
2692 if (strcasecmp(currentConfig
->bandNames
[band
], defaultConfig
->bandNames
[band
])) {
2693 equalsDefault
= false;
2695 if (currentConfig
->bandLetters
[band
] != defaultConfig
->bandLetters
[band
]) {
2696 equalsDefault
= false;
2698 for (int channel
= 0; channel
< VTX_TABLE_MAX_CHANNELS
; channel
++) {
2699 if (currentConfig
->frequency
[band
][channel
] != defaultConfig
->frequency
[band
][channel
]) {
2700 equalsDefault
= false;
2703 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2704 char *freqbuf
= formatVtxTableBandFrequency(defaultConfig
->isFactoryBand
[band
], defaultConfig
->frequency
[band
], defaultConfig
->channels
);
2705 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, band
+ 1, defaultConfig
->bandNames
[band
], defaultConfig
->bandLetters
[band
], freqbuf
);
2708 char *freqbuf
= formatVtxTableBandFrequency(currentConfig
->isFactoryBand
[band
], currentConfig
->frequency
[band
], currentConfig
->channels
);
2709 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, band
+ 1, currentConfig
->bandNames
[band
], currentConfig
->bandLetters
[band
], freqbuf
);
2713 static char *formatVtxTablePowerValues(const uint16_t *levels
, int count
)
2715 // (max 4 digit + 1 space) per level
2716 static char pwrbuf
[5 * VTX_TABLE_MAX_POWER_LEVELS
+ 1];
2719 for (int pwrindex
= 0; pwrindex
< count
; pwrindex
++) {
2720 tfp_sprintf(pwrtmp
, " %d", levels
[pwrindex
]);
2721 strcat(pwrbuf
, pwrtmp
);
2726 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2728 char *fmt
= "vtxtable powervalues%s";
2729 bool equalsDefault
= false;
2730 if (defaultConfig
) {
2731 equalsDefault
= true;
2732 for (int pwrindex
= 0; pwrindex
< VTX_TABLE_MAX_POWER_LEVELS
; pwrindex
++) {
2733 if (defaultConfig
->powerValues
[pwrindex
] != currentConfig
->powerValues
[pwrindex
]) {
2734 equalsDefault
= false;
2737 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2738 char *pwrbuf
= formatVtxTablePowerValues(defaultConfig
->powerValues
, VTX_TABLE_MAX_POWER_LEVELS
);
2739 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2742 char *pwrbuf
= formatVtxTablePowerValues(currentConfig
->powerValues
, currentConfig
->powerLevels
);
2743 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2747 static char *formatVtxTablePowerLabels(const char labels
[VTX_TABLE_MAX_POWER_LEVELS
][VTX_TABLE_POWER_LABEL_LENGTH
+ 1], int count
)
2749 static char pwrbuf
[(VTX_TABLE_POWER_LABEL_LENGTH
+ 1) * VTX_TABLE_MAX_POWER_LEVELS
+ 1];
2750 char pwrtmp
[(VTX_TABLE_POWER_LABEL_LENGTH
+ 1) + 1];
2752 for (int pwrindex
= 0; pwrindex
< count
; pwrindex
++) {
2753 strcat(pwrbuf
, " ");
2754 strcpy(pwrtmp
, labels
[pwrindex
]);
2755 // trim trailing space
2757 while ((sp
= strchr(pwrtmp
, ' '))) {
2760 strcat(pwrbuf
, pwrtmp
);
2765 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2767 char *fmt
= "vtxtable powerlabels%s";
2768 bool equalsDefault
= false;
2769 if (defaultConfig
) {
2770 equalsDefault
= true;
2771 for (int pwrindex
= 0; pwrindex
< VTX_TABLE_MAX_POWER_LEVELS
; pwrindex
++) {
2772 if (strcasecmp(defaultConfig
->powerLabels
[pwrindex
], currentConfig
->powerLabels
[pwrindex
])) {
2773 equalsDefault
= false;
2776 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2777 char *pwrbuf
= formatVtxTablePowerLabels(defaultConfig
->powerLabels
, VTX_TABLE_MAX_POWER_LEVELS
);
2778 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2781 char *pwrbuf
= formatVtxTablePowerLabels(currentConfig
->powerLabels
, currentConfig
->powerLevels
);
2782 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2786 static void printVtxTable(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2791 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2794 equalsDefault
= false;
2795 fmt
= "vtxtable bands %d";
2796 if (defaultConfig
) {
2797 equalsDefault
= (defaultConfig
->bands
== currentConfig
->bands
);
2798 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2799 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->bands
);
2801 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->bands
);
2804 equalsDefault
= false;
2805 fmt
= "vtxtable channels %d";
2806 if (defaultConfig
) {
2807 equalsDefault
= (defaultConfig
->channels
== currentConfig
->channels
);
2808 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2809 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->channels
);
2811 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->channels
);
2815 for (int band
= 0; band
< currentConfig
->bands
; band
++) {
2816 headingStr
= printVtxTableBand(dumpMask
, band
, currentConfig
, defaultConfig
, headingStr
);
2821 equalsDefault
= false;
2822 fmt
= "vtxtable powerlevels %d";
2823 if (defaultConfig
) {
2824 equalsDefault
= (defaultConfig
->powerLevels
== currentConfig
->powerLevels
);
2825 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2826 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->powerLevels
);
2828 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->powerLevels
);
2833 headingStr
= printVtxTablePowerValues(dumpMask
, currentConfig
, defaultConfig
, headingStr
);
2834 headingStr
= printVtxTablePowerLabels(dumpMask
, currentConfig
, defaultConfig
, headingStr
);
2837 static void cliVtxTable(char *cmdline
)
2842 // Band number or nothing
2843 tok
= strtok_r(cmdline
, " ", &saveptr
);
2846 printVtxTable(DUMP_MASTER
| HIDE_UNUSED
, vtxTableConfigMutable(), NULL
, NULL
);
2850 if (strcasecmp(tok
, "bands") == 0) {
2851 tok
= strtok_r(NULL
, " ", &saveptr
);
2852 int bands
= atoi(tok
);
2853 if (bands
< 0 || bands
> VTX_TABLE_MAX_BANDS
) {
2854 cliPrintErrorLinef("INVALID BAND COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_BANDS
);
2857 if (bands
< vtxTableConfigMutable()->bands
) {
2858 for (int i
= bands
; i
< vtxTableConfigMutable()->bands
; i
++) {
2859 vtxTableConfigClearBand(vtxTableConfigMutable(), i
);
2862 vtxTableConfigMutable()->bands
= bands
;
2864 } else if (strcasecmp(tok
, "channels") == 0) {
2865 tok
= strtok_r(NULL
, " ", &saveptr
);
2867 int channels
= atoi(tok
);
2868 if (channels
< 0 || channels
> VTX_TABLE_MAX_CHANNELS
) {
2869 cliPrintErrorLinef("INVALID CHANNEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_CHANNELS
);
2872 if (channels
< vtxTableConfigMutable()->channels
) {
2873 for (int i
= 0; i
< VTX_TABLE_MAX_BANDS
; i
++) {
2874 vtxTableConfigClearChannels(vtxTableConfigMutable(), i
, channels
);
2877 vtxTableConfigMutable()->channels
= channels
;
2879 } else if (strcasecmp(tok
, "powerlevels") == 0) {
2880 // Number of power levels
2881 tok
= strtok_r(NULL
, " ", &saveptr
);
2883 int levels
= atoi(tok
);
2884 if (levels
< 0 || levels
> VTX_TABLE_MAX_POWER_LEVELS
) {
2885 cliPrintErrorLinef("INVALID POWER LEVEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_POWER_LEVELS
);
2887 if (levels
< vtxTableConfigMutable()->powerLevels
) {
2888 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels
);
2889 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels
);
2891 vtxTableConfigMutable()->powerLevels
= levels
;
2894 // XXX Show current level count?
2898 } else if (strcasecmp(tok
, "powervalues") == 0) {
2900 uint16_t power
[VTX_TABLE_MAX_POWER_LEVELS
];
2902 int levels
= vtxTableConfigMutable()->powerLevels
;
2904 memset(power
, 0, sizeof(power
));
2906 for (count
= 0; count
< levels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); count
++) {
2907 int value
= atoi(tok
);
2908 power
[count
] = value
;
2911 // Check remaining tokens
2913 if (count
< levels
) {
2914 cliPrintErrorLinef("NOT ENOUGH VALUES (EXPECTED %d)", levels
);
2916 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
2917 cliPrintErrorLinef("TOO MANY VALUES (EXPECTED %d)", levels
);
2921 for (int i
= 0; i
< VTX_TABLE_MAX_POWER_LEVELS
; i
++) {
2922 vtxTableConfigMutable()->powerValues
[i
] = power
[i
];
2925 } else if (strcasecmp(tok
, "powerlabels") == 0) {
2927 char label
[VTX_TABLE_MAX_POWER_LEVELS
][VTX_TABLE_POWER_LABEL_LENGTH
+ 1];
2928 int levels
= vtxTableConfigMutable()->powerLevels
;
2930 for (count
= 0; count
< levels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); count
++) {
2931 strncpy(label
[count
], tok
, VTX_TABLE_POWER_LABEL_LENGTH
);
2932 for (unsigned i
= 0; i
< strlen(label
[count
]); i
++) {
2933 label
[count
][i
] = toupper(label
[count
][i
]);
2937 // Check remaining tokens
2939 if (count
< levels
) {
2940 cliPrintErrorLinef("NOT ENOUGH LABELS (EXPECTED %d)", levels
);
2942 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
2943 cliPrintErrorLinef("TOO MANY LABELS (EXPECTED %d)", levels
);
2947 for (int i
= 0; i
< count
; i
++) {
2948 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels
[i
], label
[i
], VTX_TABLE_POWER_LABEL_LENGTH
);
2950 } else if (strcasecmp(tok
, "band") == 0) {
2952 int bands
= vtxTableConfigMutable()->bands
;
2954 tok
= strtok_r(NULL
, " ", &saveptr
);
2959 int band
= atoi(tok
);
2962 if (band
< 0 || band
>= bands
) {
2963 cliPrintErrorLinef("INVALID BAND NUMBER %s (EXPECTED 1-%d)", tok
, bands
);
2968 tok
= strtok_r(NULL
, " ", &saveptr
);
2974 char bandname
[VTX_TABLE_BAND_NAME_LENGTH
+ 1];
2975 memset(bandname
, 0, VTX_TABLE_BAND_NAME_LENGTH
+ 1);
2976 strncpy(bandname
, tok
, VTX_TABLE_BAND_NAME_LENGTH
);
2977 for (unsigned i
= 0; i
< strlen(bandname
); i
++) {
2978 bandname
[i
] = toupper(bandname
[i
]);
2982 tok
= strtok_r(NULL
, " ", &saveptr
);
2988 char bandletter
= toupper(tok
[0]);
2990 uint16_t bandfreq
[VTX_TABLE_MAX_CHANNELS
];
2992 int channels
= vtxTableConfigMutable()->channels
;
2993 bool isFactory
= false;
2995 for (channel
= 0; channel
< channels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); channel
++) {
2996 if (channel
== 0 && !isdigit(tok
[0])) {
2998 if (strcasecmp(tok
, "FACTORY") == 0) {
3000 } else if (strcasecmp(tok
, "CUSTOM") == 0) {
3003 cliPrintErrorLinef("INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok
);
3007 int freq
= atoi(tok
);
3009 cliPrintErrorLinef("INVALID FREQUENCY %s", tok
);
3012 bandfreq
[channel
] = freq
;
3015 if (channel
< channels
) {
3016 cliPrintErrorLinef("NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels
);
3018 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
3019 cliPrintErrorLinef("TOO MANY FREQUENCIES (EXPECTED %d)", channels
);
3023 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames
[band
], bandname
, VTX_TABLE_BAND_NAME_LENGTH
);
3024 vtxTableConfigMutable()->bandLetters
[band
] = bandletter
;
3026 for (int i
= 0; i
< channel
; i
++) {
3027 vtxTableConfigMutable()->frequency
[band
][i
] = bandfreq
[i
];
3029 vtxTableConfigMutable()->isFactoryBand
[band
] = isFactory
;
3032 cliPrintErrorLinef("INVALID SUBCOMMAND %s", tok
);
3036 static void cliVtxInfo(char *cmdline
)
3040 // Display the available power levels
3041 uint16_t levels
[VTX_TABLE_MAX_POWER_LEVELS
];
3042 uint16_t powers
[VTX_TABLE_MAX_POWER_LEVELS
];
3043 vtxDevice_t
*vtxDevice
= vtxCommonDevice();
3045 uint8_t level_count
= vtxCommonGetVTXPowerLevels(vtxDevice
, levels
, powers
);
3048 for (int i
= 0; i
< level_count
; i
++) {
3049 cliPrintLinef("level %d dBm, power %d mW", levels
[i
], powers
[i
]);
3052 cliPrintErrorLinef("NO POWER VALUES DEFINED");
3055 cliPrintErrorLinef("NO VTX");
3058 #endif // USE_VTX_TABLE
3060 static void printName(dumpFlags_t dumpMask
, const pilotConfig_t
*pilotConfig
)
3062 const bool equalsDefault
= strlen(pilotConfig
->name
) == 0;
3063 cliDumpPrintLinef(dumpMask
, equalsDefault
, "\r\n# name: %s", equalsDefault
? emptyName
: pilotConfig
->name
);
3066 #if defined(USE_BOARD_INFO)
3068 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
3070 static void printBoardName(dumpFlags_t dumpMask
)
3072 if (!(dumpMask
& DO_DIFF
) || strlen(getBoardName())) {
3073 cliPrintLinef("board_name %s", getBoardName());
3077 static void cliBoardName(char *cmdline
)
3079 const unsigned int len
= strlen(cmdline
);
3080 const char *boardName
= getBoardName();
3081 if (len
> 0 && strlen(boardName
) != 0 && boardInformationIsSet() && (len
!= strlen(boardName
) || strncmp(boardName
, cmdline
, len
))) {
3082 cliPrintErrorLinef(ERROR_MESSAGE
, "BOARD_NAME", boardName
);
3084 if (len
> 0 && !configIsInCopy
&& setBoardName(cmdline
)) {
3085 boardInformationUpdated
= true;
3087 cliPrintHashLine("Set board_name.");
3089 printBoardName(DUMP_ALL
);
3093 static void printManufacturerId(dumpFlags_t dumpMask
)
3095 if (!(dumpMask
& DO_DIFF
) || strlen(getManufacturerId())) {
3096 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3100 static void cliManufacturerId(char *cmdline
)
3102 const unsigned int len
= strlen(cmdline
);
3103 const char *manufacturerId
= getManufacturerId();
3104 if (len
> 0 && boardInformationIsSet() && strlen(manufacturerId
) != 0 && (len
!= strlen(manufacturerId
) || strncmp(manufacturerId
, cmdline
, len
))) {
3105 cliPrintErrorLinef(ERROR_MESSAGE
, "MANUFACTURER_ID", manufacturerId
);
3107 if (len
> 0 && !configIsInCopy
&& setManufacturerId(cmdline
)) {
3108 boardInformationUpdated
= true;
3110 cliPrintHashLine("Set manufacturer_id.");
3112 printManufacturerId(DUMP_ALL
);
3116 #if defined(USE_SIGNATURE)
3117 static void writeSignature(char *signatureStr
, uint8_t *signature
)
3119 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
3120 tfp_sprintf(&signatureStr
[2 * i
], "%02x", signature
[i
]);
3124 static void cliSignature(char *cmdline
)
3126 const int len
= strlen(cmdline
);
3128 uint8_t signature
[SIGNATURE_LENGTH
] = {0};
3130 if (len
!= 2 * SIGNATURE_LENGTH
) {
3131 cliPrintErrorLinef("INVALID LENGTH: %d (EXPECTED: %d)", len
, 2 * SIGNATURE_LENGTH
);
3136 #define BLOCK_SIZE 2
3137 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
3138 char temp
[BLOCK_SIZE
+ 1];
3139 strncpy(temp
, &cmdline
[i
* BLOCK_SIZE
], BLOCK_SIZE
);
3140 temp
[BLOCK_SIZE
] = '\0';
3142 unsigned result
= strtoul(temp
, &end
, 16);
3143 if (end
== &temp
[BLOCK_SIZE
]) {
3144 signature
[i
] = result
;
3146 cliPrintErrorLinef("INVALID CHARACTER FOUND: %c", end
[0]);
3154 char signatureStr
[SIGNATURE_LENGTH
* 2 + 1] = {0};
3155 if (len
> 0 && signatureIsSet() && memcmp(signature
, getSignature(), SIGNATURE_LENGTH
)) {
3156 writeSignature(signatureStr
, getSignature());
3157 cliPrintErrorLinef(ERROR_MESSAGE
, "SIGNATURE", signatureStr
);
3159 if (len
> 0 && !configIsInCopy
&& setSignature(signature
)) {
3160 signatureUpdated
= true;
3162 writeSignature(signatureStr
, getSignature());
3164 cliPrintHashLine("Set signature.");
3165 } else if (signatureUpdated
|| signatureIsSet()) {
3166 writeSignature(signatureStr
, getSignature());
3169 cliPrintLinef("signature %s", signatureStr
);
3174 #undef ERROR_MESSAGE
3176 #endif // USE_BOARD_INFO
3178 static void cliMcuId(char *cmdline
)
3182 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0
, U_ID_1
, U_ID_2
);
3185 static void printFeature(dumpFlags_t dumpMask
, const uint32_t mask
, const uint32_t defaultMask
, const char *headingStr
)
3187 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3188 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // disabled features first
3189 if (strcmp(featureNames
[i
], emptyString
) != 0) { //Skip unused
3190 const char *format
= "feature -%s";
3191 const bool equalsDefault
= (~defaultMask
| mask
) & (1 << i
);
3192 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3193 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
3194 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, featureNames
[i
]);
3197 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // enabled features
3198 if (strcmp(featureNames
[i
], emptyString
) != 0) { //Skip unused
3199 const char *format
= "feature %s";
3200 if (defaultMask
& (1 << i
)) {
3201 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
3203 if (mask
& (1 << i
)) {
3204 const bool equalsDefault
= (defaultMask
| ~mask
) & (1 << i
);
3205 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3206 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, featureNames
[i
]);
3212 static void cliFeature(char *cmdline
)
3214 uint32_t len
= strlen(cmdline
);
3215 const uint32_t mask
= featureConfig()->enabledFeatures
;
3217 cliPrint("Enabled: ");
3218 for (uint32_t i
= 0; ; i
++) {
3219 if (featureNames
[i
] == NULL
) {
3222 if (mask
& (1 << i
)) {
3223 cliPrintf("%s ", featureNames
[i
]);
3227 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3228 cliPrint("Available:");
3229 for (uint32_t i
= 0; ; i
++) {
3230 if (featureNames
[i
] == NULL
)
3232 if (strcmp(featureNames
[i
], emptyString
) != 0) //Skip unused
3233 cliPrintf(" %s", featureNames
[i
]);
3240 bool remove
= false;
3241 if (cmdline
[0] == '-') {
3244 cmdline
++; // skip over -
3248 for (uint32_t i
= 0; ; i
++) {
3249 if (featureNames
[i
] == NULL
) {
3250 cliPrintErrorLinef("INVALID NAME");
3254 if (strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
3257 if (feature
& FEATURE_GPS
) {
3258 cliPrintLine("unavailable");
3262 #ifndef USE_RANGEFINDER
3263 if (feature
& FEATURE_RANGEFINDER
) {
3264 cliPrintLine("unavailable");
3269 featureConfigClear(feature
);
3270 cliPrint("Disabled");
3272 featureConfigSet(feature
);
3273 cliPrint("Enabled");
3275 cliPrintLinef(" %s", featureNames
[i
]);
3282 #if defined(USE_BEEPER)
3283 static void printBeeper(dumpFlags_t dumpMask
, const uint32_t offFlags
, const uint32_t offFlagsDefault
, const char *name
, const uint32_t allowedFlags
, const char *headingStr
)
3285 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3286 const uint8_t beeperCount
= beeperTableEntryCount();
3287 for (int32_t i
= 0; i
< beeperCount
- 1; i
++) {
3288 if (beeperModeMaskForTableIndex(i
) & allowedFlags
) {
3289 const char *formatOff
= "%s -%s";
3290 const char *formatOn
= "%s %s";
3291 const uint32_t beeperModeMask
= beeperModeMaskForTableIndex(i
);
3292 cliDefaultPrintLinef(dumpMask
, ~(offFlags
^ offFlagsDefault
) & beeperModeMask
, offFlags
& beeperModeMask
? formatOn
: formatOff
, name
, beeperNameForTableIndex(i
));
3293 const bool equalsDefault
= ~(offFlags
^ offFlagsDefault
) & beeperModeMask
;
3294 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3295 cliDumpPrintLinef(dumpMask
, equalsDefault
, offFlags
& beeperModeMask
? formatOff
: formatOn
, name
, beeperNameForTableIndex(i
));
3300 static void processBeeperCommand(char *cmdline
, uint32_t *offFlags
, const uint32_t allowedFlags
)
3302 uint32_t len
= strlen(cmdline
);
3303 uint8_t beeperCount
= beeperTableEntryCount();
3306 cliPrintf("Disabled:");
3307 for (int32_t i
= 0; ; i
++) {
3308 if (i
== beeperCount
- 1) {
3314 if (beeperModeMaskForTableIndex(i
) & *offFlags
)
3315 cliPrintf(" %s", beeperNameForTableIndex(i
));
3318 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3319 cliPrint("Available:");
3320 for (uint32_t i
= 0; i
< beeperCount
; i
++) {
3321 if (beeperModeMaskForTableIndex(i
) & allowedFlags
) {
3322 cliPrintf(" %s", beeperNameForTableIndex(i
));
3327 bool remove
= false;
3328 if (cmdline
[0] == '-') {
3329 remove
= true; // this is for beeper OFF condition
3334 for (uint32_t i
= 0; ; i
++) {
3335 if (i
== beeperCount
) {
3336 cliPrintErrorLinef("INVALID NAME");
3339 if (strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0 && beeperModeMaskForTableIndex(i
) & (allowedFlags
| BEEPER_GET_FLAG(BEEPER_ALL
))) {
3340 if (remove
) { // beeper off
3341 if (i
== BEEPER_ALL
- 1) {
3342 *offFlags
= allowedFlags
;
3344 *offFlags
|= beeperModeMaskForTableIndex(i
);
3346 cliPrint("Disabled");
3349 if (i
== BEEPER_ALL
- 1) {
3352 *offFlags
&= ~beeperModeMaskForTableIndex(i
);
3354 cliPrint("Enabled");
3356 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
3363 #if defined(USE_DSHOT)
3364 static void cliBeacon(char *cmdline
)
3366 processBeeperCommand(cmdline
, &(beeperConfigMutable()->dshotBeaconOffFlags
), DSHOT_BEACON_ALLOWED_MODES
);
3370 static void cliBeeper(char *cmdline
)
3372 processBeeperCommand(cmdline
, &(beeperConfigMutable()->beeper_off_flags
), BEEPER_ALLOWED_MODES
);
3376 #if defined(USE_RX_BIND)
3377 void cliRxBind(char *cmdline
){
3379 if (!startRxBind()) {
3380 cliPrint("Not supported.");
3382 cliPrint("Binding...");
3387 static void printMap(dumpFlags_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
, const char *headingStr
)
3389 bool equalsDefault
= true;
3391 char bufDefault
[16];
3394 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3395 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3396 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3397 if (defaultRxConfig
) {
3398 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3399 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
3404 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3405 const char *formatMap
= "map %s";
3406 if (defaultRxConfig
) {
3407 bufDefault
[i
] = '\0';
3408 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
3410 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
3414 static void cliMap(char *cmdline
)
3417 char buf
[RX_MAPPABLE_CHANNEL_COUNT
+ 1];
3419 uint32_t len
= strlen(cmdline
);
3420 if (len
== RX_MAPPABLE_CHANNEL_COUNT
) {
3422 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3423 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
3427 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3428 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
3430 if (strchr(rcChannelLetters
, buf
[i
]) && !strchr(buf
+ i
+ 1, buf
[i
]))
3433 cliShowParseError();
3436 parseRcChannels(buf
, rxConfigMutable());
3437 } else if (len
> 0) {
3438 cliShowParseError();
3442 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3443 buf
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
3447 cliPrintLinef("map %s", buf
);
3450 static char *skipSpace(char *buffer
)
3452 while (*(buffer
) == ' ') {
3459 static char *checkCommand(char *cmdline
, const char *command
)
3461 if (!strncasecmp(cmdline
, command
, strlen(command
)) // command names match
3462 && (isspace((unsigned)cmdline
[strlen(command
)]) || cmdline
[strlen(command
)] == 0)) {
3463 return skipSpace(cmdline
+ strlen(command
) + 1);
3469 static void cliRebootEx(rebootTarget_e rebootTarget
)
3471 cliPrint("\r\nRebooting");
3473 waitForSerialPortToFinishTransmitting(cliPort
);
3476 switch (rebootTarget
) {
3477 case REBOOT_TARGET_BOOTLOADER_ROM
:
3478 systemResetToBootloader(BOOTLOADER_REQUEST_ROM
);
3481 #if defined(USE_FLASH_BOOT_LOADER)
3482 case REBOOT_TARGET_BOOTLOADER_FLASH
:
3483 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH
);
3487 case REBOOT_TARGET_FIRMWARE
:
3495 static void cliReboot(void)
3497 cliRebootEx(REBOOT_TARGET_FIRMWARE
);
3500 static void cliBootloader(char *cmdline
)
3502 rebootTarget_e rebootTarget
;
3504 #if !defined(USE_FLASH_BOOT_LOADER)
3507 strncasecmp(cmdline
, "rom", 3) == 0) {
3508 rebootTarget
= REBOOT_TARGET_BOOTLOADER_ROM
;
3510 cliPrintHashLine("restarting in ROM bootloader mode");
3511 #if defined(USE_FLASH_BOOT_LOADER)
3512 } else if (isEmpty(cmdline
) || strncasecmp(cmdline
, "flash", 5) == 0) {
3513 rebootTarget
= REBOOT_TARGET_BOOTLOADER_FLASH
;
3515 cliPrintHashLine("restarting in flash bootloader mode");
3518 cliPrintErrorLinef("Invalid option");
3523 cliRebootEx(rebootTarget
);
3526 static void cliExit(char *cmdline
)
3530 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3536 // incase a motor was left running during motortest, clear it here
3537 mixerResetDisarmedMotors();
3542 static void cliGpsPassthrough(char *cmdline
)
3546 gpsEnablePassthrough(cliPort
);
3550 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3551 static void cliPrintGyroRegisters(uint8_t whichSensor
)
3553 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor
, MPU_RA_WHO_AM_I
));
3554 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_CONFIG
));
3555 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_GYRO_CONFIG
));
3558 static void cliDumpGyroRegisters(char *cmdline
)
3560 #ifdef USE_MULTI_GYRO
3561 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_1
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
3562 cliPrintLinef("\r\n# Gyro 1");
3563 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
3565 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_2
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
3566 cliPrintLinef("\r\n# Gyro 2");
3567 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2
);
3570 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
3577 static int parseOutputIndex(char *pch
, bool allowAllEscs
) {
3578 int outputIndex
= atoi(pch
);
3579 if ((outputIndex
>= 0) && (outputIndex
< getMotorCount())) {
3580 cliPrintLinef("Using output %d.", outputIndex
);
3581 } else if (allowAllEscs
&& outputIndex
== ALL_MOTORS
) {
3582 cliPrintLinef("Using all outputs.");
3584 cliPrintErrorLinef("INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3592 #if defined(USE_DSHOT)
3593 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3595 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3596 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3597 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3605 #define ESC_INFO_VERSION_POSITION 12
3607 void printEscInfo(const uint8_t *escInfoBuffer
, uint8_t bytesRead
)
3609 bool escInfoReceived
= false;
3610 if (bytesRead
> ESC_INFO_VERSION_POSITION
) {
3611 uint8_t escInfoVersion
;
3612 uint8_t frameLength
;
3613 if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 254) {
3614 escInfoVersion
= ESC_INFO_BLHELI32
;
3615 frameLength
= ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
;
3616 } else if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 255) {
3617 escInfoVersion
= ESC_INFO_KISS_V2
;
3618 frameLength
= ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE
;
3620 escInfoVersion
= ESC_INFO_KISS_V1
;
3621 frameLength
= ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE
;
3624 if (bytesRead
== frameLength
) {
3625 escInfoReceived
= true;
3627 if (calculateCrc8(escInfoBuffer
, frameLength
- 1) == escInfoBuffer
[frameLength
- 1]) {
3628 uint8_t firmwareVersion
= 0;
3629 uint8_t firmwareSubVersion
= 0;
3630 uint8_t escType
= 0;
3631 switch (escInfoVersion
) {
3632 case ESC_INFO_KISS_V1
:
3633 firmwareVersion
= escInfoBuffer
[12];
3634 firmwareSubVersion
= (escInfoBuffer
[13] & 0x1f) + 97;
3635 escType
= (escInfoBuffer
[13] & 0xe0) >> 5;
3638 case ESC_INFO_KISS_V2
:
3639 firmwareVersion
= escInfoBuffer
[13];
3640 firmwareSubVersion
= escInfoBuffer
[14];
3641 escType
= escInfoBuffer
[15];
3644 case ESC_INFO_BLHELI32
:
3645 firmwareVersion
= escInfoBuffer
[13];
3646 firmwareSubVersion
= escInfoBuffer
[14];
3647 escType
= escInfoBuffer
[15];
3652 cliPrint("ESC Type: ");
3653 switch (escInfoVersion
) {
3654 case ESC_INFO_KISS_V1
:
3655 case ESC_INFO_KISS_V2
:
3658 cliPrintLine("KISS8A");
3662 cliPrintLine("KISS16A");
3666 cliPrintLine("KISS24A");
3670 cliPrintLine("KISS Ultralite");
3674 cliPrintLine("unknown");
3680 case ESC_INFO_BLHELI32
:
3682 char *escType
= (char *)(escInfoBuffer
+ 31);
3684 cliPrintLine(escType
);
3690 cliPrint("MCU Serial No: 0x");
3691 for (int i
= 0; i
< 12; i
++) {
3692 if (i
&& (i
% 3 == 0)) {
3695 cliPrintf("%02x", escInfoBuffer
[i
]);
3699 switch (escInfoVersion
) {
3700 case ESC_INFO_KISS_V1
:
3701 case ESC_INFO_KISS_V2
:
3702 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion
/ 100, firmwareVersion
% 100, (char)firmwareSubVersion
);
3705 case ESC_INFO_BLHELI32
:
3706 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion
, firmwareSubVersion
);
3710 if (escInfoVersion
== ESC_INFO_KISS_V2
|| escInfoVersion
== ESC_INFO_BLHELI32
) {
3711 cliPrintLinef("Rotation Direction: %s", escInfoBuffer
[16] ? "reversed" : "normal");
3712 cliPrintLinef("3D: %s", escInfoBuffer
[17] ? "on" : "off");
3713 if (escInfoVersion
== ESC_INFO_BLHELI32
) {
3714 uint8_t setting
= escInfoBuffer
[18];
3715 cliPrint("Low voltage Limit: ");
3718 cliPrintLine("off");
3722 cliPrintLine("unsupported");
3726 cliPrintLinef("%d.%01d", setting
/ 10, setting
% 10);
3731 setting
= escInfoBuffer
[19];
3732 cliPrint("Current Limit: ");
3735 cliPrintLine("off");
3739 cliPrintLine("unsupported");
3743 cliPrintLinef("%d", setting
);
3748 for (int i
= 0; i
< 4; i
++) {
3749 setting
= escInfoBuffer
[i
+ 20];
3750 cliPrintLinef("LED %d: %s", i
, setting
? (setting
== 255) ? "unsupported" : "on" : "off");
3755 cliPrintErrorLinef("CHECKSUM ERROR.");
3760 if (!escInfoReceived
) {
3761 cliPrintLine("No Info.");
3765 static void executeEscInfoCommand(uint8_t escIndex
)
3767 cliPrintLinef("Info for ESC %d:", escIndex
);
3769 uint8_t escInfoBuffer
[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
];
3771 startEscDataRead(escInfoBuffer
, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
);
3773 dshotCommandWrite(escIndex
, getMotorCount(), DSHOT_CMD_ESC_INFO
, true);
3777 printEscInfo(escInfoBuffer
, getNumberEscBytesRead());
3779 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3782 // XXX Review dshotprog command under refactored motor handling
3784 static void cliDshotProg(char *cmdline
)
3786 if (isEmpty(cmdline
) || motorConfig()->dev
.motorPwmProtocol
< PWM_TYPE_DSHOT150
) {
3787 cliShowParseError();
3793 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3796 bool firstCommand
= true;
3797 while (pch
!= NULL
) {
3800 escIndex
= parseOutputIndex(pch
, true);
3801 if (escIndex
== -1) {
3808 int command
= atoi(pch
);
3809 if (command
>= 0 && command
< DSHOT_MIN_THROTTLE
) {
3811 // pwmDisableMotors();
3814 if (command
== DSHOT_CMD_ESC_INFO
) {
3815 delay(5); // Wait for potential ESC telemetry transmission to finish
3820 firstCommand
= false;
3823 if (command
!= DSHOT_CMD_ESC_INFO
) {
3824 dshotCommandWrite(escIndex
, getMotorCount(), command
, true);
3826 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3827 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
3828 if (escIndex
!= ALL_MOTORS
) {
3829 executeEscInfoCommand(escIndex
);
3831 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
3832 executeEscInfoCommand(i
);
3838 cliPrintLine("Not supported.");
3842 cliPrintLinef("Command Sent: %d", command
);
3845 cliPrintErrorLinef("INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE
- 1);
3853 pch
= strtok_r(NULL
, " ", &saveptr
);
3860 #ifdef USE_ESCSERIAL
3861 static void cliEscPassthrough(char *cmdline
)
3863 if (isEmpty(cmdline
)) {
3864 cliShowParseError();
3870 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3874 while (pch
!= NULL
) {
3877 if (strncasecmp(pch
, "sk", strlen(pch
)) == 0) {
3878 mode
= PROTOCOL_SIMONK
;
3879 } else if (strncasecmp(pch
, "bl", strlen(pch
)) == 0) {
3880 mode
= PROTOCOL_BLHELI
;
3881 } else if (strncasecmp(pch
, "ki", strlen(pch
)) == 0) {
3882 mode
= PROTOCOL_KISS
;
3883 } else if (strncasecmp(pch
, "cc", strlen(pch
)) == 0) {
3884 mode
= PROTOCOL_KISSALL
;
3886 cliShowParseError();
3892 escIndex
= parseOutputIndex(pch
, mode
== PROTOCOL_KISS
);
3893 if (escIndex
== -1) {
3899 cliShowParseError();
3907 pch
= strtok_r(NULL
, " ", &saveptr
);
3910 if (!escEnablePassthrough(cliPort
, &motorConfig()->dev
, escIndex
, mode
)) {
3911 cliPrintErrorLinef("Error starting ESC connection");
3916 #ifndef USE_QUAD_MIXER_ONLY
3917 static void cliMixer(char *cmdline
)
3921 len
= strlen(cmdline
);
3924 cliPrintLinef("Mixer: %s", mixerNames
[mixerConfig()->mixerMode
- 1]);
3926 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3927 cliPrint("Available:");
3928 for (uint32_t i
= 0; ; i
++) {
3929 if (mixerNames
[i
] == NULL
)
3931 cliPrintf(" %s", mixerNames
[i
]);
3937 for (uint32_t i
= 0; ; i
++) {
3938 if (mixerNames
[i
] == NULL
) {
3939 cliPrintErrorLinef("INVALID NAME");
3942 if (strncasecmp(cmdline
, mixerNames
[i
], len
) == 0) {
3943 mixerConfigMutable()->mixerMode
= i
+ 1;
3952 static void cliMotor(char *cmdline
)
3954 if (isEmpty(cmdline
)) {
3955 cliShowParseError();
3964 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3966 while (pch
!= NULL
) {
3969 motorIndex
= parseOutputIndex(pch
, true);
3970 if (motorIndex
== -1) {
3976 motorValue
= atoi(pch
);
3981 pch
= strtok_r(NULL
, " ", &saveptr
);
3985 if (motorValue
< PWM_RANGE_MIN
|| motorValue
> PWM_RANGE_MAX
) {
3986 cliShowArgumentRangeError("VALUE", 1000, 2000);
3988 uint32_t motorOutputValue
= motorConvertFromExternal(motorValue
);
3990 if (motorIndex
!= ALL_MOTORS
) {
3991 motor_disarmed
[motorIndex
] = motorOutputValue
;
3993 cliPrintLinef("motor %d: %d", motorIndex
, motorOutputValue
);
3995 for (int i
= 0; i
< getMotorCount(); i
++) {
3996 motor_disarmed
[i
] = motorOutputValue
;
3999 cliPrintLinef("all motors: %d", motorOutputValue
);
4003 cliShowParseError();
4008 static void cliPlaySound(char *cmdline
)
4012 static int lastSoundIdx
= -1;
4014 if (isEmpty(cmdline
)) {
4015 i
= lastSoundIdx
+ 1; //next sound index
4016 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
4017 while (true) { //no name for index; try next one
4018 if (++i
>= beeperTableEntryCount())
4019 i
= 0; //if end then wrap around to first entry
4020 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
4021 break; //if name OK then play sound below
4022 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
4023 cliPrintErrorLinef("ERROR PLAYING SOUND");
4028 } else { //index value was given
4030 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
4031 cliPrintLinef("No sound for index %d", i
);
4037 cliPrintLinef("Playing sound %d: %s", i
, name
);
4038 beeper(beeperModeForTableIndex(i
));
4042 static void cliProfile(char *cmdline
)
4044 if (isEmpty(cmdline
)) {
4045 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4048 const int i
= atoi(cmdline
);
4049 if (i
>= 0 && i
< PID_PROFILE_COUNT
) {
4050 changePidProfile(i
);
4053 cliPrintErrorLinef("PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT
- 1);
4058 static void cliRateProfile(char *cmdline
)
4060 if (isEmpty(cmdline
)) {
4061 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4064 const int i
= atoi(cmdline
);
4065 if (i
>= 0 && i
< CONTROL_RATE_PROFILE_COUNT
) {
4066 changeControlRateProfile(i
);
4069 cliPrintErrorLinef("RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT
- 1);
4074 static void cliDumpPidProfile(uint8_t pidProfileIndex
, dumpFlags_t dumpMask
)
4076 if (pidProfileIndex
>= PID_PROFILE_COUNT
) {
4081 pidProfileIndexToUse
= pidProfileIndex
;
4086 char profileStr
[10];
4087 tfp_sprintf(profileStr
, "profile %d", pidProfileIndex
);
4088 dumpAllValues(PROFILE_VALUE
, dumpMask
, profileStr
);
4090 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4093 static void cliDumpRateProfile(uint8_t rateProfileIndex
, dumpFlags_t dumpMask
)
4095 if (rateProfileIndex
>= CONTROL_RATE_PROFILE_COUNT
) {
4100 rateProfileIndexToUse
= rateProfileIndex
;
4105 char rateProfileStr
[14];
4106 tfp_sprintf(rateProfileStr
, "rateprofile %d", rateProfileIndex
);
4107 dumpAllValues(PROFILE_RATE_VALUE
, dumpMask
, rateProfileStr
);
4109 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4112 #ifdef USE_CLI_BATCH
4113 static void cliPrintCommandBatchWarning(const char *warning
)
4115 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4117 cliPrintErrorLinef(warning
);
4121 static void resetCommandBatch(void)
4123 commandBatchActive
= false;
4124 commandBatchError
= false;
4127 static void cliBatch(char *cmdline
)
4129 if (strncasecmp(cmdline
, "start", 5) == 0) {
4130 if (!commandBatchActive
) {
4131 commandBatchActive
= true;
4132 commandBatchError
= false;
4134 cliPrintLine("Command batch started");
4135 } else if (strncasecmp(cmdline
, "end", 3) == 0) {
4136 if (commandBatchActive
&& commandBatchError
) {
4137 cliPrintCommandBatchWarning(NULL
);
4139 cliPrintLine("Command batch ended");
4141 resetCommandBatch();
4143 cliPrintErrorLinef("Invalid option");
4148 static bool prepareSave(void)
4150 #if defined(USE_CUSTOM_DEFAULTS)
4151 if (processingCustomDefaults
) {
4156 #ifdef USE_CLI_BATCH
4157 if (commandBatchActive
&& commandBatchError
) {
4162 #if defined(USE_BOARD_INFO)
4163 if (boardInformationUpdated
) {
4164 persistBoardInformation();
4166 #if defined(USE_SIGNATURE)
4167 if (signatureUpdated
) {
4171 #endif // USE_BOARD_INFO
4176 bool tryPrepareSave(void)
4178 bool success
= prepareSave();
4179 #if defined(USE_CLI_BATCH)
4181 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
4182 resetCommandBatch();
4193 static void cliSave(char *cmdline
)
4197 if (tryPrepareSave()) {
4199 cliPrintHashLine("saving");
4205 #if defined(USE_CUSTOM_DEFAULTS)
4206 bool resetConfigToCustomDefaults(void)
4210 #ifdef USE_CLI_BATCH
4211 commandBatchError
= false;
4214 cliProcessCustomDefaults(true);
4216 return prepareSave();
4219 static bool isCustomDefaults(char *ptr
)
4221 return strncmp(ptr
, "# " FC_FIRMWARE_NAME
, 12) == 0;
4224 bool hasCustomDefaults(void)
4226 return isCustomDefaults(customDefaultsStart
);
4230 static void cliDefaults(char *cmdline
)
4232 bool saveConfigs
= true;
4233 #if defined(USE_CUSTOM_DEFAULTS)
4234 bool useCustomDefaults
= true;
4235 #elif defined(USE_CUSTOM_DEFAULTS_ADDRESS)
4236 // Required to keep the linker from eliminating these
4237 if (customDefaultsStart
!= customDefaultsEnd
) {
4242 if (isEmpty(cmdline
)) {
4243 } else if (strncasecmp(cmdline
, "nosave", 6) == 0) {
4244 saveConfigs
= false;
4245 #if defined(USE_CUSTOM_DEFAULTS)
4246 } else if (strncasecmp(cmdline
, "bare", 4) == 0) {
4247 useCustomDefaults
= false;
4248 } else if (strncasecmp(cmdline
, "show", 4) == 0) {
4249 char *customDefaultsPtr
= customDefaultsStart
;
4250 if (isCustomDefaults(customDefaultsPtr
)) {
4251 while (*customDefaultsPtr
&& *customDefaultsPtr
!= 0xFF && customDefaultsPtr
< customDefaultsEnd
) {
4252 if (*customDefaultsPtr
!= '\n') {
4253 cliPrintf("%c", *customDefaultsPtr
++);
4256 customDefaultsPtr
++;
4260 cliPrintError("NO CUSTOM DEFAULTS FOUND");
4266 cliPrintError("INVALID OPTION");
4271 cliPrintHashLine("resetting to defaults");
4275 #ifdef USE_CLI_BATCH
4276 // Reset only the error state and allow the batch active state to remain.
4277 // This way if a "defaults nosave" was issued after the "batch on" we'll
4278 // only reset the current error state but the batch will still be active
4279 // for subsequent commands.
4280 commandBatchError
= false;
4283 #if defined(USE_CUSTOM_DEFAULTS)
4284 if (useCustomDefaults
) {
4285 cliProcessCustomDefaults(false);
4289 if (saveConfigs
&& tryPrepareSave()) {
4290 writeUnmodifiedConfigToEEPROM();
4296 void cliPrintVarDefault(const clivalue_t
*value
)
4298 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
4300 const char *defaultFormat
= "Default value: ";
4301 const int valueOffset
= getValueOffset(value
);
4302 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
4303 if (!equalsDefault
) {
4304 cliPrintf(defaultFormat
, value
->name
);
4305 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
4311 STATIC_UNIT_TESTED
void cliGet(char *cmdline
)
4313 const clivalue_t
*val
;
4314 int matchedCommands
= 0;
4316 pidProfileIndexToUse
= getCurrentPidProfileIndex();
4317 rateProfileIndexToUse
= getCurrentControlRateProfileIndex();
4319 backupAndResetConfigs(true);
4321 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4322 if (strcasestr(valueTable
[i
].name
, cmdline
)) {
4323 val
= &valueTable
[i
];
4324 if (matchedCommands
> 0) {
4327 cliPrintf("%s = ", valueTable
[i
].name
);
4328 cliPrintVar(val
, 0);
4330 switch (val
->type
& VALUE_SECTION_MASK
) {
4335 case PROFILE_RATE_VALUE
:
4343 cliPrintVarRange(val
);
4344 cliPrintVarDefault(val
);
4352 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4353 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4355 if (!matchedCommands
) {
4356 cliPrintErrorLinef("INVALID NAME");
4360 static uint8_t getWordLength(char *bufBegin
, char *bufEnd
)
4362 while (*(bufEnd
- 1) == ' ') {
4366 return bufEnd
- bufBegin
;
4369 uint16_t cliGetSettingIndex(char *name
, uint8_t length
)
4371 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4372 const char *settingName
= valueTable
[i
].name
;
4374 // ensure exact match when setting to prevent setting variables with shorter names
4375 if (strncasecmp(name
, settingName
, strlen(settingName
)) == 0 && length
== strlen(settingName
)) {
4379 return valueTableEntryCount
;
4382 STATIC_UNIT_TESTED
void cliSet(char *cmdline
)
4384 const uint32_t len
= strlen(cmdline
);
4387 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
4388 cliPrintLine("Current settings: ");
4390 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4391 const clivalue_t
*val
= &valueTable
[i
];
4392 cliPrintf("%s = ", valueTable
[i
].name
);
4393 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4396 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
4399 uint8_t variableNameLength
= getWordLength(cmdline
, eqptr
);
4401 // skip the '=' and any ' ' characters
4403 eqptr
= skipSpace(eqptr
);
4405 const uint16_t index
= cliGetSettingIndex(cmdline
, variableNameLength
);
4406 if (index
>= valueTableEntryCount
) {
4407 cliPrintErrorLinef("INVALID NAME");
4410 const clivalue_t
*val
= &valueTable
[index
];
4412 bool valueChanged
= false;
4414 switch (val
->type
& VALUE_MODE_MASK
) {
4416 if ((val
->type
& VALUE_TYPE_MASK
) == VAR_UINT32
) {
4417 uint32_t value
= strtoul(eqptr
, NULL
, 10);
4419 if (value
<= val
->config
.u32Max
) {
4420 cliSetVar(val
, value
);
4421 valueChanged
= true;
4424 int value
= atoi(eqptr
);
4428 getMinMax(val
, &min
, &max
);
4430 if (value
>= min
&& value
<= max
) {
4431 cliSetVar(val
, value
);
4432 valueChanged
= true;
4441 if ((val
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
4442 tableIndex
= TABLE_OFF_ON
;
4444 tableIndex
= val
->config
.lookup
.tableIndex
;
4446 const lookupTableEntry_t
*tableEntry
= &lookupTables
[tableIndex
];
4447 bool matched
= false;
4448 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
4449 matched
= tableEntry
->values
[tableValueIndex
] && strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
4452 value
= tableValueIndex
;
4454 cliSetVar(val
, value
);
4455 valueChanged
= true;
4463 const uint8_t arrayLength
= val
->config
.array
.length
;
4464 char *valPtr
= eqptr
;
4467 while (i
< arrayLength
&& valPtr
!= NULL
) {
4469 valPtr
= skipSpace(valPtr
);
4471 // process substring starting at valPtr
4472 // note: no need to copy substrings for atoi()
4473 // it stops at the first character that cannot be converted...
4474 switch (val
->type
& VALUE_TYPE_MASK
) {
4478 // fetch data pointer
4479 uint8_t *data
= (uint8_t *)cliGetValuePointer(val
) + i
;
4481 *data
= (uint8_t)atoi((const char*) valPtr
);
4487 // fetch data pointer
4488 int8_t *data
= (int8_t *)cliGetValuePointer(val
) + i
;
4490 *data
= (int8_t)atoi((const char*) valPtr
);
4496 // fetch data pointer
4497 uint16_t *data
= (uint16_t *)cliGetValuePointer(val
) + i
;
4499 *data
= (uint16_t)atoi((const char*) valPtr
);
4505 // fetch data pointer
4506 int16_t *data
= (int16_t *)cliGetValuePointer(val
) + i
;
4508 *data
= (int16_t)atoi((const char*) valPtr
);
4514 // fetch data pointer
4515 uint32_t *data
= (uint32_t *)cliGetValuePointer(val
) + i
;
4517 *data
= (uint32_t)strtoul((const char*) valPtr
, NULL
, 10);
4523 // find next comma (or end of string)
4524 valPtr
= strchr(valPtr
, ',') + 1;
4531 valueChanged
= true;
4535 char *valPtr
= eqptr
;
4536 valPtr
= skipSpace(valPtr
);
4538 const unsigned int len
= strlen(valPtr
);
4539 const uint8_t min
= val
->config
.string
.minlength
;
4540 const uint8_t max
= val
->config
.string
.maxlength
;
4541 const bool updatable
= ((val
->config
.string
.flags
& STRING_FLAGS_WRITEONCE
) == 0 ||
4542 strlen((char *)cliGetValuePointer(val
)) == 0 ||
4543 strncmp(valPtr
, (char *)cliGetValuePointer(val
), len
) == 0);
4545 if (updatable
&& len
> 0 && len
<= max
) {
4546 memset((char *)cliGetValuePointer(val
), 0, max
);
4547 if (len
>= min
&& strncmp(valPtr
, emptyName
, len
)) {
4548 strncpy((char *)cliGetValuePointer(val
), valPtr
, len
);
4550 valueChanged
= true;
4552 cliPrintErrorLinef("STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max
);
4559 cliPrintf("%s set to ", val
->name
);
4560 cliPrintVar(val
, 0);
4562 cliPrintErrorLinef("INVALID VALUE");
4563 cliPrintVarRange(val
);
4568 // no equals, check for matching variables.
4573 const char *getMcuTypeById(mcuTypeId_e id
)
4575 if (id
< MCU_TYPE_UNKNOWN
) {
4576 return mcuTypeNames
[id
];
4582 static void cliStatus(char *cmdline
)
4586 // MCU type, clock, vrefint, core temperature
4588 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock
/ 1000000));
4591 // Only F4 is capable of switching between HSE/HSI (for now)
4592 int sysclkSource
= SystemSYSCLKSource();
4594 const char *SYSCLKSource
[] = { "HSI", "HSE", "PLLP", "PLLR" };
4595 const char *PLLSource
[] = { "-HSI", "-HSE" };
4599 if (sysclkSource
>= 2) {
4600 pllSource
= SystemPLLSource();
4603 cliPrintf(" (%s%s)", SYSCLKSource
[sysclkSource
], (sysclkSource
< 2) ? "" : PLLSource
[pllSource
]);
4606 #ifdef USE_ADC_INTERNAL
4607 uint16_t vrefintMv
= getVrefMv();
4608 int16_t coretemp
= getCoreTemperatureCelsius();
4609 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv
/ 1000, (vrefintMv
% 1000) / 10, coretemp
);
4614 // Stack and config sizes and usages
4616 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4618 cliPrintf(", Stack used: %d", stackUsedSize());
4622 cliPrintLinef("Config size: %d, Max available config: %d", getEEPROMConfigSize(), getEEPROMStorageSize());
4626 #if defined(USE_SENSOR_NAMES)
4627 const uint32_t detectedSensorsMask
= sensorsMask();
4628 for (uint32_t i
= 0; ; i
++) {
4629 if (sensorTypeNames
[i
] == NULL
) {
4632 const uint32_t mask
= (1 << i
);
4633 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
4634 const uint8_t sensorHardwareIndex
= detectedSensors
[i
];
4635 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
4639 cliPrintf("%s=%s", sensorTypeNames
[i
], sensorHardware
);
4640 #if defined(USE_ACC)
4641 if (mask
== SENSOR_ACC
&& acc
.dev
.revisionCode
) {
4642 cliPrintf(".%c", acc
.dev
.revisionCode
);
4648 #endif /* USE_SENSOR_NAMES */
4650 #if defined(USE_OSD)
4651 osdDisplayPortDevice_e displayPortDevice
;
4652 osdGetDisplayPort(&displayPortDevice
);
4654 cliPrintLinef("OSD: %s", lookupTableOsdDisplayPortDevice
[displayPortDevice
]);
4657 // Uptime and wall clock
4659 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4662 char buf
[FORMATTED_DATE_TIME_BUFSIZE
];
4664 if (rtcGetDateTime(&dt
)) {
4665 dateTimeFormatLocal(buf
, &dt
);
4666 cliPrintf(", Current Time: %s", buf
);
4673 const int gyroRate
= getTaskDeltaTimeUs(TASK_GYRO
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTimeUs(TASK_GYRO
)));
4674 int rxRate
= getCurrentRxRefreshRate();
4676 rxRate
= (int)(1000000.0f
/ ((float)rxRate
));
4678 const int systemRate
= getTaskDeltaTimeUs(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTimeUs(TASK_SYSTEM
)));
4679 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4680 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE
), getTaskDeltaTimeUs(TASK_GYRO
), gyroRate
, rxRate
, systemRate
);
4684 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4686 // Other devices and status
4689 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
4691 const uint16_t i2cErrorCounter
= 0;
4693 cliPrintLinef("I2C Errors: %d", i2cErrorCounter
);
4699 cliPrint("Arming disable flags:");
4700 armingDisableFlags_e flags
= getArmingDisableFlags();
4702 const int bitpos
= ffs(flags
) - 1;
4703 flags
&= ~(1 << bitpos
);
4704 cliPrintf(" %s", armingDisableFlagNames
[bitpos
]);
4709 #if defined(USE_TASK_STATISTICS)
4710 static void cliTasks(char *cmdline
)
4714 int averageLoadSum
= 0;
4717 if (systemConfig()->task_statistics
) {
4718 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4720 cliPrintLine("Task list");
4723 for (taskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
4724 taskInfo_t taskInfo
;
4725 getTaskInfo(taskId
, &taskInfo
);
4726 if (taskInfo
.isEnabled
) {
4727 int taskFrequency
= taskInfo
.averageDeltaTimeUs
== 0 ? 0 : lrintf(1e6f
/ taskInfo
.averageDeltaTimeUs
);
4728 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
4729 const int maxLoad
= taskInfo
.maxExecutionTimeUs
== 0 ? 0 :(taskInfo
.maxExecutionTimeUs
* taskFrequency
+ 5000) / 1000;
4730 const int averageLoad
= taskInfo
.averageExecutionTimeUs
== 0 ? 0 : (taskInfo
.averageExecutionTimeUs
* taskFrequency
+ 5000) / 1000;
4731 if (taskId
!= TASK_SERIAL
) {
4732 maxLoadSum
+= maxLoad
;
4733 averageLoadSum
+= averageLoad
;
4735 if (systemConfig()->task_statistics
) {
4736 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4737 taskFrequency
, taskInfo
.maxExecutionTimeUs
, taskInfo
.averageExecutionTimeUs
,
4738 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, taskInfo
.totalExecutionTimeUs
/ 1000);
4740 cliPrintLinef("%6d", taskFrequency
);
4743 schedulerResetTaskMaxExecutionTime(taskId
);
4746 if (systemConfig()->task_statistics
) {
4747 cfCheckFuncInfo_t checkFuncInfo
;
4748 getCheckFuncInfo(&checkFuncInfo
);
4749 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo
.maxExecutionTimeUs
, checkFuncInfo
.averageExecutionTimeUs
, checkFuncInfo
.totalExecutionTimeUs
/ 1000);
4750 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
4751 schedulerResetCheckFunctionMaxExecutionTime();
4756 static void cliVersion(char *cmdline
)
4760 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4763 systemConfig()->boardIdentifier
,
4768 MSP_API_VERSION_STRING
4771 #ifdef FEATURE_CUT_LEVEL
4772 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL
);
4777 #ifdef USE_UNIFIED_TARGET
4779 #ifdef USE_BOARD_INFO
4780 if (strlen(getManufacturerId())) {
4781 cliPrintf("manufacturer_id: %s ", getManufacturerId());
4783 if (strlen(getBoardName())) {
4784 cliPrintf("board_name: %s ", getBoardName());
4786 #endif // USE_BOARD_INFO
4788 #ifdef USE_CUSTOM_DEFAULTS
4789 cliPrintf("custom defaults: %s", hasCustomDefaults() ? "YES" : "NO");
4790 #endif // USE_CUSTOM_DEFAULTS
4793 #endif // USE_UNIFIED_TARGET
4796 #ifdef USE_RC_SMOOTHING_FILTER
4797 static void cliRcSmoothing(char *cmdline
)
4800 rcSmoothingFilter_t
*rcSmoothingData
= getRcSmoothingData();
4801 cliPrint("# RC Smoothing Type: ");
4802 if (rxConfig()->rc_smoothing_type
== RC_SMOOTHING_TYPE_FILTER
) {
4803 cliPrintLine("FILTER");
4804 if (rcSmoothingAutoCalculate()) {
4805 const uint16_t avgRxFrameUs
= rcSmoothingData
->averageFrameTimeUs
;
4806 cliPrint("# Detected RX frame rate: ");
4807 if (avgRxFrameUs
== 0) {
4808 cliPrintLine("NO SIGNAL");
4810 cliPrintLinef("%d.%03dms", avgRxFrameUs
/ 1000, avgRxFrameUs
% 1000);
4813 cliPrintLinef("# Input filter type: %s", lookupTables
[TABLE_RC_SMOOTHING_INPUT_TYPE
].values
[rcSmoothingData
->inputFilterType
]);
4814 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingData
->inputCutoffFrequency
);
4815 if (rcSmoothingData
->inputCutoffSetting
== 0) {
4816 cliPrintLine("(auto)");
4818 cliPrintLine("(manual)");
4820 cliPrintf("# Derivative filter type: %s", lookupTables
[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE
].values
[rcSmoothingData
->derivativeFilterType
]);
4821 if (rcSmoothingData
->derivativeFilterTypeSetting
== RC_SMOOTHING_DERIVATIVE_AUTO
) {
4822 cliPrintLine(" (auto)");
4826 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingData
->derivativeCutoffFrequency
);
4827 if (rcSmoothingData
->derivativeFilterType
== RC_SMOOTHING_DERIVATIVE_OFF
) {
4828 cliPrintLine("off)");
4830 if (rcSmoothingData
->derivativeCutoffSetting
== 0) {
4831 cliPrintLine("auto)");
4833 cliPrintLine("manual)");
4837 cliPrintLine("INTERPOLATION");
4840 #endif // USE_RC_SMOOTHING_FILTER
4842 #if defined(USE_RESOURCE_MGMT)
4844 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
4847 const uint8_t owner
;
4851 const uint8_t maxIndex
;
4852 } cliResourceValue_t
;
4854 // Handy macros for keeping the table tidy.
4855 // DEFS : Single entry
4856 // DEFA : Array of uint8_t (stride = 1)
4857 // DEFW : Wider stride case; array of structs.
4859 #define DEFS(owner, pgn, type, member) \
4860 { owner, pgn, 0, offsetof(type, member), 0 }
4862 #define DEFA(owner, pgn, type, member, max) \
4863 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
4865 #define DEFW(owner, pgn, type, member, max) \
4866 { owner, pgn, sizeof(type), offsetof(type, member), max }
4868 const cliResourceValue_t resourceTable
[] = {
4870 DEFS( OWNER_BEEPER
, PG_BEEPER_DEV_CONFIG
, beeperDevConfig_t
, ioTag
) ,
4872 DEFA( OWNER_MOTOR
, PG_MOTOR_CONFIG
, motorConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_MOTORS
),
4874 DEFA( OWNER_SERVO
, PG_SERVO_CONFIG
, servoConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_SERVOS
),
4876 #if defined(USE_PPM)
4877 DEFS( OWNER_PPMINPUT
, PG_PPM_CONFIG
, ppmConfig_t
, ioTag
),
4879 #if defined(USE_PWM)
4880 DEFA( OWNER_PWMINPUT
, PG_PWM_CONFIG
, pwmConfig_t
, ioTags
[0], PWM_INPUT_PORT_COUNT
),
4882 #ifdef USE_RANGEFINDER_HCSR04
4883 DEFS( OWNER_SONAR_TRIGGER
, PG_SONAR_CONFIG
, sonarConfig_t
, triggerTag
),
4884 DEFS( OWNER_SONAR_ECHO
, PG_SONAR_CONFIG
, sonarConfig_t
, echoTag
),
4886 #ifdef USE_LED_STRIP
4887 DEFS( OWNER_LED_STRIP
, PG_LED_STRIP_CONFIG
, ledStripConfig_t
, ioTag
),
4890 DEFA( OWNER_SERIAL_TX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagTx
[0], SERIAL_PORT_MAX_INDEX
),
4891 DEFA( OWNER_SERIAL_RX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagRx
[0], SERIAL_PORT_MAX_INDEX
),
4894 DEFA( OWNER_INVERTER
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagInverter
[0], SERIAL_PORT_MAX_INDEX
),
4897 DEFW( OWNER_I2C_SCL
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagScl
, I2CDEV_COUNT
),
4898 DEFW( OWNER_I2C_SDA
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagSda
, I2CDEV_COUNT
),
4900 DEFA( OWNER_LED
, PG_STATUS_LED_CONFIG
, statusLedConfig_t
, ioTags
[0], STATUS_LED_NUMBER
),
4901 #ifdef USE_SPEKTRUM_BIND
4902 DEFS( OWNER_RX_BIND
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_pin_override_ioTag
),
4903 DEFS( OWNER_RX_BIND_PLUG
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_plug_ioTag
),
4905 #ifdef USE_TRANSPONDER
4906 DEFS( OWNER_TRANSPONDER
, PG_TRANSPONDER_CONFIG
, transponderConfig_t
, ioTag
),
4909 DEFW( OWNER_SPI_SCK
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagSck
, SPIDEV_COUNT
),
4910 DEFW( OWNER_SPI_MISO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMiso
, SPIDEV_COUNT
),
4911 DEFW( OWNER_SPI_MOSI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMosi
, SPIDEV_COUNT
),
4913 #ifdef USE_ESCSERIAL
4914 DEFS( OWNER_ESCSERIAL
, PG_ESCSERIAL_CONFIG
, escSerialConfig_t
, ioTag
),
4916 #ifdef USE_CAMERA_CONTROL
4917 DEFS( OWNER_CAMERA_CONTROL
, PG_CAMERA_CONTROL_CONFIG
, cameraControlConfig_t
, ioTag
),
4920 DEFS( OWNER_ADC_BATT
, PG_ADC_CONFIG
, adcConfig_t
, vbat
.ioTag
),
4921 DEFS( OWNER_ADC_RSSI
, PG_ADC_CONFIG
, adcConfig_t
, rssi
.ioTag
),
4922 DEFS( OWNER_ADC_CURR
, PG_ADC_CONFIG
, adcConfig_t
, current
.ioTag
),
4923 DEFS( OWNER_ADC_EXT
, PG_ADC_CONFIG
, adcConfig_t
, external1
.ioTag
),
4926 DEFS( OWNER_BARO_CS
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_spi_csn
),
4927 DEFS( OWNER_BARO_EOC
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_eoc_tag
),
4928 DEFS( OWNER_BARO_XCLR
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_xclr_tag
),
4931 DEFS( OWNER_COMPASS_CS
, PG_COMPASS_CONFIG
, compassConfig_t
, mag_spi_csn
),
4932 #ifdef USE_MAG_DATA_READY_SIGNAL
4933 DEFS( OWNER_COMPASS_EXTI
, PG_COMPASS_CONFIG
, compassConfig_t
, interruptTag
),
4936 #ifdef USE_SDCARD_SPI
4937 DEFS( OWNER_SDCARD_CS
, PG_SDCARD_CONFIG
, sdcardConfig_t
, chipSelectTag
),
4940 DEFS( OWNER_SDCARD_DETECT
, PG_SDCARD_CONFIG
, sdcardConfig_t
, cardDetectTag
),
4942 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
4943 DEFS( OWNER_SDIO_CK
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, CKPin
),
4944 DEFS( OWNER_SDIO_CMD
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, CMDPin
),
4945 DEFS( OWNER_SDIO_D0
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D0Pin
),
4946 DEFS( OWNER_SDIO_D1
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D1Pin
),
4947 DEFS( OWNER_SDIO_D2
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D2Pin
),
4948 DEFS( OWNER_SDIO_D3
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D3Pin
),
4951 DEFA( OWNER_PINIO
, PG_PINIO_CONFIG
, pinioConfig_t
, ioTag
, PINIO_COUNT
),
4953 #if defined(USE_USB_MSC)
4954 DEFS( OWNER_USB_MSC_PIN
, PG_USB_CONFIG
, usbDev_t
, mscButtonPin
),
4956 #ifdef USE_FLASH_CHIP
4957 DEFS( OWNER_FLASH_CS
, PG_FLASH_CONFIG
, flashConfig_t
, csTag
),
4960 DEFS( OWNER_OSD_CS
, PG_MAX7456_CONFIG
, max7456Config_t
, csTag
),
4963 DEFS( OWNER_RX_SPI_CS
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, csnTag
),
4964 DEFS( OWNER_RX_SPI_EXTI
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, extiIoTag
),
4965 DEFS( OWNER_RX_SPI_BIND
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, bindIoTag
),
4966 DEFS( OWNER_RX_SPI_LED
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, ledIoTag
),
4967 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
4968 DEFS( OWNER_RX_SPI_CC2500_TX_EN
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, txEnIoTag
),
4969 DEFS( OWNER_RX_SPI_CC2500_LNA_EN
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, lnaEnIoTag
),
4970 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
4971 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, antSelIoTag
),
4975 #ifdef USE_GYRO_EXTI
4976 DEFW( OWNER_GYRO_EXTI
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, extiTag
, MAX_GYRODEV_COUNT
),
4978 DEFW( OWNER_GYRO_CS
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, csnTag
, MAX_GYRODEV_COUNT
),
4979 #ifdef USE_USB_DETECT
4980 DEFS( OWNER_USB_DETECT
, PG_USB_CONFIG
, usbDev_t
, detectPin
),
4982 #ifdef USE_VTX_RTC6705
4983 DEFS( OWNER_VTX_POWER
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, powerTag
),
4984 DEFS( OWNER_VTX_CS
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, csTag
),
4985 DEFS( OWNER_VTX_DATA
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, dataTag
),
4986 DEFS( OWNER_VTX_CLK
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, clockTag
),
4988 #ifdef USE_PIN_PULL_UP_DOWN
4989 DEFA( OWNER_PULLUP
, PG_PULLUP_CONFIG
, pinPullUpDownConfig_t
, ioTag
, PIN_PULL_UP_DOWN_COUNT
),
4990 DEFA( OWNER_PULLDOWN
, PG_PULLDOWN_CONFIG
, pinPullUpDownConfig_t
, ioTag
, PIN_PULL_UP_DOWN_COUNT
),
4998 static ioTag_t
*getIoTag(const cliResourceValue_t value
, uint8_t index
)
5000 const pgRegistry_t
* rec
= pgFind(value
.pgn
);
5001 return CONST_CAST(ioTag_t
*, rec
->address
+ value
.stride
* index
+ value
.offset
);
5004 static void printResource(dumpFlags_t dumpMask
, const char *headingStr
)
5006 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5007 for (unsigned int i
= 0; i
< ARRAYLEN(resourceTable
); i
++) {
5008 const char* owner
= ownerNames
[resourceTable
[i
].owner
];
5009 const pgRegistry_t
* pg
= pgFind(resourceTable
[i
].pgn
);
5010 const void *currentConfig
;
5011 const void *defaultConfig
;
5012 if (isReadingConfigFromCopy()) {
5013 currentConfig
= pg
->copy
;
5014 defaultConfig
= pg
->address
;
5016 currentConfig
= pg
->address
;
5017 defaultConfig
= NULL
;
5020 for (int index
= 0; index
< MAX_RESOURCE_INDEX(resourceTable
[i
].maxIndex
); index
++) {
5021 const ioTag_t ioTag
= *(ioTag_t
*)((const uint8_t *)currentConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
5022 ioTag_t ioTagDefault
= NULL
;
5023 if (defaultConfig
) {
5024 ioTagDefault
= *(ioTag_t
*)((const uint8_t *)defaultConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
5027 const bool equalsDefault
= ioTag
== ioTagDefault
;
5028 const char *format
= "resource %s %d %c%02d";
5029 const char *formatUnassigned
= "resource %s %d NONE";
5030 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5032 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTagDefault
) + 'A', IO_GPIOPinIdxByTag(ioTagDefault
));
5033 } else if (defaultConfig
) {
5034 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
5037 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
));
5038 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5039 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
5045 static void printResourceOwner(uint8_t owner
, uint8_t index
)
5047 cliPrintf("%s", ownerNames
[resourceTable
[owner
].owner
]);
5049 if (resourceTable
[owner
].maxIndex
> 0) {
5050 cliPrintf(" %d", RESOURCE_INDEX(index
));
5054 static void resourceCheck(uint8_t resourceIndex
, uint8_t index
, ioTag_t newTag
)
5060 const char * format
= "\r\nNOTE: %c%02d already assigned to ";
5061 for (int r
= 0; r
< (int)ARRAYLEN(resourceTable
); r
++) {
5062 for (int i
= 0; i
< MAX_RESOURCE_INDEX(resourceTable
[r
].maxIndex
); i
++) {
5063 ioTag_t
*tag
= getIoTag(resourceTable
[r
], i
);
5064 if (*tag
== newTag
) {
5065 bool cleared
= false;
5066 if (r
== resourceIndex
) {
5074 cliPrintf(format
, DEFIO_TAG_GPIOID(newTag
) + 'A', DEFIO_TAG_PIN(newTag
));
5076 printResourceOwner(r
, i
);
5080 printResourceOwner(r
, i
);
5081 cliPrintf(" disabled");
5090 static bool strToPin(char *pch
, ioTag_t
*tag
)
5092 if (strcasecmp(pch
, "NONE") == 0) {
5097 unsigned port
= (*pch
>= 'a') ? *pch
- 'a' : *pch
- 'A';
5103 *tag
= DEFIO_TAG_MAKE(port
, pin
);
5112 static void showDma(void)
5117 cliPrintLine("DMA:");
5119 cliPrintLine("Currently active DMA:");
5122 for (int i
= 1; i
<= DMA_LAST_HANDLER
; i
++) {
5123 const resourceOwner_t
*owner
= dmaGetOwner(i
);
5125 cliPrintf(DMA_OUTPUT_STRING
, DMA_DEVICE_NO(i
), DMA_DEVICE_INDEX(i
));
5126 if (owner
->resourceIndex
> 0) {
5127 cliPrintLinef(" %s %d", ownerNames
[owner
->owner
], owner
->resourceIndex
);
5129 cliPrintLinef(" %s", ownerNames
[owner
->owner
]);
5137 typedef struct dmaoptEntry_s
{
5139 dmaPeripheral_e peripheral
;
5144 uint32_t presenceMask
;
5147 #define MASK_IGNORED (0)
5149 // Handy macros for keeping the table tidy.
5150 // DEFS : Single entry
5151 // DEFA : Array of uint8_t (stride = 1)
5152 // DEFW : Wider stride case; array of structs.
5154 #define DEFS(device, peripheral, pgn, type, member) \
5155 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5157 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5158 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5160 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5161 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
5163 dmaoptEntry_t dmaoptEntryTable
[] = {
5164 DEFW("SPI_TX", DMA_PERIPH_SPI_TX
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, txDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5165 DEFW("SPI_RX", DMA_PERIPH_SPI_RX
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, rxDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5166 DEFA("ADC", DMA_PERIPH_ADC
, PG_ADC_CONFIG
, adcConfig_t
, dmaopt
, ADCDEV_COUNT
, MASK_IGNORED
),
5167 DEFS("SDIO", DMA_PERIPH_SDIO
, PG_SDIO_CONFIG
, sdioConfig_t
, dmaopt
),
5168 DEFW("UART_TX", DMA_PERIPH_UART_TX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, txDmaopt
, UARTDEV_CONFIG_MAX
, MASK_IGNORED
),
5169 DEFW("UART_RX", DMA_PERIPH_UART_RX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, rxDmaopt
, UARTDEV_CONFIG_MAX
, MASK_IGNORED
),
5170 #if defined(STM32H7) || defined(STM32G4)
5171 DEFW("TIMUP", DMA_PERIPH_TIMUP
, PG_TIMER_UP_CONFIG
, timerUpConfig_t
, dmaopt
, HARDWARE_TIMER_DEFINITION_COUNT
, TIMUP_TIMERS
),
5179 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5180 #define DMA_OPT_STRING_BUFSIZE 5
5182 #if defined(STM32H7) || defined(STM32G4)
5183 #define DMA_CHANREQ_STRING "Request"
5185 #define DMA_CHANREQ_STRING "Channel"
5188 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
5189 #define DMA_STCH_STRING "Stream"
5191 #define DMA_STCH_STRING "Channel"
5194 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5196 static void optToString(int optval
, char *buf
)
5198 if (optval
== DMA_OPT_UNUSED
) {
5199 memcpy(buf
, "NONE", DMA_OPT_STRING_BUFSIZE
);
5201 tfp_sprintf(buf
, "%d", optval
);
5205 static void printPeripheralDmaoptDetails(dmaoptEntry_t
*entry
, int index
, const dmaoptValue_t dmaopt
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5207 if (dmaopt
!= DMA_OPT_UNUSED
) {
5208 printValue(dumpMask
, equalsDefault
,
5210 entry
->device
, DMA_OPT_UI_INDEX(index
), dmaopt
);
5212 const dmaChannelSpec_t
*dmaChannelSpec
= dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, dmaopt
);
5213 dmaCode_t dmaCode
= 0;
5214 if (dmaChannelSpec
) {
5215 dmaCode
= dmaChannelSpec
->code
;
5217 printValue(dumpMask
, equalsDefault
,
5218 "# %s %d: " DMASPEC_FORMAT_STRING
,
5219 entry
->device
, DMA_OPT_UI_INDEX(index
), DMA_CODE_CONTROLLER(dmaCode
), DMA_CODE_STREAM(dmaCode
), DMA_CODE_CHANNEL(dmaCode
));
5220 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5221 printValue(dumpMask
, equalsDefault
,
5223 entry
->device
, DMA_OPT_UI_INDEX(index
));
5227 static const char *printPeripheralDmaopt(dmaoptEntry_t
*entry
, int index
, dumpFlags_t dumpMask
, const char *headingStr
)
5229 const pgRegistry_t
* pg
= pgFind(entry
->pgn
);
5230 const void *currentConfig
;
5231 const void *defaultConfig
;
5233 if (isReadingConfigFromCopy()) {
5234 currentConfig
= pg
->copy
;
5235 defaultConfig
= pg
->address
;
5237 currentConfig
= pg
->address
;
5238 defaultConfig
= NULL
;
5241 dmaoptValue_t currentOpt
= *(dmaoptValue_t
*)((uint8_t *)currentConfig
+ entry
->stride
* index
+ entry
->offset
);
5242 dmaoptValue_t defaultOpt
;
5244 if (defaultConfig
) {
5245 defaultOpt
= *(dmaoptValue_t
*)((uint8_t *)defaultConfig
+ entry
->stride
* index
+ entry
->offset
);
5247 defaultOpt
= DMA_OPT_UNUSED
;
5250 bool equalsDefault
= currentOpt
== defaultOpt
;
5251 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5253 if (defaultConfig
) {
5254 printPeripheralDmaoptDetails(entry
, index
, defaultOpt
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5257 printPeripheralDmaoptDetails(entry
, index
, currentOpt
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5261 #if defined(USE_TIMER_MGMT)
5262 static void printTimerDmaoptDetails(const ioTag_t ioTag
, const timerHardware_t
*timer
, const dmaoptValue_t dmaopt
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5264 const char *format
= "dma pin %c%02d %d";
5266 if (dmaopt
!= DMA_OPT_UNUSED
) {
5267 const bool printDetails
= printValue(dumpMask
, equalsDefault
, format
,
5268 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5273 const dmaChannelSpec_t
*dmaChannelSpec
= dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, dmaopt
);
5274 dmaCode_t dmaCode
= 0;
5275 if (dmaChannelSpec
) {
5276 dmaCode
= dmaChannelSpec
->code
;
5277 printValue(dumpMask
, false,
5278 "# pin %c%02d: " DMASPEC_FORMAT_STRING
,
5279 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5280 DMA_CODE_CONTROLLER(dmaCode
), DMA_CODE_STREAM(dmaCode
), DMA_CODE_CHANNEL(dmaCode
)
5284 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5285 printValue(dumpMask
, equalsDefault
,
5286 "dma pin %c%02d NONE",
5287 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
)
5292 static const char *printTimerDmaopt(const timerIOConfig_t
*currentConfig
, const timerIOConfig_t
*defaultConfig
, unsigned index
, dumpFlags_t dumpMask
, bool tagsInUse
[], const char *headingStr
)
5294 const ioTag_t ioTag
= currentConfig
[index
].ioTag
;
5300 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, currentConfig
[index
].index
);
5301 const dmaoptValue_t dmaopt
= currentConfig
[index
].dmaopt
;
5303 dmaoptValue_t defaultDmaopt
= DMA_OPT_UNUSED
;
5304 bool equalsDefault
= defaultDmaopt
== dmaopt
;
5305 if (defaultConfig
) {
5306 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5307 if (defaultConfig
[i
].ioTag
== ioTag
) {
5308 defaultDmaopt
= defaultConfig
[i
].dmaopt
;
5310 // We need to check timer as well here to get 'default' DMA options for non-default timers printed, because setting the timer resets the DMA option.
5311 equalsDefault
= (defaultDmaopt
== dmaopt
) && (defaultConfig
[i
].index
== currentConfig
[index
].index
|| dmaopt
== DMA_OPT_UNUSED
);
5313 tagsInUse
[index
] = true;
5320 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5322 if (defaultConfig
) {
5323 printTimerDmaoptDetails(ioTag
, timer
, defaultDmaopt
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5326 printTimerDmaoptDetails(ioTag
, timer
, dmaopt
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5331 static void printDmaopt(dumpFlags_t dumpMask
, const char *headingStr
)
5333 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5334 for (size_t i
= 0; i
< ARRAYLEN(dmaoptEntryTable
); i
++) {
5335 dmaoptEntry_t
*entry
= &dmaoptEntryTable
[i
];
5336 for (int index
= 0; index
< entry
->maxIndex
; index
++) {
5337 headingStr
= printPeripheralDmaopt(entry
, index
, dumpMask
, headingStr
);
5341 #if defined(USE_TIMER_MGMT)
5342 const pgRegistry_t
* pg
= pgFind(PG_TIMER_IO_CONFIG
);
5343 const timerIOConfig_t
*currentConfig
;
5344 const timerIOConfig_t
*defaultConfig
;
5346 if (isReadingConfigFromCopy()) {
5347 currentConfig
= (timerIOConfig_t
*)pg
->copy
;
5348 defaultConfig
= (timerIOConfig_t
*)pg
->address
;
5350 currentConfig
= (timerIOConfig_t
*)pg
->address
;
5351 defaultConfig
= NULL
;
5354 bool tagsInUse
[MAX_TIMER_PINMAP_COUNT
] = { false };
5355 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5356 headingStr
= printTimerDmaopt(currentConfig
, defaultConfig
, i
, dumpMask
, tagsInUse
, headingStr
);
5359 if (defaultConfig
) {
5360 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5361 if (!tagsInUse
[i
] && defaultConfig
[i
].ioTag
&& defaultConfig
[i
].dmaopt
!= DMA_OPT_UNUSED
) {
5362 const timerHardware_t
*timer
= timerGetByTagAndIndex(defaultConfig
[i
].ioTag
, defaultConfig
[i
].index
);
5363 headingStr
= cliPrintSectionHeading(dumpMask
, true, headingStr
);
5364 printTimerDmaoptDetails(defaultConfig
[i
].ioTag
, timer
, defaultConfig
[i
].dmaopt
, false, dumpMask
, cliDefaultPrintLinef
);
5366 printTimerDmaoptDetails(defaultConfig
[i
].ioTag
, timer
, DMA_OPT_UNUSED
, false, dumpMask
, cliDumpPrintLinef
);
5373 static void cliDmaopt(char *cmdline
)
5378 // Peripheral name or command option
5379 pch
= strtok_r(cmdline
, " ", &saveptr
);
5381 printDmaopt(DUMP_MASTER
| HIDE_UNUSED
, NULL
);
5384 } else if (strcasecmp(pch
, "list") == 0) {
5385 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5390 dmaoptEntry_t
*entry
= NULL
;
5391 for (unsigned i
= 0; i
< ARRAYLEN(dmaoptEntryTable
); i
++) {
5392 if (strcasecmp(pch
, dmaoptEntryTable
[i
].device
) == 0) {
5393 entry
= &dmaoptEntryTable
[i
];
5397 if (!entry
&& strcasecmp(pch
, "pin") != 0) {
5398 cliPrintErrorLinef("BAD DEVICE: %s", pch
);
5403 dmaoptValue_t orgval
= DMA_OPT_UNUSED
;
5406 dmaoptValue_t
*optaddr
= NULL
;
5408 ioTag_t ioTag
= IO_TAG_NONE
;
5409 #if defined(USE_TIMER_MGMT)
5410 timerIOConfig_t
*timerIoConfig
= NULL
;
5412 const timerHardware_t
*timer
= NULL
;
5413 pch
= strtok_r(NULL
, " ", &saveptr
);
5415 index
= atoi(pch
) - 1;
5416 if (index
< 0 || index
>= entry
->maxIndex
|| (entry
->presenceMask
!= MASK_IGNORED
&& !(entry
->presenceMask
& BIT(index
+ 1)))) {
5417 cliPrintErrorLinef("BAD INDEX: '%s'", pch
? pch
: "");
5421 const pgRegistry_t
* pg
= pgFind(entry
->pgn
);
5422 const void *currentConfig
;
5423 if (isWritingConfigToCopy()) {
5424 currentConfig
= pg
->copy
;
5426 currentConfig
= pg
->address
;
5428 optaddr
= (dmaoptValue_t
*)((uint8_t *)currentConfig
+ entry
->stride
* index
+ entry
->offset
);
5432 if (!pch
|| !(strToPin(pch
, &ioTag
) && IOGetByTag(ioTag
))) {
5433 cliPrintErrorLinef("INVALID PIN: '%s'", pch
? pch
: "");
5438 orgval
= dmaoptByTag(ioTag
);
5439 #if defined(USE_TIMER_MGMT)
5440 timerIoConfig
= timerIoConfigByTag(ioTag
);
5442 timer
= timerGetByTag(ioTag
);
5446 pch
= strtok_r(NULL
, " ", &saveptr
);
5449 printPeripheralDmaoptDetails(entry
, index
, *optaddr
, true, DUMP_MASTER
, cliDumpPrintLinef
);
5451 #if defined(USE_TIMER_MGMT)
5453 printTimerDmaoptDetails(ioTag
, timer
, orgval
, true, DUMP_MASTER
, cliDumpPrintLinef
);
5458 } else if (strcasecmp(pch
, "list") == 0) {
5459 // Show possible opts
5460 const dmaChannelSpec_t
*dmaChannelSpec
;
5462 for (int opt
= 0; (dmaChannelSpec
= dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, opt
)); opt
++) {
5463 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING
, opt
, DMA_CODE_CONTROLLER(dmaChannelSpec
->code
), DMA_CODE_STREAM(dmaChannelSpec
->code
), DMA_CODE_CHANNEL(dmaChannelSpec
->code
));
5466 for (int opt
= 0; (dmaChannelSpec
= dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, opt
)); opt
++) {
5467 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING
, opt
, DMA_CODE_CONTROLLER(dmaChannelSpec
->code
), DMA_CODE_STREAM(dmaChannelSpec
->code
), DMA_CODE_CHANNEL(dmaChannelSpec
->code
));
5474 if (strcasecmp(pch
, "none") == 0) {
5475 optval
= DMA_OPT_UNUSED
;
5480 if (!dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, optval
)) {
5481 cliPrintErrorLinef("INVALID DMA OPTION FOR %s %d: '%s'", entry
->device
, DMA_OPT_UI_INDEX(index
), pch
);
5486 if (!dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, optval
)) {
5487 cliPrintErrorLinef("INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
5494 char optvalString
[DMA_OPT_STRING_BUFSIZE
];
5495 optToString(optval
, optvalString
);
5497 char orgvalString
[DMA_OPT_STRING_BUFSIZE
];
5498 optToString(orgval
, orgvalString
);
5500 if (optval
!= orgval
) {
5504 cliPrintLinef("# dma %s %d: changed from %s to %s", entry
->device
, DMA_OPT_UI_INDEX(index
), orgvalString
, optvalString
);
5506 #if defined(USE_TIMER_MGMT)
5507 timerIoConfig
->dmaopt
= optval
;
5510 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
, optvalString
);
5514 cliPrintLinef("# dma %s %d: no change: %s", entry
->device
, DMA_OPT_UI_INDEX(index
), orgvalString
);
5516 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),orgvalString
);
5521 #endif // USE_DMA_SPEC
5524 static void cliDma(char* cmdline
)
5526 int len
= strlen(cmdline
);
5527 if (len
&& strncasecmp(cmdline
, "show", len
) == 0) {
5533 #if defined(USE_DMA_SPEC)
5536 cliShowParseError();
5540 #endif // USE_RESOURCE_MGMT
5542 #ifdef USE_TIMER_MGMT
5543 static void printTimerDetails(const ioTag_t ioTag
, const unsigned timerIndex
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5545 const char *format
= "timer %c%02d AF%d";
5546 const char *emptyFormat
= "timer %c%02d NONE";
5548 if (timerIndex
> 0) {
5549 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, timerIndex
);
5550 const bool printDetails
= printValue(dumpMask
, equalsDefault
, format
,
5551 IO_GPIOPortIdxByTag(ioTag
) + 'A',
5552 IO_GPIOPinIdxByTag(ioTag
),
5553 timer
->alternateFunction
5556 printValue(dumpMask
, false,
5557 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5558 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5559 timerGetTIMNumber(timer
->tim
),
5560 CC_INDEX_FROM_CHANNEL(timer
->channel
) + 1,
5561 timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : "",
5562 timer
->alternateFunction
5566 printValue(dumpMask
, equalsDefault
, emptyFormat
,
5567 IO_GPIOPortIdxByTag(ioTag
) + 'A',
5568 IO_GPIOPinIdxByTag(ioTag
)
5573 static void printTimer(dumpFlags_t dumpMask
, const char *headingStr
)
5575 const pgRegistry_t
* pg
= pgFind(PG_TIMER_IO_CONFIG
);
5576 const timerIOConfig_t
*currentConfig
;
5577 const timerIOConfig_t
*defaultConfig
;
5579 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5580 if (isReadingConfigFromCopy()) {
5581 currentConfig
= (timerIOConfig_t
*)pg
->copy
;
5582 defaultConfig
= (timerIOConfig_t
*)pg
->address
;
5584 currentConfig
= (timerIOConfig_t
*)pg
->address
;
5585 defaultConfig
= NULL
;
5588 bool tagsInUse
[MAX_TIMER_PINMAP_COUNT
] = { false };
5589 for (unsigned int i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5590 const ioTag_t ioTag
= currentConfig
[i
].ioTag
;
5596 const uint8_t timerIndex
= currentConfig
[i
].index
;
5598 uint8_t defaultTimerIndex
= 0;
5599 if (defaultConfig
) {
5600 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5601 if (defaultConfig
[i
].ioTag
== ioTag
) {
5602 defaultTimerIndex
= defaultConfig
[i
].index
;
5603 tagsInUse
[i
] = true;
5610 const bool equalsDefault
= defaultTimerIndex
== timerIndex
;
5611 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5612 if (defaultConfig
&& defaultTimerIndex
) {
5613 printTimerDetails(ioTag
, defaultTimerIndex
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5616 printTimerDetails(ioTag
, timerIndex
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5619 if (defaultConfig
) {
5620 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5621 if (!tagsInUse
[i
] && defaultConfig
[i
].ioTag
) {
5622 headingStr
= cliPrintSectionHeading(DO_DIFF
, true, headingStr
);
5623 printTimerDetails(defaultConfig
[i
].ioTag
, defaultConfig
[i
].index
, false, dumpMask
, cliDefaultPrintLinef
);
5625 printTimerDetails(defaultConfig
[i
].ioTag
, 0, false, dumpMask
, cliDumpPrintLinef
);
5631 #define TIMER_INDEX_UNDEFINED -1
5632 #define TIMER_AF_STRING_BUFSIZE 5
5634 static void alternateFunctionToString(const ioTag_t ioTag
, const int index
, char *buf
)
5636 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, index
+ 1);
5638 memcpy(buf
, "NONE", TIMER_AF_STRING_BUFSIZE
);
5640 tfp_sprintf(buf
, "AF%d", timer
->alternateFunction
);
5644 static void showTimers(void)
5649 cliPrintLine("Timers:");
5651 cliPrintLine("Currently active Timers:");
5656 for (int i
= 0; (timerNumber
= timerGetNumberByIndex(i
)); i
++) {
5657 cliPrintf("TIM%d:", timerNumber
);
5658 bool timerUsed
= false;
5659 for (unsigned timerIndex
= 0; timerIndex
< CC_CHANNELS_PER_TIMER
; timerIndex
++) {
5660 const resourceOwner_t
*timerOwner
= timerGetOwner(timerNumber
, CC_CHANNEL_FROM_INDEX(timerIndex
));
5661 if (timerOwner
->owner
) {
5668 if (timerOwner
->resourceIndex
> 0) {
5669 cliPrintLinef(" CH%d: %s %d", timerIndex
+ 1, ownerNames
[timerOwner
->owner
], timerOwner
->resourceIndex
);
5671 cliPrintLinef(" CH%d: %s", timerIndex
+ 1, ownerNames
[timerOwner
->owner
]);
5677 cliPrintLine(" FREE");
5682 static void cliTimer(char *cmdline
)
5684 int len
= strlen(cmdline
);
5687 printTimer(DUMP_MASTER
, NULL
);
5690 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
5691 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5694 } else if (strncasecmp(cmdline
, "show", len
) == 0) {
5703 ioTag_t ioTag
= IO_TAG_NONE
;
5704 pch
= strtok_r(cmdline
, " ", &saveptr
);
5705 if (!pch
|| !strToPin(pch
, &ioTag
)) {
5706 cliShowParseError();
5709 } else if (!IOGetByTag(ioTag
)) {
5710 cliPrintErrorLinef("PIN NOT USED ON BOARD.");
5715 int timerIOIndex
= TIMER_INDEX_UNDEFINED
;
5716 bool isExistingTimerOpt
= false;
5717 /* find existing entry, or go for next available */
5718 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5719 if (timerIOConfig(i
)->ioTag
== ioTag
) {
5721 isExistingTimerOpt
= true;
5726 /* first available empty slot */
5727 if (timerIOIndex
< 0 && timerIOConfig(i
)->ioTag
== IO_TAG_NONE
) {
5732 if (timerIOIndex
< 0) {
5733 cliPrintErrorLinef("PIN TIMER MAP FULL.");
5738 pch
= strtok_r(NULL
, " ", &saveptr
);
5740 int timerIndex
= TIMER_INDEX_UNDEFINED
;
5741 if (strcasecmp(pch
, "list") == 0) {
5742 /* output the list of available options */
5743 const timerHardware_t
*timer
;
5744 for (unsigned index
= 0; (timer
= timerGetByTagAndIndex(ioTag
, index
+ 1)); index
++) {
5745 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5746 timer
->alternateFunction
,
5747 timerGetTIMNumber(timer
->tim
),
5748 CC_INDEX_FROM_CHANNEL(timer
->channel
) + 1,
5749 timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : ""
5754 } else if (strncasecmp(pch
, "af", 2) == 0) {
5755 unsigned alternateFunction
= atoi(&pch
[2]);
5757 const timerHardware_t
*timer
;
5758 for (unsigned index
= 0; (timer
= timerGetByTagAndIndex(ioTag
, index
+ 1)); index
++) {
5759 if (timer
->alternateFunction
== alternateFunction
) {
5767 cliPrintErrorLinef("INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
5771 } else if (strcasecmp(pch
, "none") != 0) {
5772 cliPrintErrorLinef("INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
5777 int oldTimerIndex
= isExistingTimerOpt
? timerIOConfig(timerIOIndex
)->index
- 1 : -1;
5778 timerIOConfigMutable(timerIOIndex
)->ioTag
= timerIndex
== TIMER_INDEX_UNDEFINED
? IO_TAG_NONE
: ioTag
;
5779 timerIOConfigMutable(timerIOIndex
)->index
= timerIndex
+ 1;
5780 timerIOConfigMutable(timerIOIndex
)->dmaopt
= DMA_OPT_UNUSED
;
5782 char optvalString
[DMA_OPT_STRING_BUFSIZE
];
5783 alternateFunctionToString(ioTag
, timerIndex
, optvalString
);
5785 char orgvalString
[DMA_OPT_STRING_BUFSIZE
];
5786 alternateFunctionToString(ioTag
, oldTimerIndex
, orgvalString
);
5788 if (timerIndex
== oldTimerIndex
) {
5789 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
);
5791 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
, optvalString
);
5796 printTimerDetails(ioTag
, timerIOConfig(timerIOIndex
)->index
, false, DUMP_MASTER
, cliDumpPrintLinef
);
5803 #if defined(USE_RESOURCE_MGMT)
5804 static void cliResource(char *cmdline
)
5809 pch
= strtok_r(cmdline
, " ", &saveptr
);
5811 printResource(DUMP_MASTER
| HIDE_UNUSED
, NULL
);
5814 } else if (strcasecmp(pch
, "show") == 0) {
5818 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
5821 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
5823 owner
= ownerNames
[ioRecs
[i
].owner
];
5825 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
);
5826 if (ioRecs
[i
].index
> 0) {
5827 cliPrintf(" %d", ioRecs
[i
].index
);
5832 pch
= strtok_r(NULL
, " ", &saveptr
);
5833 if (strcasecmp(pch
, "all") == 0) {
5834 #if defined(USE_TIMER_MGMT)
5837 #if defined(USE_DMA)
5845 unsigned resourceIndex
= 0;
5846 for (; ; resourceIndex
++) {
5847 if (resourceIndex
>= ARRAYLEN(resourceTable
)) {
5848 cliPrintErrorLinef("INVALID RESOURCE NAME: '%s'", pch
);
5852 const char *resourceName
= ownerNames
[resourceTable
[resourceIndex
].owner
];
5853 if (strncasecmp(pch
, resourceName
, strlen(resourceName
)) == 0) {
5858 pch
= strtok_r(NULL
, " ", &saveptr
);
5859 int index
= atoi(pch
);
5861 if (resourceTable
[resourceIndex
].maxIndex
> 0 || index
> 0) {
5862 if (index
<= 0 || index
> MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
)) {
5863 cliShowArgumentRangeError("INDEX", 1, MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
));
5868 pch
= strtok_r(NULL
, " ", &saveptr
);
5871 ioTag_t
*tag
= getIoTag(resourceTable
[resourceIndex
], index
);
5873 if (strlen(pch
) > 0) {
5874 if (strToPin(pch
, tag
)) {
5875 if (*tag
== IO_TAG_NONE
) {
5877 cliPrintLine("Freed");
5879 cliPrintLine("Resource is freed");
5883 ioRec_t
*rec
= IO_Rec(IOGetByTag(*tag
));
5885 resourceCheck(resourceIndex
, index
, *tag
);
5887 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
5889 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
5892 cliShowParseError();
5899 cliShowParseError();
5903 #ifdef USE_DSHOT_TELEMETRY
5904 static void cliDshotTelemetryInfo(char *cmdline
)
5908 if (useDshotTelemetry
) {
5909 cliPrintLinef("Dshot reads: %u", dshotTelemetryState
.readCount
);
5910 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState
.invalidPacketCount
);
5911 uint32_t directionChangeCycles
= dshotDMAHandlerCycleCounters
.changeDirectionCompletedAt
- dshotDMAHandlerCycleCounters
.irqAt
;
5912 uint32_t directionChangeDurationUs
= clockCyclesToMicros(directionChangeCycles
);
5913 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles
, directionChangeDurationUs
);
5916 #ifdef USE_DSHOT_TELEMETRY_STATS
5917 cliPrintLine("Motor eRPM RPM Hz Invalid");
5918 cliPrintLine("===== ======= ====== ===== =======");
5920 cliPrintLine("Motor eRPM RPM Hz");
5921 cliPrintLine("===== ======= ====== =====");
5923 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
5924 cliPrintf("%5d %7d %6d %5d ", i
,
5925 (int)getDshotTelemetry(i
) * 100,
5926 (int)getDshotTelemetry(i
) * 100 * 2 / motorConfig()->motorPoleCount
,
5927 (int)getDshotTelemetry(i
) * 100 * 2 / motorConfig()->motorPoleCount
/ 60);
5928 #ifdef USE_DSHOT_TELEMETRY_STATS
5929 if (isDshotMotorTelemetryActive(i
)) {
5930 const int calcPercent
= getDshotTelemetryMotorInvalidPercent(i
);
5931 cliPrintLinef("%3d.%02d%%", calcPercent
/ 100, calcPercent
% 100);
5933 cliPrintLine("NO DATA");
5941 const int len
= MAX_GCR_EDGES
;
5942 #ifdef DEBUG_BBDECODE
5943 extern uint16_t bbBuffer
[134];
5944 for (int i
= 0; i
< 134; i
++) {
5945 cliPrintf("%u ", (int)bbBuffer
[i
]);
5949 for (int i
= 0; i
< len
; i
++) {
5950 cliPrintf("%u ", (int)dshotTelemetryState
.inputBuffer
[i
]);
5953 for (int i
= 1; i
< len
; i
++) {
5954 cliPrintf("%u ", (int)(dshotTelemetryState
.inputBuffer
[i
] - dshotTelemetryState
.inputBuffer
[i
-1]));
5958 cliPrintLine("Dshot telemetry not enabled");
5963 static void printConfig(char *cmdline
, bool doDiff
)
5965 dumpFlags_t dumpMask
= DUMP_MASTER
;
5967 if ((options
= checkCommand(cmdline
, "master"))) {
5968 dumpMask
= DUMP_MASTER
; // only
5969 } else if ((options
= checkCommand(cmdline
, "profile"))) {
5970 dumpMask
= DUMP_PROFILE
; // only
5971 } else if ((options
= checkCommand(cmdline
, "rates"))) {
5972 dumpMask
= DUMP_RATES
; // only
5973 } else if ((options
= checkCommand(cmdline
, "hardware"))) {
5974 dumpMask
= DUMP_MASTER
| HARDWARE_ONLY
; // Show only hardware related settings (useful to generate unified target configs).
5975 } else if ((options
= checkCommand(cmdline
, "all"))) {
5976 dumpMask
= DUMP_ALL
; // all profiles and rates
5982 dumpMask
= dumpMask
| DO_DIFF
;
5985 if (checkCommand(options
, "defaults")) {
5986 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
5987 } else if (checkCommand(options
, "bare")) {
5988 dumpMask
= dumpMask
| BARE
; // show the diff / dump without extra commands and board specific data
5991 backupAndResetConfigs((dumpMask
& BARE
) == 0);
5993 #ifdef USE_CLI_BATCH
5994 bool batchModeEnabled
= false;
5996 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
5997 cliPrintHashLine("version");
6000 if (!(dumpMask
& BARE
)) {
6001 #ifdef USE_CLI_BATCH
6002 cliPrintHashLine("start the command batch");
6003 cliPrintLine("batch start");
6004 batchModeEnabled
= true;
6007 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
6008 cliPrintHashLine("reset configuration to default settings");
6009 cliPrintLine("defaults nosave");
6013 #if defined(USE_BOARD_INFO)
6015 printBoardName(dumpMask
);
6016 printManufacturerId(dumpMask
);
6019 if ((dumpMask
& DUMP_ALL
) && !(dumpMask
& BARE
)) {
6021 #if defined(USE_SIGNATURE)
6026 if (!(dumpMask
& HARDWARE_ONLY
)) {
6027 printName(dumpMask
, &pilotConfig_Copy
);
6030 #ifdef USE_RESOURCE_MGMT
6031 printResource(dumpMask
, "resources");
6032 #if defined(USE_TIMER_MGMT)
6033 printTimer(dumpMask
, "timer");
6036 printDmaopt(dumpMask
, "dma");
6040 if (!(dumpMask
& HARDWARE_ONLY
)) {
6041 #ifndef USE_QUAD_MIXER_ONLY
6042 const char *mixerHeadingStr
= "mixer";
6043 const bool equalsDefault
= mixerConfig_Copy
.mixerMode
== mixerConfig()->mixerMode
;
6044 mixerHeadingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, mixerHeadingStr
);
6045 const char *formatMixer
= "mixer %s";
6046 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig()->mixerMode
- 1]);
6047 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig_Copy
.mixerMode
- 1]);
6049 cliDumpPrintLinef(dumpMask
, customMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
6051 printMotorMix(dumpMask
, customMotorMixer_CopyArray
, customMotorMixer(0), mixerHeadingStr
);
6054 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0), "servo");
6056 const char *servoMixHeadingStr
= "servo mixer";
6057 if (!(dumpMask
& DO_DIFF
) || customServoMixers(0)->rate
!= 0) {
6058 cliPrintHashLine(servoMixHeadingStr
);
6059 cliPrintLine("smix reset\r\n");
6060 servoMixHeadingStr
= NULL
;
6062 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0), servoMixHeadingStr
);
6066 printFeature(dumpMask
, featureConfig_Copy
.enabledFeatures
, featureConfig()->enabledFeatures
, "feature");
6068 #if defined(USE_BEEPER)
6069 printBeeper(dumpMask
, beeperConfig_Copy
.beeper_off_flags
, beeperConfig()->beeper_off_flags
, "beeper", BEEPER_ALLOWED_MODES
, "beeper");
6071 #if defined(USE_DSHOT)
6072 printBeeper(dumpMask
, beeperConfig_Copy
.dshotBeaconOffFlags
, beeperConfig()->dshotBeaconOffFlags
, "beacon", DSHOT_BEACON_ALLOWED_MODES
, "beacon");
6074 #endif // USE_BEEPER
6076 printMap(dumpMask
, &rxConfig_Copy
, rxConfig(), "map");
6078 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig(), "serial");
6080 #ifdef USE_LED_STRIP_STATUS_MODE
6081 printLed(dumpMask
, ledStripStatusModeConfig_Copy
.ledConfigs
, ledStripStatusModeConfig()->ledConfigs
, "led");
6083 printColor(dumpMask
, ledStripStatusModeConfig_Copy
.colors
, ledStripStatusModeConfig()->colors
, "color");
6085 printModeColor(dumpMask
, &ledStripStatusModeConfig_Copy
, ledStripStatusModeConfig(), "mode_color");
6088 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0), "aux");
6090 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0), "adjrange");
6092 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0), "rxrange");
6094 #ifdef USE_VTX_TABLE
6095 printVtxTable(dumpMask
, &vtxTableConfig_Copy
, vtxTableConfig(), "vtxtable");
6098 #ifdef USE_VTX_CONTROL
6099 printVtx(dumpMask
, &vtxConfig_Copy
, vtxConfig(), "vtx");
6102 printRxFailsafe(dumpMask
, rxFailsafeChannelConfigs_CopyArray
, rxFailsafeChannelConfigs(0), "rxfail");
6105 if (dumpMask
& HARDWARE_ONLY
) {
6106 dumpAllValues(HARDWARE_VALUE
, dumpMask
, "master");
6108 dumpAllValues(MASTER_VALUE
, dumpMask
, "master");
6110 if (dumpMask
& DUMP_ALL
) {
6111 for (uint32_t pidProfileIndex
= 0; pidProfileIndex
< PID_PROFILE_COUNT
; pidProfileIndex
++) {
6112 cliDumpPidProfile(pidProfileIndex
, dumpMask
);
6115 pidProfileIndexToUse
= systemConfig_Copy
.pidProfileIndex
;
6117 if (!(dumpMask
& BARE
)) {
6118 cliPrintHashLine("restore original profile selection");
6123 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
6125 for (uint32_t rateIndex
= 0; rateIndex
< CONTROL_RATE_PROFILE_COUNT
; rateIndex
++) {
6126 cliDumpRateProfile(rateIndex
, dumpMask
);
6129 rateProfileIndexToUse
= systemConfig_Copy
.activeRateProfile
;
6131 if (!(dumpMask
& BARE
)) {
6132 cliPrintHashLine("restore original rateprofile selection");
6136 cliPrintHashLine("save configuration");
6138 #ifdef USE_CLI_BATCH
6139 batchModeEnabled
= false;
6143 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
6145 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
6147 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
6150 } else if (dumpMask
& DUMP_PROFILE
) {
6151 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
6152 } else if (dumpMask
& DUMP_RATES
) {
6153 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
6156 #ifdef USE_CLI_BATCH
6157 if (batchModeEnabled
) {
6158 cliPrintHashLine("end the command batch");
6159 cliPrintLine("batch end");
6163 // restore configs from copies
6167 static void cliDump(char *cmdline
)
6169 printConfig(cmdline
, false);
6172 static void cliDiff(char *cmdline
)
6174 printConfig(cmdline
, true);
6177 #if defined(USE_USB_MSC)
6178 static void cliMsc(char *cmdline
)
6180 if (mscCheckFilesystemReady()) {
6182 int timezoneOffsetMinutes
= timeConfig()->tz_offsetMinutes
;
6183 if (!isEmpty(cmdline
)) {
6184 timezoneOffsetMinutes
= atoi(cmdline
);
6185 if ((timezoneOffsetMinutes
< TIMEZONE_OFFSET_MINUTES_MIN
) || (timezoneOffsetMinutes
> TIMEZONE_OFFSET_MINUTES_MAX
)) {
6186 cliPrintErrorLinef("INVALID TIMEZONE OFFSET");
6191 int timezoneOffsetMinutes
= 0;
6194 cliPrintHashLine("Restarting in mass storage mode");
6195 cliPrint("\r\nRebooting");
6197 waitForSerialPortToFinishTransmitting(cliPort
);
6200 systemResetToMsc(timezoneOffsetMinutes
);
6202 cliPrintHashLine("Storage not present or failed to initialize!");
6211 const char *description
;
6214 void (*func
)(char *cmdline
);
6218 #define CLI_COMMAND_DEF(name, description, args, method) \
6226 #define CLI_COMMAND_DEF(name, description, args, method) \
6233 static void cliHelp(char *cmdline
);
6235 // should be sorted a..z for bsearch()
6236 const clicmd_t cmdTable
[] = {
6237 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange
),
6238 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux
),
6239 #ifdef USE_CLI_BATCH
6240 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch
),
6242 #if defined(USE_BEEPER)
6243 #if defined(USE_DSHOT)
6244 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6245 "\t<->[name]", cliBeacon
),
6247 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6248 "\t<->[name]", cliBeeper
),
6249 #endif // USE_BEEPER
6250 #if defined(USE_RX_BIND)
6251 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL
, cliRxBind
),
6253 #if defined(USE_FLASH_BOOT_LOADER)
6254 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader
),
6256 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader
),
6258 #if defined(USE_BOARD_INFO)
6259 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName
),
6261 #ifdef USE_LED_STRIP_STATUS_MODE
6262 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
6264 #if defined(USE_CUSTOM_DEFAULTS)
6265 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave|bare|show]", cliDefaults
),
6267 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave|show]", cliDefaults
),
6269 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff
),
6270 #ifdef USE_RESOURCE_MGMT
6274 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma
),
6276 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma
),
6281 #ifdef USE_DSHOT_TELEMETRY
6282 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL
, cliDshotTelemetryInfo
),
6285 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg
),
6287 CLI_COMMAND_DEF("dump", "dump configuration",
6288 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump
),
6289 #ifdef USE_ESCSERIAL
6290 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough
),
6292 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
6293 CLI_COMMAND_DEF("feature", "configure features",
6295 "\t<->[name]", cliFeature
),
6297 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
6298 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
6299 #ifdef USE_FLASH_TOOLS
6300 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
6301 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL
, cliFlashVerify
),
6302 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
6305 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
6307 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
6309 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6310 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL
, cliDumpGyroRegisters
),
6312 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp
),
6313 #ifdef USE_LED_STRIP_STATUS_MODE
6314 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
6316 #if defined(USE_BOARD_INFO)
6317 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId
),
6319 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
6320 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL
, cliMcuId
),
6321 #ifndef USE_QUAD_MIXER_ONLY
6322 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer
),
6324 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
6325 #ifdef USE_LED_STRIP_STATUS_MODE
6326 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
6328 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
6331 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc
),
6333 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
6337 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]", cliPlaySound
),
6339 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile
),
6340 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile
),
6341 #ifdef USE_RC_SMOOTHING_FILTER
6342 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL
, cliRcSmoothing
),
6343 #endif // USE_RC_SMOOTHING_FILTER
6344 #ifdef USE_RESOURCE_MGMT
6345 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource
),
6347 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL
, cliRxFailsafe
),
6348 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
6349 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
6351 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
6353 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
6354 #if defined(USE_SERIAL_PASSTHROUGH)
6355 #if defined(USE_PINIO)
6356 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode>1] [none|<dtr pinio>|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough
),
6358 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough
),
6362 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
6364 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
6365 #if defined(USE_SIGNATURE)
6366 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature
),
6369 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6371 "\tload <mixer>\r\n"
6372 "\treverse <servo> <source> r|n", cliServoMix
),
6374 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
6375 #if defined(USE_TASK_STATISTICS)
6376 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
6378 #ifdef USE_TIMER_MGMT
6379 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer
),
6381 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
6382 #ifdef USE_VTX_CONTROL
6384 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL
, cliVtx
),
6386 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx
),
6389 #ifdef USE_VTX_TABLE
6390 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL
, cliVtxInfo
),
6391 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable
),
6395 static void cliHelp(char *cmdline
)
6397 bool anyMatches
= false;
6399 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
6400 bool printEntry
= false;
6401 if (isEmpty(cmdline
)) {
6404 if (strcasestr(cmdTable
[i
].name
, cmdline
)
6406 || strcasestr(cmdTable
[i
].description
, cmdline
)
6415 cliPrint(cmdTable
[i
].name
);
6417 if (cmdTable
[i
].description
) {
6418 cliPrintf(" - %s", cmdTable
[i
].description
);
6420 if (cmdTable
[i
].args
) {
6421 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
6427 if (!isEmpty(cmdline
) && !anyMatches
) {
6428 cliPrintErrorLinef("NO MATCHES FOR '%s'", cmdline
);
6432 static void processCharacter(const char c
)
6434 if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
6438 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
6439 if (processingCustomDefaults
) {
6444 // Strip comment starting with # from line
6445 char *p
= cliBuffer
;
6448 bufferIndex
= (uint32_t)(p
- cliBuffer
);
6451 // Strip trailing whitespace
6452 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
6456 // Process non-empty lines
6457 if (bufferIndex
> 0) {
6458 cliBuffer
[bufferIndex
] = 0; // null terminate
6460 const clicmd_t
*cmd
;
6462 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
6463 if ((options
= checkCommand(cliBuffer
, cmd
->name
))) {
6467 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
)) {
6470 cliPrintError("UNKNOWN COMMAND, TRY 'HELP'");
6475 memset(cliBuffer
, 0, sizeof(cliBuffer
));
6477 // 'exit' will reset this flag, so we don't need to print prompt again
6483 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
6484 if (!bufferIndex
&& c
== ' ')
6485 return; // Ignore leading spaces
6486 cliBuffer
[bufferIndex
++] = c
;
6491 static void processCharacterInteractive(const char c
)
6493 if (c
== '\t' || c
== '?') {
6494 // do tab completion
6495 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
6496 uint32_t i
= bufferIndex
;
6497 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
6498 if (bufferIndex
&& (strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0)) {
6506 if (pstart
) { /* Buffer matches one or more commands */
6507 for (; ; bufferIndex
++) {
6508 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
6510 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
6511 /* Unambiguous -- append a space */
6512 cliBuffer
[bufferIndex
++] = ' ';
6513 cliBuffer
[bufferIndex
] = '\0';
6516 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
6519 if (!bufferIndex
|| pstart
!= pend
) {
6520 /* Print list of ambiguous matches */
6521 cliPrint("\r\033[K");
6522 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
6523 cliPrint(cmd
->name
);
6527 i
= 0; /* Redraw prompt */
6529 for (; i
< bufferIndex
; i
++)
6530 cliWrite(cliBuffer
[i
]);
6531 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
6534 } else if (c
== 12) { // NewPage / CTRL-L
6536 cliPrint("\033[2J\033[1;1H");
6538 } else if (c
== 127) {
6541 cliBuffer
[--bufferIndex
] = 0;
6542 cliPrint("\010 \010");
6545 processCharacter(c
);
6549 void cliProcess(void)
6555 // Flush the buffer to get rid of any MSP data polls sent by configurator after CLI was invoked
6558 while (serialRxBytesWaiting(cliPort
)) {
6559 uint8_t c
= serialRead(cliPort
);
6561 processCharacterInteractive(c
);
6565 #if defined(USE_CUSTOM_DEFAULTS)
6566 static bool cliProcessCustomDefaults(bool quiet
)
6568 char *customDefaultsPtr
= customDefaultsStart
;
6569 if (processingCustomDefaults
|| !isCustomDefaults(customDefaultsPtr
)) {
6573 bufWriter_t
*cliWriterTemp
= NULL
;
6575 #if !defined(DEBUG_CUSTOM_DEFAULTS)
6579 cliWriterTemp
= cliWriter
;
6583 cliErrorWriter
= NULL
;
6586 memcpy(cliBufferTemp
, cliBuffer
, sizeof(cliBuffer
));
6587 uint32_t bufferIndexTemp
= bufferIndex
;
6589 processingCustomDefaults
= true;
6591 while (*customDefaultsPtr
&& *customDefaultsPtr
!= 0xFF && customDefaultsPtr
< customDefaultsEnd
) {
6592 processCharacter(*customDefaultsPtr
++);
6595 // Process a newline at the very end so that the last command gets executed,
6596 // even when the file did not contain a trailing newline
6597 processCharacter('\r');
6599 processingCustomDefaults
= false;
6601 if (cliWriterTemp
) {
6602 cliWriter
= cliWriterTemp
;
6603 cliErrorWriter
= cliWriter
;
6606 memcpy(cliBuffer
, cliBufferTemp
, sizeof(cliBuffer
));
6607 bufferIndex
= bufferIndexTemp
;
6609 systemConfigMutable()->configurationState
= CONFIGURATION_STATE_DEFAULTS_CUSTOM
;
6615 void cliEnter(serialPort_t
*serialPort
)
6618 cliPort
= serialPort
;
6619 setPrintfSerialPort(cliPort
);
6620 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
6621 cliErrorWriter
= cliWriter
;
6623 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics
);
6626 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6628 cliPrintLine("\r\nCLI");
6630 setArmingDisabled(ARMING_DISABLED_CLI
);
6634 #ifdef USE_CLI_BATCH
6635 resetCommandBatch();