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"
60 #include "config/simplified_tuning.h"
62 #include "drivers/accgyro/accgyro.h"
63 #include "drivers/adc.h"
64 #include "drivers/buf_writer.h"
65 #include "drivers/bus_i2c.h"
66 #include "drivers/bus_spi.h"
67 #include "drivers/dma.h"
68 #include "drivers/dma_reqmap.h"
69 #include "drivers/dshot.h"
70 #include "drivers/dshot_command.h"
71 #include "drivers/dshot_dpwm.h"
72 #include "drivers/pwm_output_dshot_shared.h"
73 #include "drivers/camera_control.h"
74 #include "drivers/compass/compass.h"
75 #include "drivers/display.h"
76 #include "drivers/dma.h"
77 #include "drivers/flash.h"
78 #include "drivers/inverter.h"
79 #include "drivers/io.h"
80 #include "drivers/io_impl.h"
81 #include "drivers/light_led.h"
82 #include "drivers/motor.h"
83 #include "drivers/rangefinder/rangefinder_hcsr04.h"
84 #include "drivers/resource.h"
85 #include "drivers/sdcard.h"
86 #include "drivers/sensor.h"
87 #include "drivers/serial.h"
88 #include "drivers/serial_escserial.h"
89 #include "drivers/sound_beeper.h"
90 #include "drivers/stack_check.h"
91 #include "drivers/system.h"
92 #include "drivers/time.h"
93 #include "drivers/timer.h"
94 #include "drivers/transponder_ir.h"
95 #include "drivers/usb_msc.h"
96 #include "drivers/vtx_common.h"
97 #include "drivers/vtx_table.h"
99 #include "fc/board_info.h"
100 #include "fc/controlrate_profile.h"
103 #include "fc/rc_adjustments.h"
104 #include "fc/rc_controls.h"
105 #include "fc/runtime_config.h"
107 #include "flight/failsafe.h"
108 #include "flight/imu.h"
109 #include "flight/mixer.h"
110 #include "flight/pid.h"
111 #include "flight/position.h"
112 #include "flight/servos.h"
114 #include "io/asyncfatfs/asyncfatfs.h"
115 #include "io/beeper.h"
116 #include "io/flashfs.h"
117 #include "io/gimbal.h"
119 #include "io/ledstrip.h"
120 #include "io/serial.h"
121 #include "io/transponder_ir.h"
122 #include "io/usb_msc.h"
123 #include "io/vtx_control.h"
127 #include "msp/msp_box.h"
128 #include "msp/msp_protocol.h"
133 #include "pg/beeper.h"
134 #include "pg/beeper_dev.h"
135 #include "pg/board.h"
136 #include "pg/bus_i2c.h"
137 #include "pg/bus_spi.h"
138 #include "pg/gyrodev.h"
139 #include "pg/max7456.h"
141 #include "pg/motor.h"
142 #include "pg/pinio.h"
143 #include "pg/pin_pull_up_down.h"
145 #include "pg/pg_ids.h"
147 #include "pg/rx_pwm.h"
148 #include "pg/rx_spi_cc2500.h"
149 #include "pg/rx_spi_expresslrs.h"
150 #include "pg/serial_uart.h"
152 #include "pg/timerio.h"
153 #include "pg/timerup.h"
155 #include "pg/vtx_table.h"
157 #include "rx/rx_bind.h"
158 #include "rx/rx_spi.h"
160 #include "scheduler/scheduler.h"
162 #include "sensors/acceleration.h"
163 #include "sensors/adcinternal.h"
164 #include "sensors/barometer.h"
165 #include "sensors/battery.h"
166 #include "sensors/boardalignment.h"
167 #include "sensors/compass.h"
168 #include "sensors/gyro.h"
169 #include "sensors/gyro_init.h"
170 #include "sensors/sensors.h"
172 #include "telemetry/frsky_hub.h"
173 #include "telemetry/telemetry.h"
177 static serialPort_t
*cliPort
= NULL
;
179 // Space required to set array parameters
180 #define CLI_IN_BUFFER_SIZE 256
181 #define CLI_OUT_BUFFER_SIZE 64
183 static bufWriter_t cliWriterDesc
;
184 static bufWriter_t
*cliWriter
= NULL
;
185 static bufWriter_t
*cliErrorWriter
= NULL
;
186 static uint8_t cliWriteBuffer
[CLI_OUT_BUFFER_SIZE
];
188 static char cliBuffer
[CLI_IN_BUFFER_SIZE
];
189 static uint32_t bufferIndex
= 0;
191 static bool configIsInCopy
= false;
193 #define CURRENT_PROFILE_INDEX -1
194 static int8_t pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
195 static int8_t rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
198 static bool commandBatchActive
= false;
199 static bool commandBatchError
= false;
202 #if defined(USE_BOARD_INFO)
203 static bool boardInformationUpdated
= false;
204 #if defined(USE_SIGNATURE)
205 static bool signatureUpdated
= false;
207 #endif // USE_BOARD_INFO
209 static const char* const emptyName
= "-";
210 static const char* const emptyString
= "";
212 #if !defined(USE_CUSTOM_DEFAULTS)
213 #define CUSTOM_DEFAULTS_START ((char*)0)
214 #define CUSTOM_DEFAULTS_END ((char *)0)
216 extern char __custom_defaults_start
;
217 extern char __custom_defaults_end
;
218 #define CUSTOM_DEFAULTS_START (&__custom_defaults_start)
219 #define CUSTOM_DEFAULTS_END (&__custom_defaults_end)
221 static bool processingCustomDefaults
= false;
222 static char cliBufferTemp
[CLI_IN_BUFFER_SIZE
];
224 #define CUSTOM_DEFAULTS_START_PREFIX ("# " FC_FIRMWARE_NAME)
225 #define CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX "# config: manufacturer_id: "
226 #define CUSTOM_DEFAULTS_BOARD_NAME_PREFIX ", board_name: "
227 #define CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX ", version: "
228 #define CUSTOM_DEFAULTS_DATE_PREFIX ", date: "
230 #define MAX_CHANGESET_ID_LENGTH 8
231 #define MAX_DATE_LENGTH 20
233 static bool customDefaultsHeaderParsed
= false;
234 static bool customDefaultsFound
= false;
235 static char customDefaultsManufacturerId
[MAX_MANUFACTURER_ID_LENGTH
+ 1] = { 0 };
236 static char customDefaultsBoardName
[MAX_BOARD_NAME_LENGTH
+ 1] = { 0 };
237 static char customDefaultsChangesetId
[MAX_CHANGESET_ID_LENGTH
+ 1] = { 0 };
238 static char customDefaultsDate
[MAX_DATE_LENGTH
+ 1] = { 0 };
241 #if defined(USE_CUSTOM_DEFAULTS_ADDRESS)
242 static char __attribute__ ((section(".custom_defaults_start_address"))) *customDefaultsStart
= CUSTOM_DEFAULTS_START
;
243 static char __attribute__ ((section(".custom_defaults_end_address"))) *customDefaultsEnd
= CUSTOM_DEFAULTS_END
;
246 #ifndef USE_QUAD_MIXER_ONLY
247 // sync this with mixerMode_e
248 static const char * const mixerNames
[] = {
249 "TRI", "QUADP", "QUADX", "BI",
250 "GIMBAL", "Y6", "HEX6",
251 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
252 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
253 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
254 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
258 // sync this with features_e
259 static const char * const featureNames
[] = {
260 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
261 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
262 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
263 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
264 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
265 "", "", "RX_SPI", "", "ESC_SENSOR", "ANTI_GRAVITY", "", NULL
268 // sync this with rxFailsafeChannelMode_e
269 static const char rxFailsafeModeCharacters
[] = "ahs";
271 static const rxFailsafeChannelMode_e rxFailsafeModesTable
[RX_FAILSAFE_TYPE_COUNT
][RX_FAILSAFE_MODE_COUNT
] = {
272 { RX_FAILSAFE_MODE_AUTO
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
},
273 { RX_FAILSAFE_MODE_INVALID
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
}
276 #if defined(USE_SENSOR_NAMES)
277 // sync this with sensors_e
278 static const char *const sensorTypeNames
[] = {
279 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
282 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
284 static const char * const *sensorHardwareNames
[] = {
285 lookupTableGyroHardware
, lookupTableAccHardware
, lookupTableBaroHardware
, lookupTableMagHardware
, lookupTableRangefinderHardware
287 #endif // USE_SENSOR_NAMES
289 // Needs to be aligned with mcuTypeId_e
290 static const char *mcuTypeNames
[] = {
300 "H743 (Rev Unknown)",
310 static const char *configurationStates
[] = { "UNCONFIGURED", "CUSTOM DEFAULTS", "CONFIGURED" };
312 typedef enum dumpFlags_e
{
313 DUMP_MASTER
= (1 << 0),
314 DUMP_PROFILE
= (1 << 1),
315 DUMP_RATES
= (1 << 2),
318 SHOW_DEFAULTS
= (1 << 5),
319 HIDE_UNUSED
= (1 << 6),
320 HARDWARE_ONLY
= (1 << 7),
324 typedef bool printFn(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...);
327 REBOOT_TARGET_FIRMWARE
,
328 REBOOT_TARGET_BOOTLOADER_ROM
,
329 REBOOT_TARGET_BOOTLOADER_FLASH
,
332 typedef struct serialPassthroughPort_s
{
337 } serialPassthroughPort_t
;
339 static void cliWriterFlushInternal(bufWriter_t
*writer
)
342 bufWriterFlush(writer
);
346 static void cliPrintInternal(bufWriter_t
*writer
, const char *str
)
350 bufWriterAppend(writer
, *str
++);
352 cliWriterFlushInternal(writer
);
356 static void cliWriterFlush(void)
358 cliWriterFlushInternal(cliWriter
);
361 void cliPrint(const char *str
)
363 cliPrintInternal(cliWriter
, str
);
366 void cliPrintLinefeed(void)
371 void cliPrintLine(const char *str
)
378 #define cliPrintHashLine(str)
380 static void cliPrintHashLine(const char *str
)
387 static void cliPutp(void *p
, char ch
)
389 bufWriterAppend(p
, ch
);
392 static void cliPrintfva(const char *format
, va_list va
)
395 tfp_format(cliWriter
, cliPutp
, format
, va
);
400 static bool cliDumpPrintLinef(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...)
402 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
404 va_start(va
, format
);
405 cliPrintfva(format
, va
);
414 static void cliWrite(uint8_t ch
)
417 bufWriterAppend(cliWriter
, ch
);
421 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...)
423 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
427 va_start(va
, format
);
428 cliPrintfva(format
, va
);
437 void cliPrintf(const char *format
, ...)
440 va_start(va
, format
);
441 cliPrintfva(format
, va
);
446 void cliPrintLinef(const char *format
, ...)
449 va_start(va
, format
);
450 cliPrintfva(format
, va
);
455 static void cliPrintErrorVa(const char *cmdName
, const char *format
, va_list va
)
457 if (cliErrorWriter
) {
458 cliPrintInternal(cliErrorWriter
, "###ERROR IN ");
459 cliPrintInternal(cliErrorWriter
, cmdName
);
460 cliPrintInternal(cliErrorWriter
, ": ");
462 tfp_format(cliErrorWriter
, cliPutp
, format
, va
);
465 cliPrintInternal(cliErrorWriter
, "###");
469 if (commandBatchActive
) {
470 commandBatchError
= true;
475 static void cliPrintError(const char *cmdName
, const char *format
, ...)
478 va_start(va
, format
);
479 cliPrintErrorVa(cmdName
, format
, va
);
482 // Supply our own linefeed in case we are printing inside a custom defaults operation
483 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
484 // instead of expecting the directly following command to supply the line feed.
485 cliPrintInternal(cliErrorWriter
, "\r\n");
489 static void cliPrintErrorLinef(const char *cmdName
, const char *format
, ...)
492 va_start(va
, format
);
493 cliPrintErrorVa(cmdName
, format
, va
);
494 cliPrintInternal(cliErrorWriter
, "\r\n");
497 static void getMinMax(const clivalue_t
*var
, int *min
, int *max
)
499 switch (var
->type
& VALUE_TYPE_MASK
) {
502 *min
= var
->config
.minmaxUnsigned
.min
;
503 *max
= var
->config
.minmaxUnsigned
.max
;
507 *min
= var
->config
.minmax
.min
;
508 *max
= var
->config
.minmax
.max
;
514 static void printValuePointer(const char *cmdName
, const clivalue_t
*var
, const void *valuePointer
, bool full
)
516 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
517 for (int i
= 0; i
< var
->config
.array
.length
; i
++) {
518 switch (var
->type
& VALUE_TYPE_MASK
) {
522 cliPrintf("%d", ((uint8_t *)valuePointer
)[i
]);
527 cliPrintf("%d", ((int8_t *)valuePointer
)[i
]);
532 cliPrintf("%d", ((uint16_t *)valuePointer
)[i
]);
537 cliPrintf("%d", ((int16_t *)valuePointer
)[i
]);
542 cliPrintf("%u", ((uint32_t *)valuePointer
)[i
]);
546 if (i
< var
->config
.array
.length
- 1) {
553 switch (var
->type
& VALUE_TYPE_MASK
) {
555 value
= *(uint8_t *)valuePointer
;
559 value
= *(int8_t *)valuePointer
;
563 value
= *(uint16_t *)valuePointer
;
567 value
= *(int16_t *)valuePointer
;
571 value
= *(uint32_t *)valuePointer
;
576 bool valueIsCorrupted
= false;
577 switch (var
->type
& VALUE_MODE_MASK
) {
579 if ((var
->type
& VALUE_TYPE_MASK
) == VAR_UINT32
) {
580 cliPrintf("%u", (uint32_t)value
);
581 if ((uint32_t)value
> var
->config
.u32Max
) {
582 valueIsCorrupted
= true;
584 cliPrintf(" 0 %u", var
->config
.u32Max
);
589 getMinMax(var
, &min
, &max
);
591 cliPrintf("%d", value
);
592 if ((value
< min
) || (value
> max
)) {
593 valueIsCorrupted
= true;
595 cliPrintf(" %d %d", min
, max
);
600 if (value
< lookupTables
[var
->config
.lookup
.tableIndex
].valueCount
) {
601 cliPrint(lookupTables
[var
->config
.lookup
.tableIndex
].values
[value
]);
603 valueIsCorrupted
= true;
607 if (value
& 1 << var
->config
.bitpos
) {
614 cliPrintf("%s", (strlen((char *)valuePointer
) == 0) ? "-" : (char *)valuePointer
);
618 if (valueIsCorrupted
) {
620 cliPrintError(cmdName
, "CORRUPTED CONFIG: %s = %d", var
->name
, value
);
626 static bool valuePtrEqualsDefault(const clivalue_t
*var
, const void *ptr
, const void *ptrDefault
)
629 int elementCount
= 1;
630 uint32_t mask
= 0xffffffff;
632 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
633 elementCount
= var
->config
.array
.length
;
635 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
636 mask
= 1 << var
->config
.bitpos
;
638 for (int i
= 0; i
< elementCount
; i
++) {
639 switch (var
->type
& VALUE_TYPE_MASK
) {
641 result
= result
&& (((uint8_t *)ptr
)[i
] & mask
) == (((uint8_t *)ptrDefault
)[i
] & mask
);
645 result
= result
&& ((int8_t *)ptr
)[i
] == ((int8_t *)ptrDefault
)[i
];
649 result
= result
&& (((uint16_t *)ptr
)[i
] & mask
) == (((uint16_t *)ptrDefault
)[i
] & mask
);
652 result
= result
&& ((int16_t *)ptr
)[i
] == ((int16_t *)ptrDefault
)[i
];
655 result
= result
&& (((uint32_t *)ptr
)[i
] & mask
) == (((uint32_t *)ptrDefault
)[i
] & mask
);
663 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask
, bool outputFlag
, const char *headingStr
)
665 if (headingStr
&& (!(dumpMask
& DO_DIFF
) || outputFlag
)) {
666 cliPrintHashLine(headingStr
);
673 static void backupPgConfig(const pgRegistry_t
*pg
)
675 memcpy(pg
->copy
, pg
->address
, pg
->size
);
678 static void restorePgConfig(const pgRegistry_t
*pg
, uint16_t notToRestoreGroupId
)
680 if (!notToRestoreGroupId
|| pgN(pg
) != notToRestoreGroupId
) {
681 memcpy(pg
->address
, pg
->copy
, pg
->size
);
685 static void backupConfigs(void)
687 if (configIsInCopy
) {
695 configIsInCopy
= true;
698 static void restoreConfigs(uint16_t notToRestoreGroupId
)
700 if (!configIsInCopy
) {
705 restorePgConfig(pg
, notToRestoreGroupId
);
708 configIsInCopy
= false;
711 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
712 static bool isReadingConfigFromCopy(void)
714 return configIsInCopy
;
718 static bool isWritingConfigToCopy(void)
720 return configIsInCopy
721 #if defined(USE_CUSTOM_DEFAULTS)
722 && !processingCustomDefaults
727 #if defined(USE_CUSTOM_DEFAULTS)
728 static bool cliProcessCustomDefaults(bool quiet
);
731 static void backupAndResetConfigs(const bool useCustomDefaults
)
735 // reset all configs to defaults to do differencing
738 #if defined(USE_CUSTOM_DEFAULTS)
739 if (useCustomDefaults
) {
740 if (!cliProcessCustomDefaults(true)) {
741 cliPrintLine("###WARNING: NO CUSTOM DEFAULTS FOUND###");
745 UNUSED(useCustomDefaults
);
749 static uint8_t getPidProfileIndexToUse(void)
751 return pidProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentPidProfileIndex() : pidProfileIndexToUse
;
754 static uint8_t getRateProfileIndexToUse(void)
756 return rateProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentControlRateProfileIndex() : rateProfileIndexToUse
;
760 static uint16_t getValueOffset(const clivalue_t
*value
)
762 switch (value
->type
& VALUE_SECTION_MASK
) {
765 return value
->offset
;
767 return value
->offset
+ sizeof(pidProfile_t
) * getPidProfileIndexToUse();
768 case PROFILE_RATE_VALUE
:
769 return value
->offset
+ sizeof(controlRateConfig_t
) * getRateProfileIndexToUse();
774 STATIC_UNIT_TESTED
void *cliGetValuePointer(const clivalue_t
*value
)
776 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
777 if (isWritingConfigToCopy()) {
778 return CONST_CAST(void *, rec
->copy
+ getValueOffset(value
));
780 return CONST_CAST(void *, rec
->address
+ getValueOffset(value
));
784 static const char *dumpPgValue(const char *cmdName
, const clivalue_t
*value
, dumpFlags_t dumpMask
, const char *headingStr
)
786 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
789 cliPrintLinef("VALUE %s ERROR", value
->name
);
790 return headingStr
; // if it's not found, the pgn shouldn't be in the value table!
794 const char *format
= "set %s = ";
795 const char *defaultFormat
= "#set %s = ";
796 const int valueOffset
= getValueOffset(value
);
797 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
799 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
800 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
801 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
802 cliPrintf(defaultFormat
, value
->name
);
803 printValuePointer(cmdName
, value
, (uint8_t*)pg
->address
+ valueOffset
, false);
806 cliPrintf(format
, value
->name
);
807 printValuePointer(cmdName
, value
, pg
->copy
+ valueOffset
, false);
813 static void dumpAllValues(const char *cmdName
, uint16_t valueSection
, dumpFlags_t dumpMask
, const char *headingStr
)
815 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
817 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
818 const clivalue_t
*value
= &valueTable
[i
];
820 if ((value
->type
& VALUE_SECTION_MASK
) == valueSection
|| ((valueSection
== MASTER_VALUE
) && (value
->type
& VALUE_SECTION_MASK
) == HARDWARE_VALUE
)) {
821 headingStr
= dumpPgValue(cmdName
, value
, dumpMask
, headingStr
);
826 static void cliPrintVar(const char *cmdName
, const clivalue_t
*var
, bool full
)
828 const void *ptr
= cliGetValuePointer(var
);
830 printValuePointer(cmdName
, var
, ptr
, full
);
833 static void cliPrintVarRange(const clivalue_t
*var
)
835 switch (var
->type
& VALUE_MODE_MASK
) {
836 case (MODE_DIRECT
): {
837 switch (var
->type
& VALUE_TYPE_MASK
) {
839 cliPrintLinef("Allowed range: 0 - %u", var
->config
.u32Max
);
844 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmaxUnsigned
.min
, var
->config
.minmaxUnsigned
.max
);
848 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
854 case (MODE_LOOKUP
): {
855 const lookupTableEntry_t
*tableEntry
= &lookupTables
[var
->config
.lookup
.tableIndex
];
856 cliPrint("Allowed values: ");
857 bool firstEntry
= true;
858 for (unsigned i
= 0; i
< tableEntry
->valueCount
; i
++) {
859 if (tableEntry
->values
[i
]) {
863 cliPrintf("%s", tableEntry
->values
[i
]);
871 cliPrintLinef("Array length: %d", var
->config
.array
.length
);
874 case (MODE_STRING
): {
875 cliPrintLinef("String length: %d - %d", var
->config
.string
.minlength
, var
->config
.string
.maxlength
);
878 case (MODE_BITSET
): {
879 cliPrintLinef("Allowed values: OFF, ON");
885 static void cliSetVar(const clivalue_t
*var
, const uint32_t value
)
887 void *ptr
= cliGetValuePointer(var
);
891 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
892 switch (var
->type
& VALUE_TYPE_MASK
) {
894 mask
= (1 << var
->config
.bitpos
) & 0xff;
896 workValue
= *(uint8_t *)ptr
| mask
;
898 workValue
= *(uint8_t *)ptr
& ~mask
;
900 *(uint8_t *)ptr
= workValue
;
904 mask
= (1 << var
->config
.bitpos
) & 0xffff;
906 workValue
= *(uint16_t *)ptr
| mask
;
908 workValue
= *(uint16_t *)ptr
& ~mask
;
910 *(uint16_t *)ptr
= workValue
;
914 mask
= 1 << var
->config
.bitpos
;
916 workValue
= *(uint32_t *)ptr
| mask
;
918 workValue
= *(uint32_t *)ptr
& ~mask
;
920 *(uint32_t *)ptr
= workValue
;
924 switch (var
->type
& VALUE_TYPE_MASK
) {
926 *(uint8_t *)ptr
= value
;
930 *(int8_t *)ptr
= value
;
934 *(uint16_t *)ptr
= value
;
938 *(int16_t *)ptr
= value
;
942 *(uint32_t *)ptr
= value
;
948 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
949 static void cliRepeat(char ch
, uint8_t len
)
952 for (int i
= 0; i
< len
; i
++) {
953 bufWriterAppend(cliWriter
, ch
);
960 static void cliPrompt(void)
962 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
963 if (processingCustomDefaults
) {
964 cliPrint("\r\nd: #");
972 static void cliShowParseError(const char *cmdName
)
974 cliPrintErrorLinef(cmdName
, "PARSING FAILED");
977 static void cliShowInvalidArgumentCountError(const char *cmdName
)
979 cliPrintErrorLinef(cmdName
, "INVALID ARGUMENT COUNT", cmdName
);
982 static void cliShowArgumentRangeError(const char *cmdName
, char *name
, int min
, int max
)
985 cliPrintErrorLinef(cmdName
, "%s NOT BETWEEN %d AND %d", name
, min
, max
);
987 cliPrintErrorLinef(cmdName
, "ARGUMENT OUT OF RANGE");
991 static const char *nextArg(const char *currentArg
)
993 const char *ptr
= strchr(currentArg
, ' ');
994 while (ptr
&& *ptr
== ' ') {
1001 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
1003 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
1006 int val
= atoi(ptr
);
1007 val
= CHANNEL_VALUE_TO_STEP(val
);
1008 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
1009 if (argIndex
== 0) {
1010 range
->startStep
= val
;
1012 range
->endStep
= val
;
1014 (*validArgumentCount
)++;
1022 // Check if a string's length is zero
1023 static bool isEmpty(const char *string
)
1025 return (string
== NULL
|| *string
== '\0') ? true : false;
1028 static void printRxFailsafe(dumpFlags_t dumpMask
, const rxFailsafeChannelConfig_t
*rxFailsafeChannelConfigs
, const rxFailsafeChannelConfig_t
*defaultRxFailsafeChannelConfigs
, const char *headingStr
)
1030 // print out rxConfig failsafe settings
1031 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1032 for (uint32_t channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
1033 const rxFailsafeChannelConfig_t
*channelFailsafeConfig
= &rxFailsafeChannelConfigs
[channel
];
1034 const rxFailsafeChannelConfig_t
*defaultChannelFailsafeConfig
= &defaultRxFailsafeChannelConfigs
[channel
];
1035 const bool equalsDefault
= !memcmp(channelFailsafeConfig
, defaultChannelFailsafeConfig
, sizeof(*channelFailsafeConfig
));
1036 const bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
1037 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1039 const char *format
= "rxfail %u %c %d";
1040 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1042 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
],
1043 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig
->step
)
1045 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1047 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
],
1048 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
1051 const char *format
= "rxfail %u %c";
1052 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1054 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
]
1056 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1058 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
]
1064 static void cliRxFailsafe(const char *cmdName
, char *cmdline
)
1069 if (isEmpty(cmdline
)) {
1070 // print out rxConfig failsafe settings
1071 for (channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
1072 cliRxFailsafe(cmdName
, itoa(channel
, buf
, 10));
1075 const char *ptr
= cmdline
;
1076 channel
= atoi(ptr
++);
1077 if ((channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
)) {
1079 rxFailsafeChannelConfig_t
*channelFailsafeConfig
= rxFailsafeChannelConfigsMutable(channel
);
1081 const rxFailsafeChannelType_e type
= (channel
< NON_AUX_CHANNEL_COUNT
) ? RX_FAILSAFE_TYPE_FLIGHT
: RX_FAILSAFE_TYPE_AUX
;
1082 rxFailsafeChannelMode_e mode
= channelFailsafeConfig
->mode
;
1083 bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
1087 const char *p
= strchr(rxFailsafeModeCharacters
, *(ptr
));
1089 const uint8_t requestedMode
= p
- rxFailsafeModeCharacters
;
1090 mode
= rxFailsafeModesTable
[type
][requestedMode
];
1092 mode
= RX_FAILSAFE_MODE_INVALID
;
1094 if (mode
== RX_FAILSAFE_MODE_INVALID
) {
1095 cliShowParseError(cmdName
);
1099 requireValue
= mode
== RX_FAILSAFE_MODE_SET
;
1103 if (!requireValue
) {
1104 cliShowParseError(cmdName
);
1107 uint16_t value
= atoi(ptr
);
1108 value
= CHANNEL_VALUE_TO_RXFAIL_STEP(value
);
1109 if (value
> MAX_RXFAIL_RANGE_STEP
) {
1110 cliPrintErrorLinef(cmdName
, "value out of range: %d", value
);
1114 channelFailsafeConfig
->step
= value
;
1115 } else if (requireValue
) {
1116 cliShowInvalidArgumentCountError(cmdName
);
1119 channelFailsafeConfig
->mode
= mode
;
1122 char modeCharacter
= rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
];
1124 // double use of cliPrintf below
1125 // 1. acknowledge interpretation on command,
1126 // 2. query current setting on single item,
1129 cliPrintLinef("rxfail %u %c %d",
1132 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
1135 cliPrintLinef("rxfail %u %c",
1141 cliShowArgumentRangeError(cmdName
, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT
- 1);
1146 static void printAux(dumpFlags_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
, const char *headingStr
)
1148 const char *format
= "aux %u %u %u %u %u %u %u";
1149 // print out aux channel settings
1150 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1151 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
1152 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
1153 bool equalsDefault
= false;
1154 if (defaultModeActivationConditions
) {
1155 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
1156 equalsDefault
= !isModeActivationConditionConfigured(mac
, macDefault
);
1157 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1158 const box_t
*box
= findBoxByBoxId(macDefault
->modeId
);
1159 const box_t
*linkedTo
= findBoxByBoxId(macDefault
->linkedTo
);
1161 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1164 macDefault
->auxChannelIndex
,
1165 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
1166 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
),
1167 macDefault
->modeLogic
,
1168 linkedTo
? linkedTo
->permanentId
: 0
1172 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
1173 const box_t
*linkedTo
= findBoxByBoxId(mac
->linkedTo
);
1175 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1178 mac
->auxChannelIndex
,
1179 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
1180 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
1182 linkedTo
? linkedTo
->permanentId
: 0
1188 static void cliAux(const char *cmdName
, char *cmdline
)
1193 if (isEmpty(cmdline
)) {
1194 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
, NULL
);
1198 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
1199 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
1200 uint8_t validArgumentCount
= 0;
1204 const box_t
*box
= findBoxByPermanentId(val
);
1206 mac
->modeId
= box
->boxId
;
1207 validArgumentCount
++;
1213 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1214 mac
->auxChannelIndex
= val
;
1215 validArgumentCount
++;
1218 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
1222 if (val
== MODELOGIC_OR
|| val
== MODELOGIC_AND
) {
1223 mac
->modeLogic
= val
;
1224 validArgumentCount
++;
1230 const box_t
*box
= findBoxByPermanentId(val
);
1232 mac
->linkedTo
= box
->boxId
;
1233 validArgumentCount
++;
1236 if (validArgumentCount
== 4) { // for backwards compatibility
1237 mac
->modeLogic
= MODELOGIC_OR
;
1239 } else if (validArgumentCount
== 5) { // for backwards compatibility
1241 } else if (validArgumentCount
!= 6) {
1242 memset(mac
, 0, sizeof(modeActivationCondition_t
));
1244 analyzeModeActivationConditions();
1245 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1247 findBoxByBoxId(mac
->modeId
)->permanentId
,
1248 mac
->auxChannelIndex
,
1249 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
1250 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
1252 findBoxByBoxId(mac
->linkedTo
)->permanentId
1255 cliShowArgumentRangeError(cmdName
, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
1260 static void printSerial(dumpFlags_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
, const char *headingStr
)
1262 const char *format
= "serial %d %d %ld %ld %ld %ld";
1263 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1264 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
1265 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
1268 bool equalsDefault
= false;
1269 if (serialConfigDefault
) {
1270 equalsDefault
= !memcmp(&serialConfig
->portConfigs
[i
], &serialConfigDefault
->portConfigs
[i
], sizeof(serialConfig
->portConfigs
[i
]));
1271 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1272 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1273 serialConfigDefault
->portConfigs
[i
].identifier
,
1274 serialConfigDefault
->portConfigs
[i
].functionMask
,
1275 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
1276 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
1277 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
1278 baudRates
[serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
]
1281 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1282 serialConfig
->portConfigs
[i
].identifier
,
1283 serialConfig
->portConfigs
[i
].functionMask
,
1284 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
1285 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
1286 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
1287 baudRates
[serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
]
1292 static void cliSerial(const char *cmdName
, char *cmdline
)
1294 const char *format
= "serial %d %d %ld %ld %ld %ld";
1295 if (isEmpty(cmdline
)) {
1296 printSerial(DUMP_MASTER
, serialConfig(), NULL
, NULL
);
1299 serialPortConfig_t portConfig
;
1300 memset(&portConfig
, 0 , sizeof(portConfig
));
1303 uint8_t validArgumentCount
= 0;
1305 const char *ptr
= cmdline
;
1307 int val
= atoi(ptr
++);
1308 serialPortConfig_t
*currentConfig
= serialFindPortConfigurationMutable(val
);
1310 if (currentConfig
) {
1311 portConfig
.identifier
= val
;
1312 validArgumentCount
++;
1317 val
= strtoul(ptr
, NULL
, 10);
1318 portConfig
.functionMask
= val
;
1319 validArgumentCount
++;
1322 for (int i
= 0; i
< 4; i
++) {
1330 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
1331 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
1337 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_1000000
) {
1340 portConfig
.msp_baudrateIndex
= baudRateIndex
;
1343 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
1346 portConfig
.gps_baudrateIndex
= baudRateIndex
;
1349 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
1352 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
1355 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_2470000
) {
1358 portConfig
.blackbox_baudrateIndex
= baudRateIndex
;
1362 validArgumentCount
++;
1365 if (validArgumentCount
< 6) {
1366 cliShowInvalidArgumentCountError(cmdName
);
1370 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
1372 cliDumpPrintLinef(0, false, format
,
1373 portConfig
.identifier
,
1374 portConfig
.functionMask
,
1375 baudRates
[portConfig
.msp_baudrateIndex
],
1376 baudRates
[portConfig
.gps_baudrateIndex
],
1377 baudRates
[portConfig
.telemetry_baudrateIndex
],
1378 baudRates
[portConfig
.blackbox_baudrateIndex
]
1383 #if defined(USE_SERIAL_PASSTHROUGH)
1384 static void cbCtrlLine(void *context
, uint16_t ctrl
)
1387 int contextValue
= (int)(long)context
;
1389 pinioSet(contextValue
- 1, !(ctrl
& CTRL_LINE_STATE_DTR
));
1391 #endif /* USE_PINIO */
1394 if (!(ctrl
& CTRL_LINE_STATE_DTR
)) {
1399 static int cliParseSerialMode(const char *tok
)
1403 if (strcasestr(tok
, "rx")) {
1406 if (strcasestr(tok
, "tx")) {
1413 static void cliSerialPassthrough(const char *cmdName
, char *cmdline
)
1415 if (isEmpty(cmdline
)) {
1416 cliShowInvalidArgumentCountError(cmdName
);
1420 serialPassthroughPort_t ports
[2] = { {SERIAL_PORT_NONE
, 0, 0, NULL
}, {cliPort
->identifier
, 0, 0, cliPort
} };
1421 bool enableBaudCb
= false;
1422 int port1PinioDtr
= 0;
1423 bool port1ResetOnDtr
= false;
1424 bool escSensorPassthrough
= false;
1426 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
1429 while (tok
!= NULL
) {
1432 if (strcasestr(tok
, "esc_sensor")) {
1433 escSensorPassthrough
= true;
1434 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_ESC_SENSOR
);
1435 ports
[0].id
= portConfig
->identifier
;
1437 ports
[0].id
= atoi(tok
);
1441 ports
[0].baud
= atoi(tok
);
1444 ports
[0].mode
= cliParseSerialMode(tok
);
1447 if (strncasecmp(tok
, "reset", strlen(tok
)) == 0) {
1448 port1ResetOnDtr
= true;
1450 } else if (strncasecmp(tok
, "none", strlen(tok
)) == 0) {
1453 port1PinioDtr
= atoi(tok
);
1454 if (port1PinioDtr
< 0 || port1PinioDtr
> PINIO_COUNT
) {
1455 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr
);
1458 #endif /* USE_PINIO */
1462 ports
[1].id
= atoi(tok
);
1463 ports
[1].port
= NULL
;
1466 ports
[1].baud
= atoi(tok
);
1469 ports
[1].mode
= cliParseSerialMode(tok
);
1473 tok
= strtok_r(NULL
, " ", &saveptr
);
1477 if (ports
[0].id
== ports
[1].id
) {
1478 cliPrintLinef("Port1 and port2 are same");
1482 for (int i
= 0; i
< 2; i
++) {
1483 if (findSerialPortIndexByIdentifier(ports
[i
].id
) == -1) {
1484 cliPrintLinef("Invalid port%d %d", i
+ 1, ports
[i
].id
);
1487 cliPrintLinef("Port%d: %d ", i
+ 1, ports
[i
].id
);
1491 if (ports
[0].baud
== 0 && ports
[1].id
== SERIAL_PORT_USB_VCP
) {
1492 enableBaudCb
= true;
1495 for (int i
= 0; i
< 2; i
++) {
1496 serialPort_t
**port
= &(ports
[i
].port
);
1497 if (*port
!= NULL
) {
1501 int portIndex
= i
+ 1;
1502 serialPortUsage_t
*portUsage
= findSerialPortUsageByIdentifier(ports
[i
].id
);
1503 if (!portUsage
|| portUsage
->serialPort
== NULL
) {
1504 bool isUseDefaultBaud
= false;
1505 if (ports
[i
].baud
== 0) {
1507 ports
[i
].baud
= 57600;
1508 isUseDefaultBaud
= true;
1511 if (!ports
[i
].mode
) {
1512 ports
[i
].mode
= MODE_RXTX
;
1515 *port
= openSerialPort(ports
[i
].id
, FUNCTION_NONE
, NULL
, NULL
,
1516 ports
[i
].baud
, ports
[i
].mode
,
1517 SERIAL_NOT_INVERTED
);
1519 cliPrintLinef("Port%d could not be opened.", portIndex
);
1523 if (isUseDefaultBaud
) {
1524 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex
, ports
[i
].baud
);
1526 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex
, ports
[i
].baud
);
1529 *port
= portUsage
->serialPort
;
1530 // If the user supplied a mode, override the port's mode, otherwise
1531 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1532 // Set the baud rate if specified
1533 if (ports
[i
].baud
) {
1534 cliPrintf("Port%d is already open, setting baud = %d.\r\n", portIndex
, ports
[i
].baud
);
1535 serialSetBaudRate(*port
, ports
[i
].baud
);
1537 cliPrintf("Port%d is already open, baud = %d.\r\n", portIndex
, (*port
)->baudRate
);
1540 if (ports
[i
].mode
&& (*port
)->mode
!= ports
[i
].mode
) {
1541 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1542 portIndex
, (*port
)->mode
, ports
[i
].mode
);
1543 serialSetMode(*port
, ports
[i
].mode
);
1546 // If this port has a rx callback associated we need to remove it now.
1547 // Otherwise no data will be pushed in the serial port buffer!
1548 if ((*port
)->rxCallback
) {
1549 (*port
)->rxCallback
= NULL
;
1554 // If no baud rate is specified allow to be set via USB
1556 cliPrintLine("Port1 baud rate change over USB enabled.");
1557 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1558 // baud rate over USB without setting it using the serialpassthrough command
1559 serialSetBaudRateCb(ports
[1].port
, serialSetBaudRate
, ports
[0].port
);
1562 char *resetMessage
= "";
1563 if (port1ResetOnDtr
&& ports
[1].id
== SERIAL_PORT_USB_VCP
) {
1564 resetMessage
= "or drop DTR ";
1567 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage
);
1569 if ((ports
[1].id
== SERIAL_PORT_USB_VCP
) && (port1ResetOnDtr
1572 #endif /* USE_PINIO */
1574 // Register control line state callback
1575 serialSetCtrlLineStateCb(ports
[0].port
, cbCtrlLine
, (void *)(intptr_t)(port1PinioDtr
));
1578 // XXX Review ESC pass through under refactored motor handling
1579 #ifdef USE_PWM_OUTPUT
1580 if (escSensorPassthrough
) {
1581 // pwmDisableMotors();
1584 for (unsigned i
= 0; i
< getMotorCount(); i
++) {
1585 const ioTag_t tag
= motorConfig()->dev
.ioTags
[i
];
1587 const timerHardware_t
*timerHardware
= timerGetConfiguredByTag(tag
);
1588 if (timerHardware
) {
1589 IO_t io
= IOGetByTag(tag
);
1590 IOInit(io
, OWNER_MOTOR
, 0);
1591 IOConfigGPIO(io
, IOCFG_OUT_PP
);
1592 if (timerHardware
->output
& TIMER_OUTPUT_INVERTED
) {
1603 serialPassthrough(ports
[0].port
, ports
[1].port
, NULL
, NULL
);
1607 static void printAdjustmentRange(dumpFlags_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
, const char *headingStr
)
1609 const char *format
= "adjrange %u 0 %u %u %u %u %u %u %u";
1610 // print out adjustment ranges channel settings
1611 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1612 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
1613 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
1614 bool equalsDefault
= false;
1615 if (defaultAdjustmentRanges
) {
1616 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
1617 equalsDefault
= !memcmp(ar
, arDefault
, sizeof(*ar
));
1618 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1619 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1621 arDefault
->auxChannelIndex
,
1622 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1623 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1624 arDefault
->adjustmentConfig
,
1625 arDefault
->auxSwitchChannelIndex
,
1626 arDefault
->adjustmentCenter
,
1627 arDefault
->adjustmentScale
1630 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1632 ar
->auxChannelIndex
,
1633 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1634 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1635 ar
->adjustmentConfig
,
1636 ar
->auxSwitchChannelIndex
,
1637 ar
->adjustmentCenter
,
1643 static void cliAdjustmentRange(const char *cmdName
, char *cmdline
)
1645 const char *format
= "adjrange %u 0 %u %u %u %u %u %u %u";
1649 if (isEmpty(cmdline
)) {
1650 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
, NULL
);
1654 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1655 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1656 uint8_t validArgumentCount
= 0;
1662 // Keeping the parameter to retain backwards compatibility for the command format.
1663 validArgumentCount
++;
1668 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1669 ar
->auxChannelIndex
= val
;
1670 validArgumentCount
++;
1674 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1679 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1680 ar
->adjustmentConfig
= val
;
1681 validArgumentCount
++;
1687 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1688 ar
->auxSwitchChannelIndex
= val
;
1689 validArgumentCount
++;
1693 if (validArgumentCount
!= 6) {
1694 memset(ar
, 0, sizeof(adjustmentRange_t
));
1695 cliShowInvalidArgumentCountError(cmdName
);
1699 // Optional arguments
1700 ar
->adjustmentCenter
= 0;
1701 ar
->adjustmentScale
= 0;
1706 ar
->adjustmentCenter
= val
;
1707 validArgumentCount
++;
1712 ar
->adjustmentScale
= val
;
1713 validArgumentCount
++;
1716 activeAdjustmentRangeReset();
1718 cliDumpPrintLinef(0, false, format
,
1720 ar
->auxChannelIndex
,
1721 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1722 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1723 ar
->adjustmentConfig
,
1724 ar
->auxSwitchChannelIndex
,
1725 ar
->adjustmentCenter
,
1730 cliShowArgumentRangeError(cmdName
, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1735 #ifndef USE_QUAD_MIXER_ONLY
1736 static void printMotorMix(dumpFlags_t dumpMask
, const motorMixer_t
*customMotorMixer
, const motorMixer_t
*defaultCustomMotorMixer
, const char *headingStr
)
1738 const char *format
= "mmix %d %s %s %s %s";
1739 char buf0
[FTOA_BUFFER_LENGTH
];
1740 char buf1
[FTOA_BUFFER_LENGTH
];
1741 char buf2
[FTOA_BUFFER_LENGTH
];
1742 char buf3
[FTOA_BUFFER_LENGTH
];
1743 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1744 if (customMotorMixer
[i
].throttle
== 0.0f
)
1746 const float thr
= customMotorMixer
[i
].throttle
;
1747 const float roll
= customMotorMixer
[i
].roll
;
1748 const float pitch
= customMotorMixer
[i
].pitch
;
1749 const float yaw
= customMotorMixer
[i
].yaw
;
1750 bool equalsDefault
= false;
1751 if (defaultCustomMotorMixer
) {
1752 const float thrDefault
= defaultCustomMotorMixer
[i
].throttle
;
1753 const float rollDefault
= defaultCustomMotorMixer
[i
].roll
;
1754 const float pitchDefault
= defaultCustomMotorMixer
[i
].pitch
;
1755 const float yawDefault
= defaultCustomMotorMixer
[i
].yaw
;
1756 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1758 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1759 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1761 ftoa(thrDefault
, buf0
),
1762 ftoa(rollDefault
, buf1
),
1763 ftoa(pitchDefault
, buf2
),
1764 ftoa(yawDefault
, buf3
));
1766 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1774 #endif // USE_QUAD_MIXER_ONLY
1776 static void cliMotorMix(const char *cmdName
, char *cmdline
)
1778 #ifdef USE_QUAD_MIXER_ONLY
1786 if (isEmpty(cmdline
)) {
1787 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
, NULL
);
1788 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1789 // erase custom mixer
1790 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1791 customMotorMixerMutable(i
)->throttle
= 0.0f
;
1793 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1794 ptr
= nextArg(cmdline
);
1797 for (uint32_t i
= 0; ; i
++) {
1798 if (mixerNames
[i
] == NULL
) {
1799 cliPrintErrorLinef(cmdName
, "INVALID NAME");
1802 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1803 mixerLoadMix(i
, customMotorMixerMutable(0));
1804 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1805 cliMotorMix(cmdName
, "");
1812 uint32_t i
= atoi(ptr
); // get motor number
1813 if (i
< MAX_SUPPORTED_MOTORS
) {
1816 customMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1821 customMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1826 customMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1831 customMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1835 cliShowInvalidArgumentCountError(cmdName
);
1837 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
, NULL
);
1840 cliShowArgumentRangeError(cmdName
, "INDEX", 0, MAX_SUPPORTED_MOTORS
- 1);
1846 static void printRxRange(dumpFlags_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
, const char *headingStr
)
1848 const char *format
= "rxrange %u %u %u";
1849 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1850 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1851 bool equalsDefault
= false;
1852 if (defaultChannelRangeConfigs
) {
1853 equalsDefault
= !memcmp(&channelRangeConfigs
[i
], &defaultChannelRangeConfigs
[i
], sizeof(channelRangeConfigs
[i
]));
1854 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1855 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1857 defaultChannelRangeConfigs
[i
].min
,
1858 defaultChannelRangeConfigs
[i
].max
1861 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1863 channelRangeConfigs
[i
].min
,
1864 channelRangeConfigs
[i
].max
1869 static void cliRxRange(const char *cmdName
, char *cmdline
)
1871 const char *format
= "rxrange %u %u %u";
1872 int i
, validArgumentCount
= 0;
1875 if (isEmpty(cmdline
)) {
1876 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
, NULL
);
1877 } else if (strcasecmp(cmdline
, "reset") == 0) {
1878 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1882 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1883 int rangeMin
= PWM_PULSE_MIN
, rangeMax
= PWM_PULSE_MAX
;
1887 rangeMin
= atoi(ptr
);
1888 validArgumentCount
++;
1893 rangeMax
= atoi(ptr
);
1894 validArgumentCount
++;
1897 if (validArgumentCount
!= 2) {
1898 cliShowInvalidArgumentCountError(cmdName
);
1899 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1900 cliShowArgumentRangeError(cmdName
, "range min/max", PWM_PULSE_MIN
, PWM_PULSE_MAX
);
1902 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1903 channelRangeConfig
->min
= rangeMin
;
1904 channelRangeConfig
->max
= rangeMax
;
1905 cliDumpPrintLinef(0, false, format
,
1907 channelRangeConfig
->min
,
1908 channelRangeConfig
->max
1913 cliShowArgumentRangeError(cmdName
, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT
- 1);
1918 #ifdef USE_LED_STRIP_STATUS_MODE
1919 static void printLed(dumpFlags_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
, const char *headingStr
)
1921 const char *format
= "led %u %s";
1922 char ledConfigBuffer
[20];
1923 char ledConfigDefaultBuffer
[20];
1924 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1925 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1926 ledConfig_t ledConfig
= ledConfigs
[i
];
1927 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1928 bool equalsDefault
= false;
1929 if (defaultLedConfigs
) {
1930 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1931 equalsDefault
= ledConfig
== ledConfigDefault
;
1932 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1933 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1934 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1936 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1940 static void cliLed(const char *cmdName
, char *cmdline
)
1942 const char *format
= "led %u %s";
1943 char ledConfigBuffer
[20];
1947 if (isEmpty(cmdline
)) {
1948 printLed(DUMP_MASTER
, ledStripStatusModeConfig()->ledConfigs
, NULL
, NULL
);
1952 if (i
>= 0 && i
< LED_MAX_STRIP_LENGTH
) {
1953 ptr
= nextArg(cmdline
);
1954 if (parseLedStripConfig(i
, ptr
)) {
1955 generateLedConfig((ledConfig_t
*)&ledStripStatusModeConfig()->ledConfigs
[i
], ledConfigBuffer
, sizeof(ledConfigBuffer
));
1956 cliDumpPrintLinef(0, false, format
, i
, ledConfigBuffer
);
1958 cliShowParseError(cmdName
);
1961 cliShowArgumentRangeError(cmdName
, "INDEX", 0, LED_MAX_STRIP_LENGTH
- 1);
1966 static void printColor(dumpFlags_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
, const char *headingStr
)
1968 const char *format
= "color %u %d,%u,%u";
1969 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1970 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1971 const hsvColor_t
*color
= &colors
[i
];
1972 bool equalsDefault
= false;
1973 if (defaultColors
) {
1974 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1975 equalsDefault
= !memcmp(color
, colorDefault
, sizeof(*color
));
1976 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1977 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1979 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1983 static void cliColor(const char *cmdName
, char *cmdline
)
1985 const char *format
= "color %u %d,%u,%u";
1986 if (isEmpty(cmdline
)) {
1987 printColor(DUMP_MASTER
, ledStripStatusModeConfig()->colors
, NULL
, NULL
);
1989 const char *ptr
= cmdline
;
1990 const int i
= atoi(ptr
);
1991 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1992 ptr
= nextArg(cmdline
);
1993 if (parseColor(i
, ptr
)) {
1994 const hsvColor_t
*color
= &ledStripStatusModeConfig()->colors
[i
];
1995 cliDumpPrintLinef(0, false, format
, i
, color
->h
, color
->s
, color
->v
);
1997 cliShowParseError(cmdName
);
2000 cliShowArgumentRangeError(cmdName
, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
2005 static void printModeColor(dumpFlags_t dumpMask
, const ledStripStatusModeConfig_t
*ledStripStatusModeConfig
, const ledStripStatusModeConfig_t
*defaultLedStripConfig
, const char *headingStr
)
2007 const char *format
= "mode_color %u %u %u";
2008 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2009 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
2010 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
2011 int colorIndex
= ledStripStatusModeConfig
->modeColors
[i
].color
[j
];
2012 bool equalsDefault
= false;
2013 if (defaultLedStripConfig
) {
2014 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
2015 equalsDefault
= colorIndex
== colorIndexDefault
;
2016 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2017 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
2019 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
2023 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
2024 const int colorIndex
= ledStripStatusModeConfig
->specialColors
.color
[j
];
2025 bool equalsDefault
= false;
2026 if (defaultLedStripConfig
) {
2027 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
2028 equalsDefault
= colorIndex
== colorIndexDefault
;
2029 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2030 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
2032 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
2035 const int ledStripAuxChannel
= ledStripStatusModeConfig
->ledstrip_aux_channel
;
2036 bool equalsDefault
= false;
2037 if (defaultLedStripConfig
) {
2038 const int ledStripAuxChannelDefault
= defaultLedStripConfig
->ledstrip_aux_channel
;
2039 equalsDefault
= ledStripAuxChannel
== ledStripAuxChannelDefault
;
2040 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2041 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannelDefault
);
2043 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannel
);
2046 static void cliModeColor(const char *cmdName
, char *cmdline
)
2048 if (isEmpty(cmdline
)) {
2049 printModeColor(DUMP_MASTER
, ledStripStatusModeConfig(), NULL
, NULL
);
2051 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
2052 int args
[ARGS_COUNT
];
2055 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
2056 while (ptr
&& argNo
< ARGS_COUNT
) {
2057 args
[argNo
++] = atoi(ptr
);
2058 ptr
= strtok_r(NULL
, " ", &saveptr
);
2061 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
2062 cliShowInvalidArgumentCountError(cmdName
);
2066 int modeIdx
= args
[MODE
];
2067 int funIdx
= args
[FUNCTION
];
2068 int color
= args
[COLOR
];
2069 if (!setModeColor(modeIdx
, funIdx
, color
)) {
2070 cliShowParseError(cmdName
);
2073 // values are validated
2074 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
2080 static void printServo(dumpFlags_t dumpMask
, const servoParam_t
*servoParams
, const servoParam_t
*defaultServoParams
, const char *headingStr
)
2082 // print out servo settings
2083 const char *format
= "servo %u %d %d %d %d %d";
2084 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2085 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2086 const servoParam_t
*servoConf
= &servoParams
[i
];
2087 bool equalsDefault
= false;
2088 if (defaultServoParams
) {
2089 const servoParam_t
*defaultServoConf
= &defaultServoParams
[i
];
2090 equalsDefault
= !memcmp(servoConf
, defaultServoConf
, sizeof(*servoConf
));
2091 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2092 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2094 defaultServoConf
->min
,
2095 defaultServoConf
->max
,
2096 defaultServoConf
->middle
,
2097 defaultServoConf
->rate
,
2098 defaultServoConf
->forwardFromChannel
2101 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2107 servoConf
->forwardFromChannel
2110 // print servo directions
2111 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2112 const char *format
= "smix reverse %d %d r";
2113 const servoParam_t
*servoConf
= &servoParams
[i
];
2114 const servoParam_t
*servoConfDefault
= &defaultServoParams
[i
];
2115 if (defaultServoParams
) {
2116 bool equalsDefault
= servoConf
->reversedSources
== servoConfDefault
->reversedSources
;
2117 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
2118 equalsDefault
= ~(servoConf
->reversedSources
^ servoConfDefault
->reversedSources
) & (1 << channel
);
2119 if (servoConfDefault
->reversedSources
& (1 << channel
)) {
2120 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
2122 if (servoConf
->reversedSources
& (1 << channel
)) {
2123 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
2127 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
2128 if (servoConf
->reversedSources
& (1 << channel
)) {
2129 cliDumpPrintLinef(dumpMask
, true, format
, i
, channel
);
2136 static void cliServo(const char *cmdName
, char *cmdline
)
2138 const char *format
= "servo %u %d %d %d %d %d";
2139 enum { SERVO_ARGUMENT_COUNT
= 6 };
2140 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
2142 servoParam_t
*servo
;
2147 if (isEmpty(cmdline
)) {
2148 printServo(DUMP_MASTER
, servoParams(0), NULL
, NULL
);
2150 int validArgumentCount
= 0;
2154 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2156 // If command line doesn't fit the format, don't modify the config
2158 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
2159 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
2160 cliShowInvalidArgumentCountError(cmdName
);
2164 arguments
[validArgumentCount
++] = atoi(ptr
);
2168 } while (*ptr
>= '0' && *ptr
<= '9');
2169 } else if (*ptr
== ' ') {
2172 cliShowParseError(cmdName
);
2177 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
, FORWARD
};
2179 i
= arguments
[INDEX
];
2181 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2182 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
2183 cliShowInvalidArgumentCountError(cmdName
);
2187 servo
= servoParamsMutable(i
);
2190 arguments
[MIN
] < PWM_PULSE_MIN
|| arguments
[MIN
] > PWM_PULSE_MAX
||
2191 arguments
[MAX
] < PWM_PULSE_MIN
|| arguments
[MAX
] > PWM_PULSE_MAX
||
2192 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
2193 arguments
[MIN
] > arguments
[MAX
] ||
2194 arguments
[RATE
] < -100 || arguments
[RATE
] > 100 ||
2195 arguments
[FORWARD
] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2197 cliShowArgumentRangeError(cmdName
, NULL
, 0, 0);
2201 servo
->min
= arguments
[MIN
];
2202 servo
->max
= arguments
[MAX
];
2203 servo
->middle
= arguments
[MIDDLE
];
2204 servo
->rate
= arguments
[RATE
];
2205 servo
->forwardFromChannel
= arguments
[FORWARD
];
2207 cliDumpPrintLinef(0, false, format
,
2213 servo
->forwardFromChannel
2221 static void printServoMix(dumpFlags_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
, const char *headingStr
)
2223 const char *format
= "smix %d %d %d %d %d %d %d %d";
2224 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2225 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
2226 const servoMixer_t customServoMixer
= customServoMixers
[i
];
2227 if (customServoMixer
.rate
== 0) {
2231 bool equalsDefault
= false;
2232 if (defaultCustomServoMixers
) {
2233 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
2234 equalsDefault
= !memcmp(&customServoMixer
, &customServoMixerDefault
, sizeof(customServoMixer
));
2236 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2237 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2239 customServoMixerDefault
.targetChannel
,
2240 customServoMixerDefault
.inputSource
,
2241 customServoMixerDefault
.rate
,
2242 customServoMixerDefault
.speed
,
2243 customServoMixerDefault
.min
,
2244 customServoMixerDefault
.max
,
2245 customServoMixerDefault
.box
2248 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2250 customServoMixer
.targetChannel
,
2251 customServoMixer
.inputSource
,
2252 customServoMixer
.rate
,
2253 customServoMixer
.speed
,
2254 customServoMixer
.min
,
2255 customServoMixer
.max
,
2256 customServoMixer
.box
2261 static void cliServoMix(const char *cmdName
, char *cmdline
)
2263 int args
[8], check
= 0;
2264 int len
= strlen(cmdline
);
2267 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
, NULL
);
2268 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
2269 // erase custom mixer
2270 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2271 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2272 servoParamsMutable(i
)->reversedSources
= 0;
2274 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
2275 const char *ptr
= nextArg(cmdline
);
2278 for (uint32_t i
= 0; ; i
++) {
2279 if (mixerNames
[i
] == NULL
) {
2280 cliPrintErrorLinef(cmdName
, "INVALID NAME");
2283 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
2284 servoMixerLoadMix(i
);
2285 cliPrintLinef("Loaded %s", mixerNames
[i
]);
2286 cliServoMix(cmdName
, "");
2291 } else if (strncasecmp(cmdline
, "reverse", 7) == 0) {
2292 enum {SERVO
= 0, INPUT
, REVERSE
, ARGS_COUNT
};
2293 char *ptr
= strchr(cmdline
, ' ');
2297 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
2298 cliPrintf("\ti%d", inputSource
);
2301 for (uint32_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
2302 cliPrintf("%d", servoIndex
);
2303 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++) {
2304 cliPrintf("\t%s ", (servoParams(servoIndex
)->reversedSources
& (1 << inputSource
)) ? "r" : "n");
2312 ptr
= strtok_r(ptr
, " ", &saveptr
);
2313 while (ptr
!= NULL
&& check
< ARGS_COUNT
- 1) {
2314 args
[check
++] = atoi(ptr
);
2315 ptr
= strtok_r(NULL
, " ", &saveptr
);
2318 if (ptr
== NULL
|| check
!= ARGS_COUNT
- 1) {
2319 cliShowInvalidArgumentCountError(cmdName
);
2323 if (args
[SERVO
] >= 0 && args
[SERVO
] < MAX_SUPPORTED_SERVOS
2324 && args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
2325 && (*ptr
== 'r' || *ptr
== 'n')) {
2327 servoParamsMutable(args
[SERVO
])->reversedSources
|= 1 << args
[INPUT
];
2329 servoParamsMutable(args
[SERVO
])->reversedSources
&= ~(1 << args
[INPUT
]);
2332 cliShowArgumentRangeError(cmdName
, "servo", 0, MAX_SUPPORTED_SERVOS
);
2336 cliServoMix(cmdName
, "reverse");
2338 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, MIN
, MAX
, BOX
, ARGS_COUNT
};
2340 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2341 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2342 args
[check
++] = atoi(ptr
);
2343 ptr
= strtok_r(NULL
, " ", &saveptr
);
2346 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2347 cliShowInvalidArgumentCountError(cmdName
);
2351 int32_t i
= args
[RULE
];
2352 if (i
>= 0 && i
< MAX_SERVO_RULES
&&
2353 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
2354 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
2355 args
[RATE
] >= -100 && args
[RATE
] <= 100 &&
2356 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
2357 args
[MIN
] >= 0 && args
[MIN
] <= 100 &&
2358 args
[MAX
] >= 0 && args
[MAX
] <= 100 && args
[MIN
] < args
[MAX
] &&
2359 args
[BOX
] >= 0 && args
[BOX
] <= MAX_SERVO_BOXES
) {
2360 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
2361 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
2362 customServoMixersMutable(i
)->rate
= args
[RATE
];
2363 customServoMixersMutable(i
)->speed
= args
[SPEED
];
2364 customServoMixersMutable(i
)->min
= args
[MIN
];
2365 customServoMixersMutable(i
)->max
= args
[MAX
];
2366 customServoMixersMutable(i
)->box
= args
[BOX
];
2367 cliServoMix(cmdName
, "");
2369 cliShowArgumentRangeError(cmdName
, NULL
, 0, 0);
2377 static void cliWriteBytes(const uint8_t *buffer
, int count
)
2386 static void cliSdInfo(const char *cmdName
, char *cmdline
)
2391 cliPrint("SD card: ");
2393 if (sdcardConfig()->mode
== SDCARD_MODE_NONE
) {
2394 cliPrintLine("Not configured");
2399 if (!sdcard_isInserted()) {
2400 cliPrintLine("None inserted");
2404 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2405 cliPrintLine("Startup failed");
2409 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
2411 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2412 metadata
->manufacturerID
,
2413 metadata
->numBlocks
/ 2, /* One block is half a kB */
2414 metadata
->productionMonth
,
2415 metadata
->productionYear
,
2416 metadata
->productRevisionMajor
,
2417 metadata
->productRevisionMinor
2420 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
2422 cliPrint("'\r\n" "Filesystem: ");
2424 switch (afatfs_getFilesystemState()) {
2425 case AFATFS_FILESYSTEM_STATE_READY
:
2428 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
2429 cliPrint("Initializing");
2431 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
2432 case AFATFS_FILESYSTEM_STATE_FATAL
:
2435 switch (afatfs_getLastError()) {
2436 case AFATFS_ERROR_BAD_MBR
:
2437 cliPrint(" - no FAT MBR partitions");
2439 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
2440 cliPrint(" - bad FAT header");
2442 case AFATFS_ERROR_GENERIC
:
2443 case AFATFS_ERROR_NONE
:
2444 ; // Nothing more detailed to print
2454 #ifdef USE_FLASH_CHIP
2456 static void cliFlashInfo(const char *cmdName
, char *cmdline
)
2461 const flashGeometry_t
*layout
= flashGetGeometry();
2463 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2464 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
);
2466 for (uint8_t index
= 0; index
< FLASH_MAX_PARTITIONS
; index
++) {
2467 const flashPartition_t
*partition
;
2469 cliPrintLine("Paritions:");
2471 partition
= flashPartitionFindByIndex(index
);
2475 cliPrintLinef(" %d: %s %u %u", index
, flashPartitionGetTypeName(partition
->type
), partition
->startSector
, partition
->endSector
);
2478 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
2480 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2481 FLASH_PARTITION_SECTOR_COUNT(flashPartition
) * layout
->sectorSize
,
2487 #ifdef USE_FLASH_TOOLS
2488 static void cliFlashErase(const char *cmdName
, char *cmdline
)
2493 if (!flashfsIsSupported()) {
2499 cliPrintLine("Erasing, please wait ... ");
2501 cliPrintLine("Erasing,");
2505 flashfsEraseCompletely();
2507 while (!flashfsIsReady()) {
2519 beeper(BEEPER_BLACKBOX_ERASE
);
2521 cliPrintLine("Done.");
2524 static void cliFlashVerify(const char *cmdName
, char *cmdline
)
2528 cliPrintLine("Verifying");
2529 if (flashfsVerifyEntireFlash()) {
2530 cliPrintLine("Success");
2532 cliPrintErrorLinef(cmdName
, "Failed");
2536 static void cliFlashWrite(const char *cmdName
, char *cmdline
)
2538 const uint32_t address
= atoi(cmdline
);
2539 const char *text
= strchr(cmdline
, ' ');
2542 cliShowInvalidArgumentCountError(cmdName
);
2544 flashfsSeekAbs(address
);
2545 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2548 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2552 static void cliFlashRead(const char *cmdName
, char *cmdline
)
2554 uint32_t address
= atoi(cmdline
);
2556 const char *nextArg
= strchr(cmdline
, ' ');
2559 cliShowInvalidArgumentCountError(cmdName
);
2561 uint32_t length
= atoi(nextArg
);
2563 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2566 while (length
> 0) {
2567 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2569 for (int i
= 0; i
< bytesRead
; i
++) {
2570 cliWrite(buffer
[i
]);
2573 length
-= bytesRead
;
2574 address
+= bytesRead
;
2576 if (bytesRead
== 0) {
2577 //Assume we reached the end of the volume or something fatal happened
2587 #ifdef USE_VTX_CONTROL
2588 static void printVtx(dumpFlags_t dumpMask
, const vtxConfig_t
*vtxConfig
, const vtxConfig_t
*vtxConfigDefault
, const char *headingStr
)
2590 // print out vtx channel settings
2591 const char *format
= "vtx %u %u %u %u %u %u %u";
2592 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2593 bool equalsDefault
= false;
2594 for (uint32_t i
= 0; i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
; i
++) {
2595 const vtxChannelActivationCondition_t
*cac
= &vtxConfig
->vtxChannelActivationConditions
[i
];
2596 if (vtxConfigDefault
) {
2597 const vtxChannelActivationCondition_t
*cacDefault
= &vtxConfigDefault
->vtxChannelActivationConditions
[i
];
2598 equalsDefault
= !memcmp(cac
, cacDefault
, sizeof(*cac
));
2599 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2600 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2602 cacDefault
->auxChannelIndex
,
2604 cacDefault
->channel
,
2606 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.startStep
),
2607 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.endStep
)
2610 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2612 cac
->auxChannelIndex
,
2616 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2617 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2622 static void cliVtx(const char *cmdName
, char *cmdline
)
2624 const char *format
= "vtx %u %u %u %u %u %u %u";
2628 if (isEmpty(cmdline
)) {
2629 printVtx(DUMP_MASTER
, vtxConfig(), NULL
, NULL
);
2631 #ifdef USE_VTX_TABLE
2632 const uint8_t maxBandIndex
= vtxTableConfig()->bands
;
2633 const uint8_t maxChannelIndex
= vtxTableConfig()->channels
;
2634 const uint8_t maxPowerIndex
= vtxTableConfig()->powerLevels
;
2636 const uint8_t maxBandIndex
= VTX_TABLE_MAX_BANDS
;
2637 const uint8_t maxChannelIndex
= VTX_TABLE_MAX_CHANNELS
;
2638 const uint8_t maxPowerIndex
= VTX_TABLE_MAX_POWER_LEVELS
;
2642 if (i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
) {
2643 vtxChannelActivationCondition_t
*cac
= &vtxConfigMutable()->vtxChannelActivationConditions
[i
];
2644 uint8_t validArgumentCount
= 0;
2648 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
2649 cac
->auxChannelIndex
= val
;
2650 validArgumentCount
++;
2656 if (val
>= 0 && val
<= maxBandIndex
) {
2658 validArgumentCount
++;
2664 if (val
>= 0 && val
<= maxChannelIndex
) {
2666 validArgumentCount
++;
2672 if (val
>= 0 && val
<= maxPowerIndex
) {
2674 validArgumentCount
++;
2677 ptr
= processChannelRangeArgs(ptr
, &cac
->range
, &validArgumentCount
);
2679 if (validArgumentCount
!= 6) {
2680 memset(cac
, 0, sizeof(vtxChannelActivationCondition_t
));
2681 cliShowInvalidArgumentCountError(cmdName
);
2683 cliDumpPrintLinef(0, false, format
,
2685 cac
->auxChannelIndex
,
2689 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2690 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2694 cliShowArgumentRangeError(cmdName
, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
- 1);
2699 #endif // VTX_CONTROL
2701 #ifdef USE_VTX_TABLE
2703 static char *formatVtxTableBandFrequency(const bool isFactory
, const uint16_t *frequency
, int channels
)
2705 static char freqbuf
[5 * VTX_TABLE_MAX_CHANNELS
+ 8 + 1];
2706 char freqtmp
[5 + 1];
2708 strcat(freqbuf
, isFactory
? " FACTORY" : " CUSTOM ");
2709 for (int channel
= 0; channel
< channels
; channel
++) {
2710 tfp_sprintf(freqtmp
, " %4d", frequency
[channel
]);
2711 strcat(freqbuf
, freqtmp
);
2716 static const char *printVtxTableBand(dumpFlags_t dumpMask
, int band
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2718 char *fmt
= "vtxtable band %d %s %c%s";
2719 bool equalsDefault
= false;
2721 if (defaultConfig
) {
2722 equalsDefault
= true;
2723 if (strcasecmp(currentConfig
->bandNames
[band
], defaultConfig
->bandNames
[band
])) {
2724 equalsDefault
= false;
2726 if (currentConfig
->bandLetters
[band
] != defaultConfig
->bandLetters
[band
]) {
2727 equalsDefault
= false;
2729 for (int channel
= 0; channel
< VTX_TABLE_MAX_CHANNELS
; channel
++) {
2730 if (currentConfig
->frequency
[band
][channel
] != defaultConfig
->frequency
[band
][channel
]) {
2731 equalsDefault
= false;
2734 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2735 char *freqbuf
= formatVtxTableBandFrequency(defaultConfig
->isFactoryBand
[band
], defaultConfig
->frequency
[band
], defaultConfig
->channels
);
2736 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, band
+ 1, defaultConfig
->bandNames
[band
], defaultConfig
->bandLetters
[band
], freqbuf
);
2739 char *freqbuf
= formatVtxTableBandFrequency(currentConfig
->isFactoryBand
[band
], currentConfig
->frequency
[band
], currentConfig
->channels
);
2740 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, band
+ 1, currentConfig
->bandNames
[band
], currentConfig
->bandLetters
[band
], freqbuf
);
2744 static char *formatVtxTablePowerValues(const uint16_t *levels
, int count
)
2746 // (max 4 digit + 1 space) per level
2747 static char pwrbuf
[5 * VTX_TABLE_MAX_POWER_LEVELS
+ 1];
2750 for (int pwrindex
= 0; pwrindex
< count
; pwrindex
++) {
2751 tfp_sprintf(pwrtmp
, " %d", levels
[pwrindex
]);
2752 strcat(pwrbuf
, pwrtmp
);
2757 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2759 char *fmt
= "vtxtable powervalues%s";
2760 bool equalsDefault
= false;
2761 if (defaultConfig
) {
2762 equalsDefault
= true;
2763 for (int pwrindex
= 0; pwrindex
< VTX_TABLE_MAX_POWER_LEVELS
; pwrindex
++) {
2764 if (defaultConfig
->powerValues
[pwrindex
] != currentConfig
->powerValues
[pwrindex
]) {
2765 equalsDefault
= false;
2768 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2769 char *pwrbuf
= formatVtxTablePowerValues(defaultConfig
->powerValues
, VTX_TABLE_MAX_POWER_LEVELS
);
2770 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2773 char *pwrbuf
= formatVtxTablePowerValues(currentConfig
->powerValues
, currentConfig
->powerLevels
);
2774 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2778 static char *formatVtxTablePowerLabels(const char labels
[VTX_TABLE_MAX_POWER_LEVELS
][VTX_TABLE_POWER_LABEL_LENGTH
+ 1], int count
)
2780 static char pwrbuf
[(VTX_TABLE_POWER_LABEL_LENGTH
+ 1) * VTX_TABLE_MAX_POWER_LEVELS
+ 1];
2781 char pwrtmp
[(VTX_TABLE_POWER_LABEL_LENGTH
+ 1) + 1];
2783 for (int pwrindex
= 0; pwrindex
< count
; pwrindex
++) {
2784 strcat(pwrbuf
, " ");
2785 strcpy(pwrtmp
, labels
[pwrindex
]);
2786 // trim trailing space
2788 while ((sp
= strchr(pwrtmp
, ' '))) {
2791 strcat(pwrbuf
, pwrtmp
);
2796 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2798 char *fmt
= "vtxtable powerlabels%s";
2799 bool equalsDefault
= false;
2800 if (defaultConfig
) {
2801 equalsDefault
= true;
2802 for (int pwrindex
= 0; pwrindex
< VTX_TABLE_MAX_POWER_LEVELS
; pwrindex
++) {
2803 if (strcasecmp(defaultConfig
->powerLabels
[pwrindex
], currentConfig
->powerLabels
[pwrindex
])) {
2804 equalsDefault
= false;
2807 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2808 char *pwrbuf
= formatVtxTablePowerLabels(defaultConfig
->powerLabels
, VTX_TABLE_MAX_POWER_LEVELS
);
2809 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2812 char *pwrbuf
= formatVtxTablePowerLabels(currentConfig
->powerLabels
, currentConfig
->powerLevels
);
2813 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2817 static void printVtxTable(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2822 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2825 equalsDefault
= false;
2826 fmt
= "vtxtable bands %d";
2827 if (defaultConfig
) {
2828 equalsDefault
= (defaultConfig
->bands
== currentConfig
->bands
);
2829 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2830 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->bands
);
2832 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->bands
);
2835 equalsDefault
= false;
2836 fmt
= "vtxtable channels %d";
2837 if (defaultConfig
) {
2838 equalsDefault
= (defaultConfig
->channels
== currentConfig
->channels
);
2839 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2840 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->channels
);
2842 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->channels
);
2846 for (int band
= 0; band
< currentConfig
->bands
; band
++) {
2847 headingStr
= printVtxTableBand(dumpMask
, band
, currentConfig
, defaultConfig
, headingStr
);
2852 equalsDefault
= false;
2853 fmt
= "vtxtable powerlevels %d";
2854 if (defaultConfig
) {
2855 equalsDefault
= (defaultConfig
->powerLevels
== currentConfig
->powerLevels
);
2856 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2857 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->powerLevels
);
2859 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->powerLevels
);
2864 headingStr
= printVtxTablePowerValues(dumpMask
, currentConfig
, defaultConfig
, headingStr
);
2865 headingStr
= printVtxTablePowerLabels(dumpMask
, currentConfig
, defaultConfig
, headingStr
);
2868 static void cliVtxTable(const char *cmdName
, char *cmdline
)
2873 // Band number or nothing
2874 tok
= strtok_r(cmdline
, " ", &saveptr
);
2877 printVtxTable(DUMP_MASTER
| HIDE_UNUSED
, vtxTableConfigMutable(), NULL
, NULL
);
2881 if (strcasecmp(tok
, "bands") == 0) {
2882 tok
= strtok_r(NULL
, " ", &saveptr
);
2883 int bands
= atoi(tok
);
2884 if (bands
< 0 || bands
> VTX_TABLE_MAX_BANDS
) {
2885 cliShowArgumentRangeError(cmdName
, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS
);
2888 if (bands
< vtxTableConfigMutable()->bands
) {
2889 for (int i
= bands
; i
< vtxTableConfigMutable()->bands
; i
++) {
2890 vtxTableConfigClearBand(vtxTableConfigMutable(), i
);
2893 vtxTableConfigMutable()->bands
= bands
;
2895 } else if (strcasecmp(tok
, "channels") == 0) {
2896 tok
= strtok_r(NULL
, " ", &saveptr
);
2898 int channels
= atoi(tok
);
2899 if (channels
< 0 || channels
> VTX_TABLE_MAX_CHANNELS
) {
2900 cliShowArgumentRangeError(cmdName
, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS
);
2903 if (channels
< vtxTableConfigMutable()->channels
) {
2904 for (int i
= 0; i
< VTX_TABLE_MAX_BANDS
; i
++) {
2905 vtxTableConfigClearChannels(vtxTableConfigMutable(), i
, channels
);
2908 vtxTableConfigMutable()->channels
= channels
;
2910 } else if (strcasecmp(tok
, "powerlevels") == 0) {
2911 // Number of power levels
2912 tok
= strtok_r(NULL
, " ", &saveptr
);
2914 int levels
= atoi(tok
);
2915 if (levels
< 0 || levels
> VTX_TABLE_MAX_POWER_LEVELS
) {
2916 cliShowArgumentRangeError(cmdName
, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS
);
2918 if (levels
< vtxTableConfigMutable()->powerLevels
) {
2919 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels
);
2920 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels
);
2922 vtxTableConfigMutable()->powerLevels
= levels
;
2925 // XXX Show current level count?
2929 } else if (strcasecmp(tok
, "powervalues") == 0) {
2931 uint16_t power
[VTX_TABLE_MAX_POWER_LEVELS
];
2933 int levels
= vtxTableConfigMutable()->powerLevels
;
2935 memset(power
, 0, sizeof(power
));
2937 for (count
= 0; count
< levels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); count
++) {
2938 int value
= atoi(tok
);
2939 power
[count
] = value
;
2942 // Check remaining tokens
2944 if (count
< levels
) {
2945 cliPrintErrorLinef(cmdName
, "NOT ENOUGH VALUES (EXPECTED %d)", levels
);
2947 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
2948 cliPrintErrorLinef(cmdName
, "TOO MANY VALUES (EXPECTED %d)", levels
);
2952 for (int i
= 0; i
< VTX_TABLE_MAX_POWER_LEVELS
; i
++) {
2953 vtxTableConfigMutable()->powerValues
[i
] = power
[i
];
2956 } else if (strcasecmp(tok
, "powerlabels") == 0) {
2958 char label
[VTX_TABLE_MAX_POWER_LEVELS
][VTX_TABLE_POWER_LABEL_LENGTH
+ 1];
2959 int levels
= vtxTableConfigMutable()->powerLevels
;
2961 for (count
= 0; count
< levels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); count
++) {
2962 strncpy(label
[count
], tok
, VTX_TABLE_POWER_LABEL_LENGTH
);
2963 for (unsigned i
= 0; i
< strlen(label
[count
]); i
++) {
2964 label
[count
][i
] = toupper(label
[count
][i
]);
2968 // Check remaining tokens
2970 if (count
< levels
) {
2971 cliPrintErrorLinef(cmdName
, "NOT ENOUGH LABELS (EXPECTED %d)", levels
);
2973 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
2974 cliPrintErrorLinef(cmdName
, "TOO MANY LABELS (EXPECTED %d)", levels
);
2978 for (int i
= 0; i
< count
; i
++) {
2979 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels
[i
], label
[i
], VTX_TABLE_POWER_LABEL_LENGTH
);
2981 } else if (strcasecmp(tok
, "band") == 0) {
2983 int bands
= vtxTableConfigMutable()->bands
;
2985 tok
= strtok_r(NULL
, " ", &saveptr
);
2990 int band
= atoi(tok
);
2993 if (band
< 0 || band
>= bands
) {
2994 cliShowArgumentRangeError(cmdName
, "BAND NUMBER", 1, bands
);
2999 tok
= strtok_r(NULL
, " ", &saveptr
);
3005 char bandname
[VTX_TABLE_BAND_NAME_LENGTH
+ 1];
3006 memset(bandname
, 0, VTX_TABLE_BAND_NAME_LENGTH
+ 1);
3007 strncpy(bandname
, tok
, VTX_TABLE_BAND_NAME_LENGTH
);
3008 for (unsigned i
= 0; i
< strlen(bandname
); i
++) {
3009 bandname
[i
] = toupper(bandname
[i
]);
3013 tok
= strtok_r(NULL
, " ", &saveptr
);
3019 char bandletter
= toupper(tok
[0]);
3021 uint16_t bandfreq
[VTX_TABLE_MAX_CHANNELS
];
3023 int channels
= vtxTableConfigMutable()->channels
;
3024 bool isFactory
= false;
3026 for (channel
= 0; channel
< channels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); channel
++) {
3027 if (channel
== 0 && !isdigit(tok
[0])) {
3029 if (strcasecmp(tok
, "FACTORY") == 0) {
3031 } else if (strcasecmp(tok
, "CUSTOM") == 0) {
3034 cliPrintErrorLinef(cmdName
, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok
);
3038 int freq
= atoi(tok
);
3040 cliPrintErrorLinef(cmdName
, "INVALID FREQUENCY %s", tok
);
3043 bandfreq
[channel
] = freq
;
3046 if (channel
< channels
) {
3047 cliPrintErrorLinef(cmdName
, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels
);
3049 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
3050 cliPrintErrorLinef(cmdName
, "TOO MANY FREQUENCIES (EXPECTED %d)", channels
);
3054 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames
[band
], bandname
, VTX_TABLE_BAND_NAME_LENGTH
);
3055 vtxTableConfigMutable()->bandLetters
[band
] = bandletter
;
3057 for (int i
= 0; i
< channel
; i
++) {
3058 vtxTableConfigMutable()->frequency
[band
][i
] = bandfreq
[i
];
3060 vtxTableConfigMutable()->isFactoryBand
[band
] = isFactory
;
3063 cliPrintErrorLinef(cmdName
, "INVALID SUBCOMMAND %s", tok
);
3067 static void cliVtxInfo(const char *cmdName
, char *cmdline
)
3071 // Display the available power levels
3072 uint16_t levels
[VTX_TABLE_MAX_POWER_LEVELS
];
3073 uint16_t powers
[VTX_TABLE_MAX_POWER_LEVELS
];
3074 vtxDevice_t
*vtxDevice
= vtxCommonDevice();
3076 uint8_t level_count
= vtxCommonGetVTXPowerLevels(vtxDevice
, levels
, powers
);
3079 for (int i
= 0; i
< level_count
; i
++) {
3080 cliPrintLinef("level %d dBm, power %d mW", levels
[i
], powers
[i
]);
3083 cliPrintErrorLinef(cmdName
, "NO POWER VALUES DEFINED");
3086 cliPrintErrorLinef(cmdName
, "NO VTX");
3089 #endif // USE_VTX_TABLE
3091 #if defined(USE_SIMPLIFIED_TUNING)
3092 static void applySimplifiedTuningAllProfiles(void)
3094 for (unsigned pidProfileIndex
= 0; pidProfileIndex
< PID_PROFILE_COUNT
; pidProfileIndex
++) {
3095 applySimplifiedTuning(pidProfilesMutable(pidProfileIndex
), gyroConfigMutable());
3099 static void cliSimplifiedTuning(const char *cmdName
, char *cmdline
)
3101 if (strcasecmp(cmdline
, "apply") == 0) {
3102 applySimplifiedTuningAllProfiles();
3104 cliPrintLine("Applied simplified tuning.");
3105 } else if (strcasecmp(cmdline
, "disable") == 0) {
3106 for (unsigned pidProfileIndex
= 0; pidProfileIndex
< PID_PROFILE_COUNT
; pidProfileIndex
++) {
3107 disableSimplifiedTuning(pidProfilesMutable(pidProfileIndex
), gyroConfigMutable());
3110 cliPrintLine("Disabled simplified tuning.");
3112 cliShowParseError(cmdName
);
3117 static void printCraftName(dumpFlags_t dumpMask
, const pilotConfig_t
*pilotConfig
)
3119 const bool equalsDefault
= strlen(pilotConfig
->craftName
) == 0;
3120 cliDumpPrintLinef(dumpMask
, equalsDefault
, "\r\n# name: %s", equalsDefault
? emptyName
: pilotConfig
->craftName
);
3123 #if defined(USE_BOARD_INFO)
3125 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
3127 static void printBoardName(dumpFlags_t dumpMask
)
3129 if (!(dumpMask
& DO_DIFF
) || strlen(getBoardName())) {
3130 cliPrintLinef("board_name %s", getBoardName());
3134 static void cliBoardName(const char *cmdName
, char *cmdline
)
3136 const unsigned int len
= strlen(cmdline
);
3137 const char *boardName
= getBoardName();
3138 if (len
> 0 && strlen(boardName
) != 0 && boardInformationIsSet() && (len
!= strlen(boardName
) || strncmp(boardName
, cmdline
, len
))) {
3139 cliPrintErrorLinef(cmdName
, ERROR_MESSAGE
, "BOARD_NAME", boardName
);
3141 if (len
> 0 && !configIsInCopy
&& setBoardName(cmdline
)) {
3142 boardInformationUpdated
= true;
3144 cliPrintHashLine("Set board_name.");
3146 printBoardName(DUMP_ALL
);
3150 static void printManufacturerId(dumpFlags_t dumpMask
)
3152 if (!(dumpMask
& DO_DIFF
) || strlen(getManufacturerId())) {
3153 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3157 static void cliManufacturerId(const char *cmdName
, char *cmdline
)
3159 const unsigned int len
= strlen(cmdline
);
3160 const char *manufacturerId
= getManufacturerId();
3161 if (len
> 0 && boardInformationIsSet() && strlen(manufacturerId
) != 0 && (len
!= strlen(manufacturerId
) || strncmp(manufacturerId
, cmdline
, len
))) {
3162 cliPrintErrorLinef(cmdName
, ERROR_MESSAGE
, "MANUFACTURER_ID", manufacturerId
);
3164 if (len
> 0 && !configIsInCopy
&& setManufacturerId(cmdline
)) {
3165 boardInformationUpdated
= true;
3167 cliPrintHashLine("Set manufacturer_id.");
3169 printManufacturerId(DUMP_ALL
);
3173 #if defined(USE_SIGNATURE)
3174 static void writeSignature(char *signatureStr
, uint8_t *signature
)
3176 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
3177 tfp_sprintf(&signatureStr
[2 * i
], "%02x", signature
[i
]);
3181 static void cliSignature(const char *cmdName
, char *cmdline
)
3183 const int len
= strlen(cmdline
);
3185 uint8_t signature
[SIGNATURE_LENGTH
] = {0};
3187 if (len
!= 2 * SIGNATURE_LENGTH
) {
3188 cliPrintErrorLinef(cmdName
, "INVALID LENGTH: %d (EXPECTED: %d)", len
, 2 * SIGNATURE_LENGTH
);
3193 #define BLOCK_SIZE 2
3194 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
3195 char temp
[BLOCK_SIZE
+ 1];
3196 strncpy(temp
, &cmdline
[i
* BLOCK_SIZE
], BLOCK_SIZE
);
3197 temp
[BLOCK_SIZE
] = '\0';
3199 unsigned result
= strtoul(temp
, &end
, 16);
3200 if (end
== &temp
[BLOCK_SIZE
]) {
3201 signature
[i
] = result
;
3203 cliPrintErrorLinef(cmdName
, "INVALID CHARACTER FOUND: %c", end
[0]);
3211 char signatureStr
[SIGNATURE_LENGTH
* 2 + 1] = {0};
3212 if (len
> 0 && signatureIsSet() && memcmp(signature
, getSignature(), SIGNATURE_LENGTH
)) {
3213 writeSignature(signatureStr
, getSignature());
3214 cliPrintErrorLinef(cmdName
, ERROR_MESSAGE
, "SIGNATURE", signatureStr
);
3216 if (len
> 0 && !configIsInCopy
&& setSignature(signature
)) {
3217 signatureUpdated
= true;
3219 writeSignature(signatureStr
, getSignature());
3221 cliPrintHashLine("Set signature.");
3222 } else if (signatureUpdated
|| signatureIsSet()) {
3223 writeSignature(signatureStr
, getSignature());
3226 cliPrintLinef("signature %s", signatureStr
);
3231 #undef ERROR_MESSAGE
3233 #endif // USE_BOARD_INFO
3235 static void cliMcuId(const char *cmdName
, char *cmdline
)
3240 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0
, U_ID_1
, U_ID_2
);
3243 static void printFeature(dumpFlags_t dumpMask
, const uint32_t mask
, const uint32_t defaultMask
, const char *headingStr
)
3245 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3246 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // disabled features first
3247 if (strcmp(featureNames
[i
], emptyString
) != 0) { //Skip unused
3248 const char *format
= "feature -%s";
3249 const bool equalsDefault
= (~defaultMask
| mask
) & (1 << i
);
3250 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3251 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
3252 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, featureNames
[i
]);
3255 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // enabled features
3256 if (strcmp(featureNames
[i
], emptyString
) != 0) { //Skip unused
3257 const char *format
= "feature %s";
3258 if (defaultMask
& (1 << i
)) {
3259 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
3261 if (mask
& (1 << i
)) {
3262 const bool equalsDefault
= (defaultMask
| ~mask
) & (1 << i
);
3263 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3264 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, featureNames
[i
]);
3270 static void cliFeature(const char *cmdName
, char *cmdline
)
3272 uint32_t len
= strlen(cmdline
);
3273 const uint32_t mask
= featureConfig()->enabledFeatures
;
3275 cliPrint("Enabled: ");
3276 for (uint32_t i
= 0; ; i
++) {
3277 if (featureNames
[i
] == NULL
) {
3280 if (mask
& (1 << i
)) {
3281 cliPrintf("%s ", featureNames
[i
]);
3285 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3286 cliPrint("Available:");
3287 for (uint32_t i
= 0; ; i
++) {
3288 if (featureNames
[i
] == NULL
)
3290 if (strcmp(featureNames
[i
], emptyString
) != 0) //Skip unused
3291 cliPrintf(" %s", featureNames
[i
]);
3298 bool remove
= false;
3299 if (cmdline
[0] == '-') {
3302 cmdline
++; // skip over -
3306 for (uint32_t i
= 0; ; i
++) {
3307 if (featureNames
[i
] == NULL
) {
3308 cliPrintErrorLinef(cmdName
, "INVALID NAME");
3312 if (strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
3315 if (feature
& FEATURE_GPS
) {
3316 cliPrintLine("unavailable");
3320 #ifndef USE_RANGEFINDER
3321 if (feature
& FEATURE_RANGEFINDER
) {
3322 cliPrintLine("unavailable");
3327 featureConfigClear(feature
);
3328 cliPrint("Disabled");
3330 featureConfigSet(feature
);
3331 cliPrint("Enabled");
3333 cliPrintLinef(" %s", featureNames
[i
]);
3340 #if defined(USE_BEEPER)
3341 static void printBeeper(dumpFlags_t dumpMask
, const uint32_t offFlags
, const uint32_t offFlagsDefault
, const char *name
, const uint32_t allowedFlags
, const char *headingStr
)
3343 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3344 const uint8_t beeperCount
= beeperTableEntryCount();
3345 for (int32_t i
= 0; i
< beeperCount
- 1; i
++) {
3346 if (beeperModeMaskForTableIndex(i
) & allowedFlags
) {
3347 const char *formatOff
= "%s -%s";
3348 const char *formatOn
= "%s %s";
3349 const uint32_t beeperModeMask
= beeperModeMaskForTableIndex(i
);
3350 cliDefaultPrintLinef(dumpMask
, ~(offFlags
^ offFlagsDefault
) & beeperModeMask
, offFlags
& beeperModeMask
? formatOn
: formatOff
, name
, beeperNameForTableIndex(i
));
3351 const bool equalsDefault
= ~(offFlags
^ offFlagsDefault
) & beeperModeMask
;
3352 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3353 cliDumpPrintLinef(dumpMask
, equalsDefault
, offFlags
& beeperModeMask
? formatOff
: formatOn
, name
, beeperNameForTableIndex(i
));
3358 static void processBeeperCommand(const char *cmdName
, char *cmdline
, uint32_t *offFlags
, const uint32_t allowedFlags
)
3360 uint32_t len
= strlen(cmdline
);
3361 uint8_t beeperCount
= beeperTableEntryCount();
3364 cliPrintf("Disabled:");
3365 for (int32_t i
= 0; ; i
++) {
3366 if (i
== beeperCount
- 1) {
3372 if (beeperModeMaskForTableIndex(i
) & *offFlags
)
3373 cliPrintf(" %s", beeperNameForTableIndex(i
));
3376 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3377 cliPrint("Available:");
3378 for (uint32_t i
= 0; i
< beeperCount
; i
++) {
3379 if (beeperModeMaskForTableIndex(i
) & allowedFlags
) {
3380 cliPrintf(" %s", beeperNameForTableIndex(i
));
3385 bool remove
= false;
3386 if (cmdline
[0] == '-') {
3387 remove
= true; // this is for beeper OFF condition
3392 for (uint32_t i
= 0; ; i
++) {
3393 if (i
== beeperCount
) {
3394 cliPrintErrorLinef(cmdName
, "INVALID NAME");
3397 if (strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0 && beeperModeMaskForTableIndex(i
) & (allowedFlags
| BEEPER_GET_FLAG(BEEPER_ALL
))) {
3398 if (remove
) { // beeper off
3399 if (i
== BEEPER_ALL
- 1) {
3400 *offFlags
= allowedFlags
;
3402 *offFlags
|= beeperModeMaskForTableIndex(i
);
3404 cliPrint("Disabled");
3407 if (i
== BEEPER_ALL
- 1) {
3410 *offFlags
&= ~beeperModeMaskForTableIndex(i
);
3412 cliPrint("Enabled");
3414 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
3421 #if defined(USE_DSHOT)
3422 static void cliBeacon(const char *cmdName
, char *cmdline
)
3424 processBeeperCommand(cmdName
, cmdline
, &(beeperConfigMutable()->dshotBeaconOffFlags
), DSHOT_BEACON_ALLOWED_MODES
);
3428 static void cliBeeper(const char *cmdName
, char *cmdline
)
3430 processBeeperCommand(cmdName
, cmdline
, &(beeperConfigMutable()->beeper_off_flags
), BEEPER_ALLOWED_MODES
);
3434 #if defined(USE_RX_BIND)
3435 static void cliRxBind(const char *cmdName
, char *cmdline
)
3438 if (!startRxBind()) {
3439 cliPrintErrorLinef(cmdName
, "Not supported.");
3441 cliPrintLinef("Binding...");
3446 static void printMap(dumpFlags_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
, const char *headingStr
)
3448 bool equalsDefault
= true;
3450 char bufDefault
[16];
3453 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3454 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3455 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3456 if (defaultRxConfig
) {
3457 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3458 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
3463 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3464 const char *formatMap
= "map %s";
3465 if (defaultRxConfig
) {
3466 bufDefault
[i
] = '\0';
3467 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
3469 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
3473 static void cliMap(const char *cmdName
, char *cmdline
)
3476 char buf
[RX_MAPPABLE_CHANNEL_COUNT
+ 1];
3478 uint32_t len
= strlen(cmdline
);
3479 if (len
== RX_MAPPABLE_CHANNEL_COUNT
) {
3481 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3482 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
3486 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3487 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
3489 if (strchr(rcChannelLetters
, buf
[i
]) && !strchr(buf
+ i
+ 1, buf
[i
]))
3492 cliShowParseError(cmdName
);
3495 parseRcChannels(buf
, rxConfigMutable());
3496 } else if (len
> 0) {
3497 cliShowInvalidArgumentCountError(cmdName
);
3501 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3502 buf
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
3506 cliPrintLinef("map %s", buf
);
3509 static char *skipSpace(char *buffer
)
3511 while (*(buffer
) == ' ') {
3518 static char *checkCommand(char *cmdline
, const char *command
)
3520 if (!strncasecmp(cmdline
, command
, strlen(command
)) // command names match
3521 && (isspace((unsigned)cmdline
[strlen(command
)]) || cmdline
[strlen(command
)] == 0)) {
3522 return skipSpace(cmdline
+ strlen(command
) + 1);
3528 static void cliRebootEx(rebootTarget_e rebootTarget
)
3530 cliPrint("\r\nRebooting");
3532 waitForSerialPortToFinishTransmitting(cliPort
);
3535 switch (rebootTarget
) {
3536 case REBOOT_TARGET_BOOTLOADER_ROM
:
3537 systemResetToBootloader(BOOTLOADER_REQUEST_ROM
);
3540 #if defined(USE_FLASH_BOOT_LOADER)
3541 case REBOOT_TARGET_BOOTLOADER_FLASH
:
3542 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH
);
3546 case REBOOT_TARGET_FIRMWARE
:
3554 static void cliReboot(void)
3556 cliRebootEx(REBOOT_TARGET_FIRMWARE
);
3559 static void cliBootloader(const char *cmdName
, char *cmdline
)
3561 rebootTarget_e rebootTarget
;
3563 #if !defined(USE_FLASH_BOOT_LOADER)
3566 strncasecmp(cmdline
, "rom", 3) == 0) {
3567 rebootTarget
= REBOOT_TARGET_BOOTLOADER_ROM
;
3569 cliPrintHashLine("restarting in ROM bootloader mode");
3570 #if defined(USE_FLASH_BOOT_LOADER)
3571 } else if (isEmpty(cmdline
) || strncasecmp(cmdline
, "flash", 5) == 0) {
3572 rebootTarget
= REBOOT_TARGET_BOOTLOADER_FLASH
;
3574 cliPrintHashLine("restarting in flash bootloader mode");
3577 cliPrintErrorLinef(cmdName
, "Invalid option");
3582 cliRebootEx(rebootTarget
);
3585 static void cliExit(const char *cmdName
, char *cmdline
)
3590 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3596 // incase a motor was left running during motortest, clear it here
3597 mixerResetDisarmedMotors();
3602 static void cliGpsPassthrough(const char *cmdName
, char *cmdline
)
3607 gpsEnablePassthrough(cliPort
);
3611 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3612 static void cliPrintGyroRegisters(uint8_t whichSensor
)
3614 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor
, MPU_RA_WHO_AM_I
));
3615 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_CONFIG
));
3616 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_GYRO_CONFIG
));
3619 static void cliDumpGyroRegisters(const char *cmdName
, char *cmdline
)
3624 #ifdef USE_MULTI_GYRO
3625 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_1
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
3626 cliPrintLinef("\r\n# Gyro 1");
3627 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
3629 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_2
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
3630 cliPrintLinef("\r\n# Gyro 2");
3631 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2
);
3634 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
3640 static int parseOutputIndex(const char *cmdName
, char *pch
, bool allowAllEscs
)
3642 int outputIndex
= atoi(pch
);
3643 if ((outputIndex
>= 0) && (outputIndex
< getMotorCount())) {
3644 cliPrintLinef("Using output %d.", outputIndex
);
3645 } else if (allowAllEscs
&& outputIndex
== ALL_MOTORS
) {
3646 cliPrintLinef("Using all outputs.");
3648 cliPrintErrorLinef(cmdName
, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3656 #if defined(USE_DSHOT)
3657 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3659 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3660 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3661 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3669 #define ESC_INFO_VERSION_POSITION 12
3671 static void printEscInfo(const char *cmdName
, const uint8_t *escInfoBuffer
, uint8_t bytesRead
)
3673 bool escInfoReceived
= false;
3674 if (bytesRead
> ESC_INFO_VERSION_POSITION
) {
3675 uint8_t escInfoVersion
;
3676 uint8_t frameLength
;
3677 if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 254) {
3678 escInfoVersion
= ESC_INFO_BLHELI32
;
3679 frameLength
= ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
;
3680 } else if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 255) {
3681 escInfoVersion
= ESC_INFO_KISS_V2
;
3682 frameLength
= ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE
;
3684 escInfoVersion
= ESC_INFO_KISS_V1
;
3685 frameLength
= ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE
;
3688 if (bytesRead
== frameLength
) {
3689 escInfoReceived
= true;
3691 if (calculateCrc8(escInfoBuffer
, frameLength
- 1) == escInfoBuffer
[frameLength
- 1]) {
3692 uint8_t firmwareVersion
= 0;
3693 uint8_t firmwareSubVersion
= 0;
3694 uint8_t escType
= 0;
3695 switch (escInfoVersion
) {
3696 case ESC_INFO_KISS_V1
:
3697 firmwareVersion
= escInfoBuffer
[12];
3698 firmwareSubVersion
= (escInfoBuffer
[13] & 0x1f) + 97;
3699 escType
= (escInfoBuffer
[13] & 0xe0) >> 5;
3702 case ESC_INFO_KISS_V2
:
3703 firmwareVersion
= escInfoBuffer
[13];
3704 firmwareSubVersion
= escInfoBuffer
[14];
3705 escType
= escInfoBuffer
[15];
3708 case ESC_INFO_BLHELI32
:
3709 firmwareVersion
= escInfoBuffer
[13];
3710 firmwareSubVersion
= escInfoBuffer
[14];
3711 escType
= escInfoBuffer
[15];
3716 cliPrint("ESC Type: ");
3717 switch (escInfoVersion
) {
3718 case ESC_INFO_KISS_V1
:
3719 case ESC_INFO_KISS_V2
:
3722 cliPrintLine("KISS8A");
3726 cliPrintLine("KISS16A");
3730 cliPrintLine("KISS24A");
3734 cliPrintLine("KISS Ultralite");
3738 cliPrintLine("unknown");
3744 case ESC_INFO_BLHELI32
:
3746 char *escType
= (char *)(escInfoBuffer
+ 31);
3748 cliPrintLine(escType
);
3754 cliPrint("MCU Serial No: 0x");
3755 for (int i
= 0; i
< 12; i
++) {
3756 if (i
&& (i
% 3 == 0)) {
3759 cliPrintf("%02x", escInfoBuffer
[i
]);
3763 switch (escInfoVersion
) {
3764 case ESC_INFO_KISS_V1
:
3765 case ESC_INFO_KISS_V2
:
3766 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion
/ 100, firmwareVersion
% 100, (char)firmwareSubVersion
);
3769 case ESC_INFO_BLHELI32
:
3770 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion
, firmwareSubVersion
);
3774 if (escInfoVersion
== ESC_INFO_KISS_V2
|| escInfoVersion
== ESC_INFO_BLHELI32
) {
3775 cliPrintLinef("Rotation Direction: %s", escInfoBuffer
[16] ? "reversed" : "normal");
3776 cliPrintLinef("3D: %s", escInfoBuffer
[17] ? "on" : "off");
3777 if (escInfoVersion
== ESC_INFO_BLHELI32
) {
3778 uint8_t setting
= escInfoBuffer
[18];
3779 cliPrint("Low voltage Limit: ");
3782 cliPrintLine("off");
3786 cliPrintLine("unsupported");
3790 cliPrintLinef("%d.%01d", setting
/ 10, setting
% 10);
3795 setting
= escInfoBuffer
[19];
3796 cliPrint("Current Limit: ");
3799 cliPrintLine("off");
3803 cliPrintLine("unsupported");
3807 cliPrintLinef("%d", setting
);
3812 for (int i
= 0; i
< 4; i
++) {
3813 setting
= escInfoBuffer
[i
+ 20];
3814 cliPrintLinef("LED %d: %s", i
, setting
? (setting
== 255) ? "unsupported" : "on" : "off");
3819 cliPrintErrorLinef(cmdName
, "CHECKSUM ERROR.");
3824 if (!escInfoReceived
) {
3825 cliPrintLine("No Info.");
3829 static void executeEscInfoCommand(const char *cmdName
, uint8_t escIndex
)
3831 cliPrintLinef("Info for ESC %d:", escIndex
);
3833 uint8_t escInfoBuffer
[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
];
3835 startEscDataRead(escInfoBuffer
, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
);
3837 dshotCommandWrite(escIndex
, getMotorCount(), DSHOT_CMD_ESC_INFO
, DSHOT_CMD_TYPE_BLOCKING
);
3841 printEscInfo(cmdName
, escInfoBuffer
, getNumberEscBytesRead());
3843 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3845 static void cliDshotProg(const char *cmdName
, char *cmdline
)
3847 if (isEmpty(cmdline
) || !isMotorProtocolDshot()) {
3848 cliShowParseError(cmdName
);
3854 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3857 bool firstCommand
= true;
3858 while (pch
!= NULL
) {
3861 escIndex
= parseOutputIndex(cmdName
, pch
, true);
3862 if (escIndex
== -1) {
3869 int command
= atoi(pch
);
3870 if (command
>= 0 && command
< DSHOT_MIN_THROTTLE
) {
3872 // pwmDisableMotors();
3875 firstCommand
= false;
3878 if (command
!= DSHOT_CMD_ESC_INFO
) {
3879 dshotCommandWrite(escIndex
, getMotorCount(), command
, DSHOT_CMD_TYPE_BLOCKING
);
3881 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3882 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
3883 if (escIndex
!= ALL_MOTORS
) {
3884 executeEscInfoCommand(cmdName
, escIndex
);
3886 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
3887 executeEscInfoCommand(cmdName
, i
);
3893 cliPrintLine("Not supported.");
3897 cliPrintLinef("Command Sent: %d", command
);
3900 cliPrintErrorLinef(cmdName
, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE
- 1);
3908 pch
= strtok_r(NULL
, " ", &saveptr
);
3915 #ifdef USE_ESCSERIAL
3916 static void cliEscPassthrough(const char *cmdName
, char *cmdline
)
3918 if (isEmpty(cmdline
)) {
3919 cliShowInvalidArgumentCountError(cmdName
);
3925 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3929 while (pch
!= NULL
) {
3932 if (strncasecmp(pch
, "sk", strlen(pch
)) == 0) {
3933 mode
= PROTOCOL_SIMONK
;
3934 } else if (strncasecmp(pch
, "bl", strlen(pch
)) == 0) {
3935 mode
= PROTOCOL_BLHELI
;
3936 } else if (strncasecmp(pch
, "ki", strlen(pch
)) == 0) {
3937 mode
= PROTOCOL_KISS
;
3938 } else if (strncasecmp(pch
, "cc", strlen(pch
)) == 0) {
3939 mode
= PROTOCOL_KISSALL
;
3941 cliShowParseError(cmdName
);
3947 escIndex
= parseOutputIndex(cmdName
, pch
, mode
== PROTOCOL_KISS
);
3948 if (escIndex
== -1) {
3954 cliShowInvalidArgumentCountError(cmdName
);
3962 pch
= strtok_r(NULL
, " ", &saveptr
);
3965 if (!escEnablePassthrough(cliPort
, &motorConfig()->dev
, escIndex
, mode
)) {
3966 cliPrintErrorLinef(cmdName
, "Error starting ESC connection");
3971 #ifndef USE_QUAD_MIXER_ONLY
3972 static void cliMixer(const char *cmdName
, char *cmdline
)
3976 len
= strlen(cmdline
);
3979 cliPrintLinef("Mixer: %s", mixerNames
[mixerConfig()->mixerMode
- 1]);
3981 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3982 cliPrint("Available:");
3983 for (uint32_t i
= 0; ; i
++) {
3984 if (mixerNames
[i
] == NULL
)
3986 cliPrintf(" %s", mixerNames
[i
]);
3992 for (uint32_t i
= 0; ; i
++) {
3993 if (mixerNames
[i
] == NULL
) {
3994 cliPrintErrorLinef(cmdName
, "INVALID NAME");
3997 if (strncasecmp(cmdline
, mixerNames
[i
], len
) == 0) {
3998 mixerConfigMutable()->mixerMode
= i
+ 1;
4003 cliMixer(cmdName
, "");
4007 static void cliMotor(const char *cmdName
, char *cmdline
)
4009 if (isEmpty(cmdline
)) {
4010 cliShowInvalidArgumentCountError(cmdName
);
4019 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
4021 while (pch
!= NULL
) {
4024 motorIndex
= parseOutputIndex(cmdName
, pch
, true);
4025 if (motorIndex
== -1) {
4031 motorValue
= atoi(pch
);
4036 pch
= strtok_r(NULL
, " ", &saveptr
);
4040 if (motorValue
< PWM_RANGE_MIN
|| motorValue
> PWM_RANGE_MAX
) {
4041 cliShowArgumentRangeError(cmdName
, "VALUE", 1000, 2000);
4043 uint32_t motorOutputValue
= motorConvertFromExternal(motorValue
);
4045 if (motorIndex
!= ALL_MOTORS
) {
4046 motor_disarmed
[motorIndex
] = motorOutputValue
;
4048 cliPrintLinef("motor %d: %d", motorIndex
, motorOutputValue
);
4050 for (int i
= 0; i
< getMotorCount(); i
++) {
4051 motor_disarmed
[i
] = motorOutputValue
;
4054 cliPrintLinef("all motors: %d", motorOutputValue
);
4058 cliShowInvalidArgumentCountError(cmdName
);
4063 static void cliPlaySound(const char *cmdName
, char *cmdline
)
4067 static int lastSoundIdx
= -1;
4069 if (isEmpty(cmdline
)) {
4070 i
= lastSoundIdx
+ 1; //next sound index
4071 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
4072 while (true) { //no name for index; try next one
4073 if (++i
>= beeperTableEntryCount())
4074 i
= 0; //if end then wrap around to first entry
4075 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
4076 break; //if name OK then play sound below
4077 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
4078 cliPrintErrorLinef(cmdName
, "ERROR PLAYING SOUND");
4083 } else { //index value was given
4085 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
4086 cliPrintLinef("No sound for index %d", i
);
4092 cliPrintLinef("Playing sound %d: %s", i
, name
);
4093 beeper(beeperModeForTableIndex(i
));
4097 static void cliProfile(const char *cmdName
, char *cmdline
)
4099 if (isEmpty(cmdline
)) {
4100 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4103 const int i
= atoi(cmdline
);
4104 if (i
>= 0 && i
< PID_PROFILE_COUNT
) {
4105 changePidProfile(i
);
4106 cliProfile(cmdName
, "");
4108 cliPrintErrorLinef(cmdName
, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT
- 1);
4113 static void cliRateProfile(const char *cmdName
, char *cmdline
)
4115 if (isEmpty(cmdline
)) {
4116 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4119 const int i
= atoi(cmdline
);
4120 if (i
>= 0 && i
< CONTROL_RATE_PROFILE_COUNT
) {
4121 changeControlRateProfile(i
);
4122 cliRateProfile(cmdName
, "");
4124 cliPrintErrorLinef(cmdName
, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT
- 1);
4129 static void cliDumpPidProfile(const char *cmdName
, uint8_t pidProfileIndex
, dumpFlags_t dumpMask
)
4131 if (pidProfileIndex
>= PID_PROFILE_COUNT
) {
4136 pidProfileIndexToUse
= pidProfileIndex
;
4139 cliProfile(cmdName
, "");
4141 char profileStr
[10];
4142 tfp_sprintf(profileStr
, "profile %d", pidProfileIndex
);
4143 dumpAllValues(cmdName
, PROFILE_VALUE
, dumpMask
, profileStr
);
4145 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4148 static void cliDumpRateProfile(const char *cmdName
, uint8_t rateProfileIndex
, dumpFlags_t dumpMask
)
4150 if (rateProfileIndex
>= CONTROL_RATE_PROFILE_COUNT
) {
4155 rateProfileIndexToUse
= rateProfileIndex
;
4158 cliRateProfile(cmdName
, "");
4160 char rateProfileStr
[14];
4161 tfp_sprintf(rateProfileStr
, "rateprofile %d", rateProfileIndex
);
4162 dumpAllValues(cmdName
, PROFILE_RATE_VALUE
, dumpMask
, rateProfileStr
);
4164 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4167 #ifdef USE_CLI_BATCH
4168 static void cliPrintCommandBatchWarning(const char *cmdName
, const char *warning
)
4170 cliPrintErrorLinef(cmdName
, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4172 cliPrintErrorLinef(cmdName
, warning
);
4176 static void resetCommandBatch(void)
4178 commandBatchActive
= false;
4179 commandBatchError
= false;
4182 static void cliBatch(const char *cmdName
, char *cmdline
)
4184 if (strncasecmp(cmdline
, "start", 5) == 0) {
4185 if (!commandBatchActive
) {
4186 commandBatchActive
= true;
4187 commandBatchError
= false;
4189 cliPrintLine("Command batch started");
4190 } else if (strncasecmp(cmdline
, "end", 3) == 0) {
4191 if (commandBatchActive
&& commandBatchError
) {
4192 cliPrintCommandBatchWarning(cmdName
, NULL
);
4194 cliPrintLine("Command batch ended");
4196 resetCommandBatch();
4198 cliPrintErrorLinef(cmdName
, "Invalid option");
4203 static bool prepareSave(void)
4205 #if defined(USE_CUSTOM_DEFAULTS)
4206 if (processingCustomDefaults
) {
4211 #ifdef USE_CLI_BATCH
4212 if (commandBatchActive
&& commandBatchError
) {
4217 #if defined(USE_BOARD_INFO)
4218 if (boardInformationUpdated
) {
4219 persistBoardInformation();
4221 #if defined(USE_SIGNATURE)
4222 if (signatureUpdated
) {
4226 #endif // USE_BOARD_INFO
4231 bool tryPrepareSave(const char *cmdName
)
4233 bool success
= prepareSave();
4234 #if defined(USE_CLI_BATCH)
4236 cliPrintCommandBatchWarning(cmdName
, "PLEASE FIX ERRORS THEN 'SAVE'");
4237 resetCommandBatch();
4249 static void cliSave(const char *cmdName
, char *cmdline
)
4253 if (tryPrepareSave(cmdName
)) {
4255 cliPrintHashLine("saving");
4261 #if defined(USE_CUSTOM_DEFAULTS)
4262 bool resetConfigToCustomDefaults(void)
4266 #ifdef USE_CLI_BATCH
4267 commandBatchError
= false;
4270 cliProcessCustomDefaults(true);
4272 #if defined(USE_SIMPLIFIED_TUNING)
4273 applySimplifiedTuningAllProfiles();
4276 return prepareSave();
4279 static bool customDefaultsHasNext(const char *customDefaultsPtr
)
4281 return *customDefaultsPtr
&& *customDefaultsPtr
!= 0xFF && customDefaultsPtr
< customDefaultsEnd
;
4284 static const char *parseCustomDefaultsHeaderElement(char *dest
, const char *customDefaultsPtr
, const char *prefix
, const char terminator
, const unsigned maxLength
)
4286 char *endPtr
= NULL
;
4287 unsigned len
= strlen(prefix
);
4288 if (customDefaultsPtr
&& customDefaultsHasNext(customDefaultsPtr
) && strncmp(customDefaultsPtr
, prefix
, len
) == 0) {
4289 customDefaultsPtr
+= len
;
4290 endPtr
= strchr(customDefaultsPtr
, terminator
);
4293 if (endPtr
&& customDefaultsHasNext(endPtr
)) {
4294 len
= endPtr
- customDefaultsPtr
;
4295 memcpy(dest
, customDefaultsPtr
, MIN(len
, maxLength
));
4297 customDefaultsPtr
+= len
;
4299 return customDefaultsPtr
;
4305 static void parseCustomDefaultsHeader(void)
4307 const char *customDefaultsPtr
= customDefaultsStart
;
4308 if (strncmp(customDefaultsPtr
, CUSTOM_DEFAULTS_START_PREFIX
, strlen(CUSTOM_DEFAULTS_START_PREFIX
)) == 0) {
4309 customDefaultsFound
= true;
4311 customDefaultsPtr
= strchr(customDefaultsPtr
, '\n');
4312 if (customDefaultsPtr
&& customDefaultsHasNext(customDefaultsPtr
)) {
4313 customDefaultsPtr
++;
4316 customDefaultsPtr
= parseCustomDefaultsHeaderElement(customDefaultsManufacturerId
, customDefaultsPtr
, CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX
, CUSTOM_DEFAULTS_BOARD_NAME_PREFIX
[0], MAX_MANUFACTURER_ID_LENGTH
);
4318 customDefaultsPtr
= parseCustomDefaultsHeaderElement(customDefaultsBoardName
, customDefaultsPtr
, CUSTOM_DEFAULTS_BOARD_NAME_PREFIX
, CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX
[0], MAX_BOARD_NAME_LENGTH
);
4320 customDefaultsPtr
= parseCustomDefaultsHeaderElement(customDefaultsChangesetId
, customDefaultsPtr
, CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX
, CUSTOM_DEFAULTS_DATE_PREFIX
[0], MAX_CHANGESET_ID_LENGTH
);
4322 customDefaultsPtr
= parseCustomDefaultsHeaderElement(customDefaultsDate
, customDefaultsPtr
, CUSTOM_DEFAULTS_DATE_PREFIX
, '\n', MAX_DATE_LENGTH
);
4325 customDefaultsHeaderParsed
= true;
4328 bool hasCustomDefaults(void)
4330 if (!customDefaultsHeaderParsed
) {
4331 parseCustomDefaultsHeader();
4334 return customDefaultsFound
;
4338 static void cliDefaults(const char *cmdName
, char *cmdline
)
4340 bool saveConfigs
= true;
4341 uint16_t parameterGroupId
= 0;
4342 #if defined(USE_CUSTOM_DEFAULTS)
4343 bool useCustomDefaults
= true;
4344 #elif defined(USE_CUSTOM_DEFAULTS_ADDRESS)
4345 // Required to keep the linker from eliminating these
4346 if (customDefaultsStart
!= customDefaultsEnd
) {
4352 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
4354 bool expectParameterGroupId
= false;
4355 while (tok
!= NULL
) {
4356 if (expectParameterGroupId
) {
4357 parameterGroupId
= atoi(tok
);
4358 expectParameterGroupId
= false;
4360 if (!parameterGroupId
) {
4361 cliShowParseError(cmdName
);
4365 } else if (strcasestr(tok
, "group_id")) {
4366 expectParameterGroupId
= true;
4367 } else if (strcasestr(tok
, "nosave")) {
4368 saveConfigs
= false;
4369 #if defined(USE_CUSTOM_DEFAULTS)
4370 } else if (strcasestr(tok
, "bare")) {
4371 useCustomDefaults
= false;
4372 } else if (strcasestr(tok
, "show")) {
4374 cliShowParseError(cmdName
);
4375 } else if (hasCustomDefaults()) {
4376 char *customDefaultsPtr
= customDefaultsStart
;
4377 while (customDefaultsHasNext(customDefaultsPtr
)) {
4378 if (*customDefaultsPtr
!= '\n') {
4379 cliPrintf("%c", *customDefaultsPtr
++);
4382 customDefaultsPtr
++;
4386 cliPrintError(cmdName
, "NO CUSTOM DEFAULTS FOUND");
4392 cliShowParseError(cmdName
);
4398 tok
= strtok_r(NULL
, " ", &saveptr
);
4401 if (expectParameterGroupId
) {
4402 cliShowParseError(cmdName
);
4407 if (parameterGroupId
) {
4408 cliPrintLinef("\r\n# resetting group %d to defaults", parameterGroupId
);
4411 cliPrintHashLine("resetting to defaults");
4416 #ifdef USE_CLI_BATCH
4417 // Reset only the error state and allow the batch active state to remain.
4418 // This way if a "defaults nosave" was issued after the "batch on" we'll
4419 // only reset the current error state but the batch will still be active
4420 // for subsequent commands.
4421 commandBatchError
= false;
4424 #if defined(USE_CUSTOM_DEFAULTS)
4425 if (useCustomDefaults
) {
4426 cliProcessCustomDefaults(false);
4430 #if defined(USE_SIMPLIFIED_TUNING)
4431 applySimplifiedTuningAllProfiles();
4434 if (parameterGroupId
) {
4435 restoreConfigs(parameterGroupId
);
4438 if (saveConfigs
&& tryPrepareSave(cmdName
)) {
4439 writeUnmodifiedConfigToEEPROM();
4445 static void cliPrintVarDefault(const char *cmdName
, const clivalue_t
*value
)
4447 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
4449 const char *defaultFormat
= "Default value: ";
4450 const int valueOffset
= getValueOffset(value
);
4451 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
4452 if (!equalsDefault
) {
4453 cliPrintf(defaultFormat
, value
->name
);
4454 printValuePointer(cmdName
, value
, (uint8_t*)pg
->address
+ valueOffset
, false);
4460 STATIC_UNIT_TESTED
void cliGet(const char *cmdName
, char *cmdline
)
4462 const clivalue_t
*val
;
4463 int matchedCommands
= 0;
4465 pidProfileIndexToUse
= getCurrentPidProfileIndex();
4466 rateProfileIndexToUse
= getCurrentControlRateProfileIndex();
4468 backupAndResetConfigs(true);
4470 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4471 if (strcasestr(valueTable
[i
].name
, cmdline
)) {
4472 val
= &valueTable
[i
];
4473 if (matchedCommands
> 0) {
4476 cliPrintf("%s = ", valueTable
[i
].name
);
4477 cliPrintVar(cmdName
, val
, 0);
4479 switch (val
->type
& VALUE_SECTION_MASK
) {
4481 cliProfile(cmdName
, "");
4484 case PROFILE_RATE_VALUE
:
4485 cliRateProfile(cmdName
, "");
4492 cliPrintVarRange(val
);
4493 cliPrintVarDefault(cmdName
, val
);
4501 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4502 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4504 if (!matchedCommands
) {
4505 cliPrintErrorLinef(cmdName
, "INVALID NAME");
4509 static uint8_t getWordLength(char *bufBegin
, char *bufEnd
)
4511 while (*(bufEnd
- 1) == ' ') {
4515 return bufEnd
- bufBegin
;
4518 uint16_t cliGetSettingIndex(char *name
, uint8_t length
)
4520 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4521 const char *settingName
= valueTable
[i
].name
;
4523 // ensure exact match when setting to prevent setting variables with shorter names
4524 if (strncasecmp(name
, settingName
, strlen(settingName
)) == 0 && length
== strlen(settingName
)) {
4528 return valueTableEntryCount
;
4531 STATIC_UNIT_TESTED
void cliSet(const char *cmdName
, char *cmdline
)
4533 const uint32_t len
= strlen(cmdline
);
4536 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
4537 cliPrintLine("Current settings: ");
4539 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4540 const clivalue_t
*val
= &valueTable
[i
];
4541 cliPrintf("%s = ", valueTable
[i
].name
);
4542 cliPrintVar(cmdName
, val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4545 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
4548 uint8_t variableNameLength
= getWordLength(cmdline
, eqptr
);
4550 // skip the '=' and any ' ' characters
4552 eqptr
= skipSpace(eqptr
);
4554 const uint16_t index
= cliGetSettingIndex(cmdline
, variableNameLength
);
4555 if (index
>= valueTableEntryCount
) {
4556 cliPrintErrorLinef(cmdName
, "INVALID NAME");
4559 const clivalue_t
*val
= &valueTable
[index
];
4561 bool valueChanged
= false;
4563 switch (val
->type
& VALUE_MODE_MASK
) {
4565 if ((val
->type
& VALUE_TYPE_MASK
) == VAR_UINT32
) {
4566 uint32_t value
= strtoul(eqptr
, NULL
, 10);
4568 if (value
<= val
->config
.u32Max
) {
4569 cliSetVar(val
, value
);
4570 valueChanged
= true;
4573 int value
= atoi(eqptr
);
4577 getMinMax(val
, &min
, &max
);
4579 if (value
>= min
&& value
<= max
) {
4580 cliSetVar(val
, value
);
4581 valueChanged
= true;
4590 if ((val
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
4591 tableIndex
= TABLE_OFF_ON
;
4593 tableIndex
= val
->config
.lookup
.tableIndex
;
4595 const lookupTableEntry_t
*tableEntry
= &lookupTables
[tableIndex
];
4596 bool matched
= false;
4597 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
4598 matched
= tableEntry
->values
[tableValueIndex
] && strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
4601 value
= tableValueIndex
;
4603 cliSetVar(val
, value
);
4604 valueChanged
= true;
4612 const uint8_t arrayLength
= val
->config
.array
.length
;
4613 char *valPtr
= eqptr
;
4616 while (i
< arrayLength
&& valPtr
!= NULL
) {
4618 valPtr
= skipSpace(valPtr
);
4620 // process substring starting at valPtr
4621 // note: no need to copy substrings for atoi()
4622 // it stops at the first character that cannot be converted...
4623 switch (val
->type
& VALUE_TYPE_MASK
) {
4627 // fetch data pointer
4628 uint8_t *data
= (uint8_t *)cliGetValuePointer(val
) + i
;
4630 *data
= (uint8_t)atoi((const char*) valPtr
);
4636 // fetch data pointer
4637 int8_t *data
= (int8_t *)cliGetValuePointer(val
) + i
;
4639 *data
= (int8_t)atoi((const char*) valPtr
);
4645 // fetch data pointer
4646 uint16_t *data
= (uint16_t *)cliGetValuePointer(val
) + i
;
4648 *data
= (uint16_t)atoi((const char*) valPtr
);
4654 // fetch data pointer
4655 int16_t *data
= (int16_t *)cliGetValuePointer(val
) + i
;
4657 *data
= (int16_t)atoi((const char*) valPtr
);
4663 // fetch data pointer
4664 uint32_t *data
= (uint32_t *)cliGetValuePointer(val
) + i
;
4666 *data
= (uint32_t)strtoul((const char*) valPtr
, NULL
, 10);
4672 // find next comma (or end of string)
4673 valPtr
= strchr(valPtr
, ',') + 1;
4680 valueChanged
= true;
4684 char *valPtr
= eqptr
;
4685 valPtr
= skipSpace(valPtr
);
4687 const unsigned int len
= strlen(valPtr
);
4688 const uint8_t min
= val
->config
.string
.minlength
;
4689 const uint8_t max
= val
->config
.string
.maxlength
;
4690 const bool updatable
= ((val
->config
.string
.flags
& STRING_FLAGS_WRITEONCE
) == 0 ||
4691 strlen((char *)cliGetValuePointer(val
)) == 0 ||
4692 strncmp(valPtr
, (char *)cliGetValuePointer(val
), len
) == 0);
4694 if (updatable
&& len
> 0 && len
<= max
) {
4695 memset((char *)cliGetValuePointer(val
), 0, max
);
4696 if (len
>= min
&& strncmp(valPtr
, emptyName
, len
)) {
4697 strncpy((char *)cliGetValuePointer(val
), valPtr
, len
);
4699 valueChanged
= true;
4701 cliPrintErrorLinef(cmdName
, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max
);
4708 cliPrintf("%s set to ", val
->name
);
4709 cliPrintVar(cmdName
, val
, 0);
4711 cliPrintErrorLinef(cmdName
, "INVALID VALUE");
4712 cliPrintVarRange(val
);
4717 // no equals, check for matching variables.
4718 cliGet(cmdName
, cmdline
);
4722 static const char *getMcuTypeById(mcuTypeId_e id
)
4724 if (id
< ARRAYLEN(mcuTypeNames
)) {
4725 return mcuTypeNames
[id
];
4731 static void cliStatus(const char *cmdName
, char *cmdline
)
4736 // MCU type, clock, vrefint, core temperature
4738 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock
/ 1000000));
4740 #if defined(STM32F4) || defined(STM32G4)
4741 // Only F4 and G4 is capable of switching between HSE/HSI (for now)
4742 int sysclkSource
= SystemSYSCLKSource();
4744 const char *SYSCLKSource
[] = { "HSI", "HSE", "PLLP", "PLLR" };
4745 const char *PLLSource
[] = { "-HSI", "-HSE" };
4749 if (sysclkSource
>= 2) {
4750 pllSource
= SystemPLLSource();
4753 cliPrintf(" (%s%s)", SYSCLKSource
[sysclkSource
], (sysclkSource
< 2) ? "" : PLLSource
[pllSource
]);
4756 #ifdef USE_ADC_INTERNAL
4757 uint16_t vrefintMv
= getVrefMv();
4758 int16_t coretemp
= getCoreTemperatureCelsius();
4759 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv
/ 1000, (vrefintMv
% 1000) / 10, coretemp
);
4764 // Stack and config sizes and usages
4766 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4767 #ifdef USE_STACK_CHECK
4768 cliPrintf(", Stack used: %d", stackUsedSize());
4772 cliPrintLinef("Configuration: %s, size: %d, max available: %d", configurationStates
[systemConfigMutable()->configurationState
], getEEPROMConfigSize(), getEEPROMStorageSize());
4775 #if defined(USE_SPI) || defined(USE_I2C)
4776 cliPrint("Devices detected:");
4777 #if defined(USE_SPI)
4778 cliPrintf(" SPI:%d", spiGetRegisteredDeviceCount());
4779 #if defined(USE_I2C)
4783 #if defined(USE_I2C)
4784 cliPrintf(" I2C:%d", i2cGetRegisteredDeviceCount());
4790 cliPrint("Gyros detected:");
4792 for (unsigned pos
= 0; pos
< 7; pos
++) {
4793 if (gyroConfig()->gyrosDetected
& BIT(pos
)) {
4799 cliPrintf(" gyro %d", pos
+ 1);
4803 if (gyroActiveDev()->gyroModeSPI
!= GYRO_EXTI_NO_INT
) {
4804 cliPrintf(" locked");
4806 if (gyroActiveDev()->gyroModeSPI
== GYRO_EXTI_INT_DMA
) {
4809 if (spiGetExtDeviceCount(&gyroActiveDev()->dev
) > 1) {
4810 cliPrintf(" shared");
4815 #if defined(USE_SENSOR_NAMES)
4816 const uint32_t detectedSensorsMask
= sensorsMask();
4817 for (uint32_t i
= 0; ; i
++) {
4818 if (sensorTypeNames
[i
] == NULL
) {
4821 const uint32_t mask
= (1 << i
);
4822 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
4823 const uint8_t sensorHardwareIndex
= detectedSensors
[i
];
4824 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
4828 cliPrintf("%s=%s", sensorTypeNames
[i
], sensorHardware
);
4829 #if defined(USE_ACC)
4830 if (mask
== SENSOR_ACC
&& acc
.dev
.revisionCode
) {
4831 cliPrintf(".%c", acc
.dev
.revisionCode
);
4837 #endif /* USE_SENSOR_NAMES */
4839 #if defined(USE_OSD)
4840 osdDisplayPortDevice_e displayPortDeviceType
;
4841 osdGetDisplayPort(&displayPortDeviceType
);
4843 cliPrintLinef("OSD: %s", lookupTableOsdDisplayPortDevice
[displayPortDeviceType
]);
4846 // Uptime and wall clock
4848 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4851 char buf
[FORMATTED_DATE_TIME_BUFSIZE
];
4853 if (rtcGetDateTime(&dt
)) {
4854 dateTimeFormatLocal(buf
, &dt
);
4855 cliPrintf(", Current Time: %s", buf
);
4862 const int gyroRate
= getTaskDeltaTimeUs(TASK_GYRO
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTimeUs(TASK_GYRO
)));
4863 int rxRate
= getCurrentRxRefreshRate();
4865 rxRate
= (int)(1000000.0f
/ ((float)rxRate
));
4867 const int systemRate
= getTaskDeltaTimeUs(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTimeUs(TASK_SYSTEM
)));
4868 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4869 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE
), getTaskDeltaTimeUs(TASK_GYRO
), gyroRate
, rxRate
, systemRate
);
4873 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4875 // Other devices and status
4878 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
4880 const uint16_t i2cErrorCounter
= 0;
4882 cliPrintLinef("I2C Errors: %d", i2cErrorCounter
);
4885 cliSdInfo(cmdName
, "");
4888 cliPrint("Arming disable flags:");
4889 armingDisableFlags_e flags
= getArmingDisableFlags();
4891 const int bitpos
= ffs(flags
) - 1;
4892 flags
&= ~(1 << bitpos
);
4893 cliPrintf(" %s", armingDisableFlagNames
[bitpos
]);
4898 static void cliTasks(const char *cmdName
, char *cmdline
)
4902 int averageLoadSum
= 0;
4905 if (systemConfig()->task_statistics
) {
4906 #if defined(USE_LATE_TASK_STATISTICS)
4907 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms late run reqd/us");
4909 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4912 cliPrintLine("Task list");
4915 for (taskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
4916 taskInfo_t taskInfo
;
4917 getTaskInfo(taskId
, &taskInfo
);
4918 if (taskInfo
.isEnabled
) {
4919 int taskFrequency
= taskInfo
.averageDeltaTime10thUs
== 0 ? 0 : lrintf(1e7f
/ taskInfo
.averageDeltaTime10thUs
);
4920 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
4921 const int maxLoad
= taskInfo
.maxExecutionTimeUs
== 0 ? 0 : (taskInfo
.maxExecutionTimeUs
* taskFrequency
) / 1000;
4922 const int averageLoad
= taskInfo
.averageExecutionTime10thUs
== 0 ? 0 : (taskInfo
.averageExecutionTime10thUs
* taskFrequency
) / 10000;
4923 if (taskId
!= TASK_SERIAL
) {
4924 averageLoadSum
+= averageLoad
;
4926 if (systemConfig()->task_statistics
) {
4927 #if defined(USE_LATE_TASK_STATISTICS)
4928 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d %6d %6d %7d",
4929 taskFrequency
, taskInfo
.maxExecutionTimeUs
, taskInfo
.averageExecutionTime10thUs
/ 10,
4930 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10,
4931 taskInfo
.totalExecutionTimeUs
/ 1000,
4932 taskInfo
.lateCount
, taskInfo
.runCount
, taskInfo
.execTime
);
4934 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4935 taskFrequency
, taskInfo
.maxExecutionTimeUs
, taskInfo
.averageExecutionTime10thUs
/ 10,
4936 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10,
4937 taskInfo
.totalExecutionTimeUs
/ 1000);
4940 cliPrintLinef("%6d", taskFrequency
);
4943 schedulerResetTaskMaxExecutionTime(taskId
);
4946 if (systemConfig()->task_statistics
) {
4947 cfCheckFuncInfo_t checkFuncInfo
;
4948 getCheckFuncInfo(&checkFuncInfo
);
4949 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo
.maxExecutionTimeUs
, checkFuncInfo
.averageExecutionTimeUs
, checkFuncInfo
.totalExecutionTimeUs
/ 1000);
4950 cliPrintLinef("Total (excluding SERIAL) %33d.%1d%%", averageLoadSum
/10, averageLoadSum
%10);
4951 if (debugMode
== DEBUG_SCHEDULER_DETERMINISM
) {
4952 extern int32_t schedLoopStartCycles
, taskGuardCycles
;
4954 cliPrintLinef("Scheduler start cycles %d guard cycles %d", schedLoopStartCycles
, taskGuardCycles
);
4956 schedulerResetCheckFunctionMaxExecutionTime();
4960 static void printVersion(const char *cmdName
, bool printBoardInfo
)
4962 #if !(defined(USE_CUSTOM_DEFAULTS))
4964 UNUSED(printBoardInfo
);
4967 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4970 systemConfig()->boardIdentifier
,
4975 MSP_API_VERSION_STRING
4978 #ifdef FEATURE_CUT_LEVEL
4979 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL
);
4984 #if defined(USE_CUSTOM_DEFAULTS)
4985 if (hasCustomDefaults()) {
4986 if (strlen(customDefaultsManufacturerId
) || strlen(customDefaultsBoardName
) || strlen(customDefaultsChangesetId
) || strlen(customDefaultsDate
)) {
4987 cliPrintLinef("%s%s%s%s%s%s%s%s",
4988 CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX
, customDefaultsManufacturerId
,
4989 CUSTOM_DEFAULTS_BOARD_NAME_PREFIX
, customDefaultsBoardName
,
4990 CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX
, customDefaultsChangesetId
,
4991 CUSTOM_DEFAULTS_DATE_PREFIX
, customDefaultsDate
4994 cliPrintHashLine("config: YES");
4997 cliPrintError(cmdName
, "NO CONFIG FOUND");
4999 #endif // USE_CUSTOM_DEFAULTS
5001 #if defined(USE_BOARD_INFO)
5002 if (printBoardInfo
&& strlen(getManufacturerId()) && strlen(getBoardName())) {
5003 cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
5008 static void cliVersion(const char *cmdName
, char *cmdline
)
5012 printVersion(cmdName
, true);
5015 #ifdef USE_RC_SMOOTHING_FILTER
5016 static void cliRcSmoothing(const char *cmdName
, char *cmdline
)
5020 rcSmoothingFilter_t
*rcSmoothingData
= getRcSmoothingData();
5021 cliPrint("# RC Smoothing Type: ");
5022 if (rxConfig()->rc_smoothing_mode
) {
5023 cliPrintLine("FILTER");
5024 if (rcSmoothingAutoCalculate()) {
5025 const uint16_t avgRxFrameUs
= rcSmoothingData
->averageFrameTimeUs
;
5026 cliPrint("# Detected RX frame rate: ");
5027 if (avgRxFrameUs
== 0) {
5028 cliPrintLine("NO SIGNAL");
5030 cliPrintLinef("%d.%03dms", avgRxFrameUs
/ 1000, avgRxFrameUs
% 1000);
5033 cliPrintf("# Active setpoint cutoff: %dhz ", rcSmoothingData
->setpointCutoffFrequency
);
5034 if (rcSmoothingData
->setpointCutoffSetting
) {
5035 cliPrintLine("(manual)");
5037 cliPrintLine("(auto)");
5039 cliPrintf("# Active FF cutoff: %dhz ", rcSmoothingData
->feedforwardCutoffFrequency
);
5040 if (rcSmoothingData
->ffCutoffSetting
) {
5041 cliPrintLine("(manual)");
5043 cliPrintLine("(auto)");
5045 cliPrintf("# Active throttle cutoff: %dhz ", rcSmoothingData
->throttleCutoffFrequency
);
5046 if (rcSmoothingData
->throttleCutoffSetting
) {
5047 cliPrintLine("(manual)");
5049 cliPrintLine("(auto)");
5052 cliPrintLine("OFF");
5055 #endif // USE_RC_SMOOTHING_FILTER
5057 #if defined(USE_RESOURCE_MGMT)
5059 #define RESOURCE_VALUE_MAX_INDEX(x) ((x) == 0 ? 1 : (x))
5062 const uint8_t owner
;
5066 const uint8_t maxIndex
;
5067 } cliResourceValue_t
;
5069 // Handy macros for keeping the table tidy.
5070 // DEFS : Single entry
5071 // DEFA : Array of uint8_t (stride = 1)
5072 // DEFW : Wider stride case; array of structs.
5074 #define DEFS(owner, pgn, type, member) \
5075 { owner, pgn, 0, offsetof(type, member), 0 }
5077 #define DEFA(owner, pgn, type, member, max) \
5078 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
5080 #define DEFW(owner, pgn, type, member, max) \
5081 { owner, pgn, sizeof(type), offsetof(type, member), max }
5083 const cliResourceValue_t resourceTable
[] = {
5085 DEFS( OWNER_BEEPER
, PG_BEEPER_DEV_CONFIG
, beeperDevConfig_t
, ioTag
) ,
5087 DEFA( OWNER_MOTOR
, PG_MOTOR_CONFIG
, motorConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_MOTORS
),
5089 DEFA( OWNER_SERVO
, PG_SERVO_CONFIG
, servoConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_SERVOS
),
5091 #if defined(USE_PPM)
5092 DEFS( OWNER_PPMINPUT
, PG_PPM_CONFIG
, ppmConfig_t
, ioTag
),
5094 #if defined(USE_PWM)
5095 DEFA( OWNER_PWMINPUT
, PG_PWM_CONFIG
, pwmConfig_t
, ioTags
[0], PWM_INPUT_PORT_COUNT
),
5097 #ifdef USE_RANGEFINDER_HCSR04
5098 DEFS( OWNER_SONAR_TRIGGER
, PG_SONAR_CONFIG
, sonarConfig_t
, triggerTag
),
5099 DEFS( OWNER_SONAR_ECHO
, PG_SONAR_CONFIG
, sonarConfig_t
, echoTag
),
5101 #ifdef USE_LED_STRIP
5102 DEFS( OWNER_LED_STRIP
, PG_LED_STRIP_CONFIG
, ledStripConfig_t
, ioTag
),
5105 DEFA( OWNER_SERIAL_TX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagTx
[0], SERIAL_PORT_MAX_INDEX
),
5106 DEFA( OWNER_SERIAL_RX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagRx
[0], SERIAL_PORT_MAX_INDEX
),
5109 DEFA( OWNER_INVERTER
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagInverter
[0], SERIAL_PORT_MAX_INDEX
),
5112 DEFW( OWNER_I2C_SCL
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagScl
, I2CDEV_COUNT
),
5113 DEFW( OWNER_I2C_SDA
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagSda
, I2CDEV_COUNT
),
5115 DEFA( OWNER_LED
, PG_STATUS_LED_CONFIG
, statusLedConfig_t
, ioTags
[0], STATUS_LED_NUMBER
),
5116 #ifdef USE_SPEKTRUM_BIND
5117 DEFS( OWNER_RX_BIND
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_pin_override_ioTag
),
5118 DEFS( OWNER_RX_BIND_PLUG
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_plug_ioTag
),
5120 #ifdef USE_TRANSPONDER
5121 DEFS( OWNER_TRANSPONDER
, PG_TRANSPONDER_CONFIG
, transponderConfig_t
, ioTag
),
5124 DEFW( OWNER_SPI_SCK
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagSck
, SPIDEV_COUNT
),
5125 DEFW( OWNER_SPI_MISO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMiso
, SPIDEV_COUNT
),
5126 DEFW( OWNER_SPI_MOSI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMosi
, SPIDEV_COUNT
),
5128 #ifdef USE_ESCSERIAL
5129 DEFS( OWNER_ESCSERIAL
, PG_ESCSERIAL_CONFIG
, escSerialConfig_t
, ioTag
),
5131 #ifdef USE_CAMERA_CONTROL
5132 DEFS( OWNER_CAMERA_CONTROL
, PG_CAMERA_CONTROL_CONFIG
, cameraControlConfig_t
, ioTag
),
5135 DEFS( OWNER_ADC_BATT
, PG_ADC_CONFIG
, adcConfig_t
, vbat
.ioTag
),
5136 DEFS( OWNER_ADC_RSSI
, PG_ADC_CONFIG
, adcConfig_t
, rssi
.ioTag
),
5137 DEFS( OWNER_ADC_CURR
, PG_ADC_CONFIG
, adcConfig_t
, current
.ioTag
),
5138 DEFS( OWNER_ADC_EXT
, PG_ADC_CONFIG
, adcConfig_t
, external1
.ioTag
),
5141 DEFS( OWNER_BARO_CS
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_spi_csn
),
5142 DEFS( OWNER_BARO_EOC
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_eoc_tag
),
5143 DEFS( OWNER_BARO_XCLR
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_xclr_tag
),
5146 DEFS( OWNER_COMPASS_CS
, PG_COMPASS_CONFIG
, compassConfig_t
, mag_spi_csn
),
5147 #ifdef USE_MAG_DATA_READY_SIGNAL
5148 DEFS( OWNER_COMPASS_EXTI
, PG_COMPASS_CONFIG
, compassConfig_t
, interruptTag
),
5151 #ifdef USE_SDCARD_SPI
5152 DEFS( OWNER_SDCARD_CS
, PG_SDCARD_CONFIG
, sdcardConfig_t
, chipSelectTag
),
5155 DEFS( OWNER_SDCARD_DETECT
, PG_SDCARD_CONFIG
, sdcardConfig_t
, cardDetectTag
),
5157 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
5158 DEFS( OWNER_SDIO_CK
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, CKPin
),
5159 DEFS( OWNER_SDIO_CMD
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, CMDPin
),
5160 DEFS( OWNER_SDIO_D0
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D0Pin
),
5161 DEFS( OWNER_SDIO_D1
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D1Pin
),
5162 DEFS( OWNER_SDIO_D2
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D2Pin
),
5163 DEFS( OWNER_SDIO_D3
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D3Pin
),
5166 DEFA( OWNER_PINIO
, PG_PINIO_CONFIG
, pinioConfig_t
, ioTag
, PINIO_COUNT
),
5168 #if defined(USE_USB_MSC)
5169 DEFS( OWNER_USB_MSC_PIN
, PG_USB_CONFIG
, usbDev_t
, mscButtonPin
),
5171 #ifdef USE_FLASH_CHIP
5172 DEFS( OWNER_FLASH_CS
, PG_FLASH_CONFIG
, flashConfig_t
, csTag
),
5175 DEFS( OWNER_OSD_CS
, PG_MAX7456_CONFIG
, max7456Config_t
, csTag
),
5178 DEFS( OWNER_RX_SPI_CS
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, csnTag
),
5179 DEFS( OWNER_RX_SPI_EXTI
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, extiIoTag
),
5180 DEFS( OWNER_RX_SPI_BIND
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, bindIoTag
),
5181 DEFS( OWNER_RX_SPI_LED
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, ledIoTag
),
5182 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
5183 DEFS( OWNER_RX_SPI_CC2500_TX_EN
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, txEnIoTag
),
5184 DEFS( OWNER_RX_SPI_CC2500_LNA_EN
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, lnaEnIoTag
),
5185 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
5186 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, antSelIoTag
),
5189 #if defined(USE_RX_EXPRESSLRS)
5190 DEFS( OWNER_RX_SPI_EXPRESSLRS_RESET
, PG_RX_EXPRESSLRS_SPI_CONFIG
, rxExpressLrsSpiConfig_t
, resetIoTag
),
5191 DEFS( OWNER_RX_SPI_EXPRESSLRS_BUSY
, PG_RX_EXPRESSLRS_SPI_CONFIG
, rxExpressLrsSpiConfig_t
, busyIoTag
),
5194 DEFW( OWNER_GYRO_EXTI
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, extiTag
, MAX_GYRODEV_COUNT
),
5195 DEFW( OWNER_GYRO_CS
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, csnTag
, MAX_GYRODEV_COUNT
),
5196 #ifdef USE_USB_DETECT
5197 DEFS( OWNER_USB_DETECT
, PG_USB_CONFIG
, usbDev_t
, detectPin
),
5199 #ifdef USE_VTX_RTC6705
5200 DEFS( OWNER_VTX_POWER
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, powerTag
),
5201 DEFS( OWNER_VTX_CS
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, csTag
),
5202 DEFS( OWNER_VTX_DATA
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, dataTag
),
5203 DEFS( OWNER_VTX_CLK
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, clockTag
),
5205 #ifdef USE_PIN_PULL_UP_DOWN
5206 DEFA( OWNER_PULLUP
, PG_PULLUP_CONFIG
, pinPullUpDownConfig_t
, ioTag
, PIN_PULL_UP_DOWN_COUNT
),
5207 DEFA( OWNER_PULLDOWN
, PG_PULLDOWN_CONFIG
, pinPullUpDownConfig_t
, ioTag
, PIN_PULL_UP_DOWN_COUNT
),
5215 static ioTag_t
*getIoTag(const cliResourceValue_t value
, uint8_t index
)
5217 const pgRegistry_t
* rec
= pgFind(value
.pgn
);
5218 return CONST_CAST(ioTag_t
*, rec
->address
+ value
.stride
* index
+ value
.offset
);
5221 static void printResource(dumpFlags_t dumpMask
, const char *headingStr
)
5223 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5224 for (unsigned int i
= 0; i
< ARRAYLEN(resourceTable
); i
++) {
5225 const char* owner
= ownerNames
[resourceTable
[i
].owner
];
5226 const pgRegistry_t
* pg
= pgFind(resourceTable
[i
].pgn
);
5227 const void *currentConfig
;
5228 const void *defaultConfig
;
5229 if (isReadingConfigFromCopy()) {
5230 currentConfig
= pg
->copy
;
5231 defaultConfig
= pg
->address
;
5233 currentConfig
= pg
->address
;
5234 defaultConfig
= NULL
;
5237 for (int index
= 0; index
< RESOURCE_VALUE_MAX_INDEX(resourceTable
[i
].maxIndex
); index
++) {
5238 const ioTag_t ioTag
= *(ioTag_t
*)((const uint8_t *)currentConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
5239 ioTag_t ioTagDefault
= 0;
5240 if (defaultConfig
) {
5241 ioTagDefault
= *(ioTag_t
*)((const uint8_t *)defaultConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
5244 const bool equalsDefault
= ioTag
== ioTagDefault
;
5245 const char *format
= "resource %s %d %c%02d";
5246 const char *formatUnassigned
= "resource %s %d NONE";
5247 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5249 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTagDefault
) + 'A', IO_GPIOPinIdxByTag(ioTagDefault
));
5250 } else if (defaultConfig
) {
5251 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
5254 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
));
5255 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5256 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
5262 static void printResourceOwner(uint8_t owner
, uint8_t index
)
5264 cliPrintf("%s", ownerNames
[resourceTable
[owner
].owner
]);
5266 if (resourceTable
[owner
].maxIndex
> 0) {
5267 cliPrintf(" %d", RESOURCE_INDEX(index
));
5271 static void resourceCheck(uint8_t resourceIndex
, uint8_t index
, ioTag_t newTag
)
5277 const char * format
= "\r\nNOTE: %c%02d already assigned to ";
5278 for (int r
= 0; r
< (int)ARRAYLEN(resourceTable
); r
++) {
5279 for (int i
= 0; i
< RESOURCE_VALUE_MAX_INDEX(resourceTable
[r
].maxIndex
); i
++) {
5280 ioTag_t
*tag
= getIoTag(resourceTable
[r
], i
);
5281 if (*tag
== newTag
) {
5282 bool cleared
= false;
5283 if (r
== resourceIndex
) {
5291 cliPrintf(format
, DEFIO_TAG_GPIOID(newTag
) + 'A', DEFIO_TAG_PIN(newTag
));
5293 printResourceOwner(r
, i
);
5297 printResourceOwner(r
, i
);
5298 cliPrintf(" disabled");
5307 static bool strToPin(char *ptr
, ioTag_t
*tag
)
5309 if (strcasecmp(ptr
, "NONE") == 0) {
5314 const unsigned port
= (*ptr
>= 'a') ? *ptr
- 'a' : *ptr
- 'A';
5319 const long pin
= strtol(ptr
, &end
, 10);
5320 if (end
!= ptr
&& pin
>= 0 && pin
< 16) {
5321 *tag
= DEFIO_TAG_MAKE(port
, pin
);
5332 static void showDma(void)
5337 cliPrintLine("DMA:");
5339 cliPrintLine("Currently active DMA:");
5342 for (int i
= 1; i
<= DMA_LAST_HANDLER
; i
++) {
5343 const resourceOwner_t
*owner
= dmaGetOwner(i
);
5345 cliPrintf(DMA_OUTPUT_STRING
, DMA_DEVICE_NO(i
), DMA_DEVICE_INDEX(i
));
5346 if (owner
->resourceIndex
> 0) {
5347 cliPrintLinef(" %s %d", ownerNames
[owner
->owner
], owner
->resourceIndex
);
5349 cliPrintLinef(" %s", ownerNames
[owner
->owner
]);
5357 typedef struct dmaoptEntry_s
{
5359 dmaPeripheral_e peripheral
;
5364 uint32_t presenceMask
;
5367 #define MASK_IGNORED (0)
5369 // Handy macros for keeping the table tidy.
5370 // DEFS : Single entry
5371 // DEFA : Array of uint8_t (stride = 1)
5372 // DEFW : Wider stride case; array of structs.
5374 #define DEFS(device, peripheral, pgn, type, member) \
5375 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5377 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5378 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5380 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5381 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
5383 dmaoptEntry_t dmaoptEntryTable
[] = {
5384 DEFW("SPI_MOSI", DMA_PERIPH_SPI_MOSI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, txDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5385 DEFW("SPI_MISO", DMA_PERIPH_SPI_MISO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, rxDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5386 // SPI_TX/SPI_RX for backwards compatibility with unified configs defined for 4.2.x
5387 DEFW("SPI_TX", DMA_PERIPH_SPI_MOSI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, txDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5388 DEFW("SPI_RX", DMA_PERIPH_SPI_MISO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, rxDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5389 DEFA("ADC", DMA_PERIPH_ADC
, PG_ADC_CONFIG
, adcConfig_t
, dmaopt
, ADCDEV_COUNT
, MASK_IGNORED
),
5390 DEFS("SDIO", DMA_PERIPH_SDIO
, PG_SDIO_CONFIG
, sdioConfig_t
, dmaopt
),
5391 DEFW("UART_TX", DMA_PERIPH_UART_TX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, txDmaopt
, UARTDEV_CONFIG_MAX
, MASK_IGNORED
),
5392 DEFW("UART_RX", DMA_PERIPH_UART_RX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, rxDmaopt
, UARTDEV_CONFIG_MAX
, MASK_IGNORED
),
5393 #if defined(STM32H7) || defined(STM32G4)
5394 DEFW("TIMUP", DMA_PERIPH_TIMUP
, PG_TIMER_UP_CONFIG
, timerUpConfig_t
, dmaopt
, HARDWARE_TIMER_DEFINITION_COUNT
, TIMUP_TIMERS
),
5402 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5403 #define DMA_OPT_STRING_BUFSIZE 5
5405 #if defined(STM32H7) || defined(STM32G4)
5406 #define DMA_CHANREQ_STRING "Request"
5408 #define DMA_CHANREQ_STRING "Channel"
5411 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
5412 #define DMA_STCH_STRING "Stream"
5414 #define DMA_STCH_STRING "Channel"
5417 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5419 static void optToString(int optval
, char *buf
)
5421 if (optval
== DMA_OPT_UNUSED
) {
5422 memcpy(buf
, "NONE", DMA_OPT_STRING_BUFSIZE
);
5424 tfp_sprintf(buf
, "%d", optval
);
5428 static void printPeripheralDmaoptDetails(dmaoptEntry_t
*entry
, int index
, const dmaoptValue_t dmaopt
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5430 // We compute number to display for different peripherals in advance.
5431 // This is done to deal with TIMUP which numbered non-contiguously.
5432 // Note that using timerGetNumberByIndex is not a generic solution,
5433 // but we are lucky that TIMUP is the only peripheral with non-contiguous numbering.
5437 if (entry
->presenceMask
) {
5438 uiIndex
= timerGetNumberByIndex(index
);
5440 uiIndex
= DMA_OPT_UI_INDEX(index
);
5443 if (dmaopt
!= DMA_OPT_UNUSED
) {
5444 printValue(dumpMask
, equalsDefault
,
5446 entry
->device
, uiIndex
, dmaopt
);
5448 const dmaChannelSpec_t
*dmaChannelSpec
= dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, dmaopt
);
5449 dmaCode_t dmaCode
= 0;
5450 if (dmaChannelSpec
) {
5451 dmaCode
= dmaChannelSpec
->code
;
5453 printValue(dumpMask
, equalsDefault
,
5454 "# %s %d: " DMASPEC_FORMAT_STRING
,
5455 entry
->device
, uiIndex
, DMA_CODE_CONTROLLER(dmaCode
), DMA_CODE_STREAM(dmaCode
), DMA_CODE_CHANNEL(dmaCode
));
5456 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5457 printValue(dumpMask
, equalsDefault
,
5459 entry
->device
, uiIndex
);
5463 static const char *printPeripheralDmaopt(dmaoptEntry_t
*entry
, int index
, dumpFlags_t dumpMask
, const char *headingStr
)
5465 const pgRegistry_t
* pg
= pgFind(entry
->pgn
);
5466 const void *currentConfig
;
5467 const void *defaultConfig
;
5469 if (isReadingConfigFromCopy()) {
5470 currentConfig
= pg
->copy
;
5471 defaultConfig
= pg
->address
;
5473 currentConfig
= pg
->address
;
5474 defaultConfig
= NULL
;
5477 dmaoptValue_t currentOpt
= *(dmaoptValue_t
*)((uint8_t *)currentConfig
+ entry
->stride
* index
+ entry
->offset
);
5478 dmaoptValue_t defaultOpt
;
5480 if (defaultConfig
) {
5481 defaultOpt
= *(dmaoptValue_t
*)((uint8_t *)defaultConfig
+ entry
->stride
* index
+ entry
->offset
);
5483 defaultOpt
= DMA_OPT_UNUSED
;
5486 bool equalsDefault
= currentOpt
== defaultOpt
;
5487 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5489 if (defaultConfig
) {
5490 printPeripheralDmaoptDetails(entry
, index
, defaultOpt
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5493 printPeripheralDmaoptDetails(entry
, index
, currentOpt
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5497 #if defined(USE_TIMER_MGMT)
5498 static void printTimerDmaoptDetails(const ioTag_t ioTag
, const timerHardware_t
*timer
, const dmaoptValue_t dmaopt
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5500 const char *format
= "dma pin %c%02d %d";
5502 if (dmaopt
!= DMA_OPT_UNUSED
) {
5503 const bool printDetails
= printValue(dumpMask
, equalsDefault
, format
,
5504 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5509 const dmaChannelSpec_t
*dmaChannelSpec
= dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, dmaopt
);
5510 dmaCode_t dmaCode
= 0;
5511 if (dmaChannelSpec
) {
5512 dmaCode
= dmaChannelSpec
->code
;
5513 printValue(dumpMask
, false,
5514 "# pin %c%02d: " DMASPEC_FORMAT_STRING
,
5515 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5516 DMA_CODE_CONTROLLER(dmaCode
), DMA_CODE_STREAM(dmaCode
), DMA_CODE_CHANNEL(dmaCode
)
5520 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5521 printValue(dumpMask
, equalsDefault
,
5522 "dma pin %c%02d NONE",
5523 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
)
5528 static const char *printTimerDmaopt(const timerIOConfig_t
*currentConfig
, const timerIOConfig_t
*defaultConfig
, unsigned index
, dumpFlags_t dumpMask
, bool tagsInUse
[], const char *headingStr
)
5530 const ioTag_t ioTag
= currentConfig
[index
].ioTag
;
5536 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, currentConfig
[index
].index
);
5537 const dmaoptValue_t dmaopt
= currentConfig
[index
].dmaopt
;
5539 dmaoptValue_t defaultDmaopt
= DMA_OPT_UNUSED
;
5540 bool equalsDefault
= defaultDmaopt
== dmaopt
;
5541 if (defaultConfig
) {
5542 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5543 if (defaultConfig
[i
].ioTag
== ioTag
) {
5544 defaultDmaopt
= defaultConfig
[i
].dmaopt
;
5546 // 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.
5547 equalsDefault
= (defaultDmaopt
== dmaopt
) && (defaultConfig
[i
].index
== currentConfig
[index
].index
|| dmaopt
== DMA_OPT_UNUSED
);
5549 tagsInUse
[index
] = true;
5556 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5558 if (defaultConfig
) {
5559 printTimerDmaoptDetails(ioTag
, timer
, defaultDmaopt
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5562 printTimerDmaoptDetails(ioTag
, timer
, dmaopt
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5567 static void printDmaopt(dumpFlags_t dumpMask
, const char *headingStr
)
5569 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5570 for (size_t i
= 0; i
< ARRAYLEN(dmaoptEntryTable
); i
++) {
5571 dmaoptEntry_t
*entry
= &dmaoptEntryTable
[i
];
5572 for (int index
= 0; index
< entry
->maxIndex
; index
++) {
5573 headingStr
= printPeripheralDmaopt(entry
, index
, dumpMask
, headingStr
);
5577 #if defined(USE_TIMER_MGMT)
5578 const pgRegistry_t
* pg
= pgFind(PG_TIMER_IO_CONFIG
);
5579 const timerIOConfig_t
*currentConfig
;
5580 const timerIOConfig_t
*defaultConfig
;
5582 if (isReadingConfigFromCopy()) {
5583 currentConfig
= (timerIOConfig_t
*)pg
->copy
;
5584 defaultConfig
= (timerIOConfig_t
*)pg
->address
;
5586 currentConfig
= (timerIOConfig_t
*)pg
->address
;
5587 defaultConfig
= NULL
;
5590 bool tagsInUse
[MAX_TIMER_PINMAP_COUNT
] = { false };
5591 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5592 headingStr
= printTimerDmaopt(currentConfig
, defaultConfig
, i
, dumpMask
, tagsInUse
, headingStr
);
5595 if (defaultConfig
) {
5596 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5597 if (!tagsInUse
[i
] && defaultConfig
[i
].ioTag
&& defaultConfig
[i
].dmaopt
!= DMA_OPT_UNUSED
) {
5598 const timerHardware_t
*timer
= timerGetByTagAndIndex(defaultConfig
[i
].ioTag
, defaultConfig
[i
].index
);
5599 headingStr
= cliPrintSectionHeading(dumpMask
, true, headingStr
);
5600 printTimerDmaoptDetails(defaultConfig
[i
].ioTag
, timer
, defaultConfig
[i
].dmaopt
, false, dumpMask
, cliDefaultPrintLinef
);
5602 printTimerDmaoptDetails(defaultConfig
[i
].ioTag
, timer
, DMA_OPT_UNUSED
, false, dumpMask
, cliDumpPrintLinef
);
5609 static void cliDmaopt(const char *cmdName
, char *cmdline
)
5614 // Peripheral name or command option
5615 pch
= strtok_r(cmdline
, " ", &saveptr
);
5617 printDmaopt(DUMP_MASTER
| HIDE_UNUSED
, NULL
);
5620 } else if (strcasecmp(pch
, "list") == 0) {
5621 cliPrintErrorLinef(cmdName
, "NOT IMPLEMENTED YET");
5626 dmaoptEntry_t
*entry
= NULL
;
5627 for (unsigned i
= 0; i
< ARRAYLEN(dmaoptEntryTable
); i
++) {
5628 if (strcasecmp(pch
, dmaoptEntryTable
[i
].device
) == 0) {
5629 entry
= &dmaoptEntryTable
[i
];
5633 if (!entry
&& strcasecmp(pch
, "pin") != 0) {
5634 cliPrintErrorLinef(cmdName
, "BAD DEVICE: %s", pch
);
5639 dmaoptValue_t orgval
= DMA_OPT_UNUSED
;
5642 dmaoptValue_t
*optaddr
= NULL
;
5644 ioTag_t ioTag
= IO_TAG_NONE
;
5645 #if defined(USE_TIMER_MGMT)
5646 timerIOConfig_t
*timerIoConfig
= NULL
;
5648 const timerHardware_t
*timer
= NULL
;
5649 pch
= strtok_r(NULL
, " ", &saveptr
);
5651 index
= pch
? (atoi(pch
) - 1) : -1;
5652 if (index
< 0 || index
>= entry
->maxIndex
|| (entry
->presenceMask
!= MASK_IGNORED
&& !(entry
->presenceMask
& BIT(index
+ 1)))) {
5653 cliPrintErrorLinef(cmdName
, "BAD INDEX: '%s'", pch
? pch
: "");
5657 const pgRegistry_t
* pg
= pgFind(entry
->pgn
);
5658 const void *currentConfig
;
5659 if (isWritingConfigToCopy()) {
5660 currentConfig
= pg
->copy
;
5662 currentConfig
= pg
->address
;
5664 optaddr
= (dmaoptValue_t
*)((uint8_t *)currentConfig
+ entry
->stride
* index
+ entry
->offset
);
5668 if (!pch
|| !(strToPin(pch
, &ioTag
) && IOGetByTag(ioTag
))) {
5669 cliPrintErrorLinef(cmdName
, "INVALID PIN: '%s'", pch
? pch
: "");
5674 orgval
= dmaoptByTag(ioTag
);
5675 #if defined(USE_TIMER_MGMT)
5676 timerIoConfig
= timerIoConfigByTag(ioTag
);
5678 timer
= timerGetConfiguredByTag(ioTag
);
5682 pch
= strtok_r(NULL
, " ", &saveptr
);
5685 printPeripheralDmaoptDetails(entry
, index
, *optaddr
, true, DUMP_MASTER
, cliDumpPrintLinef
);
5687 #if defined(USE_TIMER_MGMT)
5689 printTimerDmaoptDetails(ioTag
, timer
, orgval
, true, DUMP_MASTER
, cliDumpPrintLinef
);
5694 } else if (strcasecmp(pch
, "list") == 0) {
5695 // Show possible opts
5696 const dmaChannelSpec_t
*dmaChannelSpec
;
5698 for (int opt
= 0; (dmaChannelSpec
= dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, opt
)); opt
++) {
5699 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING
, opt
, DMA_CODE_CONTROLLER(dmaChannelSpec
->code
), DMA_CODE_STREAM(dmaChannelSpec
->code
), DMA_CODE_CHANNEL(dmaChannelSpec
->code
));
5702 for (int opt
= 0; (dmaChannelSpec
= dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, opt
)); opt
++) {
5703 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING
, opt
, DMA_CODE_CONTROLLER(dmaChannelSpec
->code
), DMA_CODE_STREAM(dmaChannelSpec
->code
), DMA_CODE_CHANNEL(dmaChannelSpec
->code
));
5710 if (strcasecmp(pch
, "none") == 0) {
5711 optval
= DMA_OPT_UNUSED
;
5716 if (!dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, optval
)) {
5717 cliPrintErrorLinef(cmdName
, "INVALID DMA OPTION FOR %s %d: '%s'", entry
->device
, DMA_OPT_UI_INDEX(index
), pch
);
5722 if (!dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, optval
)) {
5723 cliPrintErrorLinef(cmdName
, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
5730 char optvalString
[DMA_OPT_STRING_BUFSIZE
];
5731 optToString(optval
, optvalString
);
5733 char orgvalString
[DMA_OPT_STRING_BUFSIZE
];
5734 optToString(orgval
, orgvalString
);
5736 if (optval
!= orgval
) {
5740 cliPrintLinef("# dma %s %d: changed from %s to %s", entry
->device
, DMA_OPT_UI_INDEX(index
), orgvalString
, optvalString
);
5742 #if defined(USE_TIMER_MGMT)
5743 timerIoConfig
->dmaopt
= optval
;
5746 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
, optvalString
);
5750 cliPrintLinef("# dma %s %d: no change: %s", entry
->device
, DMA_OPT_UI_INDEX(index
), orgvalString
);
5752 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),orgvalString
);
5757 #endif // USE_DMA_SPEC
5760 static void cliDma(const char *cmdName
, char* cmdline
)
5762 int len
= strlen(cmdline
);
5763 if (len
&& strncasecmp(cmdline
, "show", len
) == 0) {
5769 #if defined(USE_DMA_SPEC)
5770 cliDmaopt(cmdName
, cmdline
);
5772 cliShowParseError(cmdName
);
5776 #endif // USE_RESOURCE_MGMT
5778 #ifdef USE_TIMER_MGMT
5779 static void printTimerDetails(const ioTag_t ioTag
, const unsigned timerIndex
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5781 const char *format
= "timer %c%02d AF%d";
5782 const char *emptyFormat
= "timer %c%02d NONE";
5784 if (timerIndex
> 0) {
5785 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, timerIndex
);
5786 const bool printDetails
= printValue(dumpMask
, equalsDefault
, format
,
5787 IO_GPIOPortIdxByTag(ioTag
) + 'A',
5788 IO_GPIOPinIdxByTag(ioTag
),
5789 timer
->alternateFunction
5792 printValue(dumpMask
, false,
5793 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5794 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5795 timerGetTIMNumber(timer
->tim
),
5796 CC_INDEX_FROM_CHANNEL(timer
->channel
) + 1,
5797 timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : "",
5798 timer
->alternateFunction
5802 printValue(dumpMask
, equalsDefault
, emptyFormat
,
5803 IO_GPIOPortIdxByTag(ioTag
) + 'A',
5804 IO_GPIOPinIdxByTag(ioTag
)
5809 static void printTimer(dumpFlags_t dumpMask
, const char *headingStr
)
5811 const pgRegistry_t
* pg
= pgFind(PG_TIMER_IO_CONFIG
);
5812 const timerIOConfig_t
*currentConfig
;
5813 const timerIOConfig_t
*defaultConfig
;
5815 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5816 if (isReadingConfigFromCopy()) {
5817 currentConfig
= (timerIOConfig_t
*)pg
->copy
;
5818 defaultConfig
= (timerIOConfig_t
*)pg
->address
;
5820 currentConfig
= (timerIOConfig_t
*)pg
->address
;
5821 defaultConfig
= NULL
;
5824 bool tagsInUse
[MAX_TIMER_PINMAP_COUNT
] = { false };
5825 for (unsigned int i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5826 const ioTag_t ioTag
= currentConfig
[i
].ioTag
;
5832 const uint8_t timerIndex
= currentConfig
[i
].index
;
5834 uint8_t defaultTimerIndex
= 0;
5835 if (defaultConfig
) {
5836 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5837 if (defaultConfig
[i
].ioTag
== ioTag
) {
5838 defaultTimerIndex
= defaultConfig
[i
].index
;
5839 tagsInUse
[i
] = true;
5846 const bool equalsDefault
= defaultTimerIndex
== timerIndex
;
5847 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5848 if (defaultConfig
&& defaultTimerIndex
) {
5849 printTimerDetails(ioTag
, defaultTimerIndex
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5852 printTimerDetails(ioTag
, timerIndex
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5855 if (defaultConfig
) {
5856 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5857 if (!tagsInUse
[i
] && defaultConfig
[i
].ioTag
) {
5858 headingStr
= cliPrintSectionHeading(DO_DIFF
, true, headingStr
);
5859 printTimerDetails(defaultConfig
[i
].ioTag
, defaultConfig
[i
].index
, false, dumpMask
, cliDefaultPrintLinef
);
5861 printTimerDetails(defaultConfig
[i
].ioTag
, 0, false, dumpMask
, cliDumpPrintLinef
);
5867 #define TIMER_INDEX_UNDEFINED -1
5868 #define TIMER_AF_STRING_BUFSIZE 5
5870 static void alternateFunctionToString(const ioTag_t ioTag
, const int index
, char *buf
)
5872 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, index
+ 1);
5874 memcpy(buf
, "NONE", TIMER_AF_STRING_BUFSIZE
);
5876 tfp_sprintf(buf
, "AF%d", timer
->alternateFunction
);
5880 static void showTimers(void)
5885 cliPrintLine("Timers:");
5887 cliPrintLine("Currently active Timers:");
5892 for (int i
= 0; (timerNumber
= timerGetNumberByIndex(i
)); i
++) {
5893 cliPrintf("TIM%d:", timerNumber
);
5894 bool timerUsed
= false;
5895 for (unsigned timerIndex
= 0; timerIndex
< CC_CHANNELS_PER_TIMER
; timerIndex
++) {
5896 const timerHardware_t
*timer
= timerGetAllocatedByNumberAndChannel(timerNumber
, CC_CHANNEL_FROM_INDEX(timerIndex
));
5897 const resourceOwner_t
*timerOwner
= timerGetOwner(timer
);
5898 if (timerOwner
->owner
) {
5905 if (timerOwner
->resourceIndex
> 0) {
5906 cliPrintLinef(" CH%d%s: %s %d", timerIndex
+ 1, timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : " ", ownerNames
[timerOwner
->owner
], timerOwner
->resourceIndex
);
5908 cliPrintLinef(" CH%d%s: %s", timerIndex
+ 1, timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : " ", ownerNames
[timerOwner
->owner
]);
5914 cliPrintLine(" FREE");
5919 static void cliTimer(const char *cmdName
, char *cmdline
)
5921 int len
= strlen(cmdline
);
5924 printTimer(DUMP_MASTER
, NULL
);
5927 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
5928 cliPrintErrorLinef(cmdName
, "NOT IMPLEMENTED YET");
5931 } else if (strncasecmp(cmdline
, "show", len
) == 0) {
5940 ioTag_t ioTag
= IO_TAG_NONE
;
5941 pch
= strtok_r(cmdline
, " ", &saveptr
);
5942 if (!pch
|| !strToPin(pch
, &ioTag
)) {
5943 cliShowParseError(cmdName
);
5946 } else if (!IOGetByTag(ioTag
)) {
5947 cliPrintErrorLinef(cmdName
, "PIN NOT USED ON BOARD.");
5952 int timerIOIndex
= TIMER_INDEX_UNDEFINED
;
5953 bool isExistingTimerOpt
= false;
5954 /* find existing entry, or go for next available */
5955 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5956 if (timerIOConfig(i
)->ioTag
== ioTag
) {
5958 isExistingTimerOpt
= true;
5963 /* first available empty slot */
5964 if (timerIOIndex
< 0 && timerIOConfig(i
)->ioTag
== IO_TAG_NONE
) {
5969 if (timerIOIndex
< 0) {
5970 cliPrintErrorLinef(cmdName
, "PIN TIMER MAP FULL.");
5975 pch
= strtok_r(NULL
, " ", &saveptr
);
5977 int timerIndex
= TIMER_INDEX_UNDEFINED
;
5978 if (strcasecmp(pch
, "list") == 0) {
5979 /* output the list of available options */
5980 const timerHardware_t
*timer
;
5981 for (unsigned index
= 0; (timer
= timerGetByTagAndIndex(ioTag
, index
+ 1)); index
++) {
5982 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5983 timer
->alternateFunction
,
5984 timerGetTIMNumber(timer
->tim
),
5985 CC_INDEX_FROM_CHANNEL(timer
->channel
) + 1,
5986 timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : ""
5991 } else if (strncasecmp(pch
, "af", 2) == 0) {
5992 unsigned alternateFunction
= atoi(&pch
[2]);
5994 const timerHardware_t
*timer
;
5995 for (unsigned index
= 0; (timer
= timerGetByTagAndIndex(ioTag
, index
+ 1)); index
++) {
5996 if (timer
->alternateFunction
== alternateFunction
) {
6004 cliPrintErrorLinef(cmdName
, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
6008 } else if (strcasecmp(pch
, "none") != 0) {
6009 cliPrintErrorLinef(cmdName
, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
6014 int oldTimerIndex
= isExistingTimerOpt
? timerIOConfig(timerIOIndex
)->index
- 1 : -1;
6015 timerIOConfigMutable(timerIOIndex
)->ioTag
= timerIndex
== TIMER_INDEX_UNDEFINED
? IO_TAG_NONE
: ioTag
;
6016 timerIOConfigMutable(timerIOIndex
)->index
= timerIndex
+ 1;
6017 timerIOConfigMutable(timerIOIndex
)->dmaopt
= DMA_OPT_UNUSED
;
6019 char optvalString
[DMA_OPT_STRING_BUFSIZE
];
6020 alternateFunctionToString(ioTag
, timerIndex
, optvalString
);
6022 char orgvalString
[DMA_OPT_STRING_BUFSIZE
];
6023 alternateFunctionToString(ioTag
, oldTimerIndex
, orgvalString
);
6025 if (timerIndex
== oldTimerIndex
) {
6026 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
);
6028 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
, optvalString
);
6033 printTimerDetails(ioTag
, timerIOConfig(timerIOIndex
)->index
, false, DUMP_MASTER
, cliDumpPrintLinef
);
6040 #if defined(USE_RESOURCE_MGMT)
6041 static void cliResource(const char *cmdName
, char *cmdline
)
6046 pch
= strtok_r(cmdline
, " ", &saveptr
);
6048 printResource(DUMP_MASTER
| HIDE_UNUSED
, NULL
);
6051 } else if (strcasecmp(pch
, "show") == 0) {
6055 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
6058 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
6060 owner
= ownerNames
[ioRecs
[i
].owner
];
6062 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
);
6063 if (ioRecs
[i
].index
> 0) {
6064 cliPrintf(" %d", ioRecs
[i
].index
);
6069 pch
= strtok_r(NULL
, " ", &saveptr
);
6070 if (strcasecmp(pch
, "all") == 0) {
6071 #if defined(USE_TIMER_MGMT)
6072 cliTimer(cmdName
, "show");
6074 #if defined(USE_DMA)
6075 cliDma(cmdName
, "show");
6082 unsigned resourceIndex
= 0;
6083 for (; ; resourceIndex
++) {
6084 if (resourceIndex
>= ARRAYLEN(resourceTable
)) {
6085 cliPrintErrorLinef(cmdName
, "INVALID RESOURCE NAME: '%s'", pch
);
6089 const char *resourceName
= ownerNames
[resourceTable
[resourceIndex
].owner
];
6090 if (strncasecmp(pch
, resourceName
, strlen(resourceName
)) == 0) {
6095 pch
= strtok_r(NULL
, " ", &saveptr
);
6096 int index
= atoi(pch
);
6098 if (resourceTable
[resourceIndex
].maxIndex
> 0 || index
> 0) {
6099 if (index
<= 0 || index
> RESOURCE_VALUE_MAX_INDEX(resourceTable
[resourceIndex
].maxIndex
)) {
6100 cliShowArgumentRangeError(cmdName
, "INDEX", 1, RESOURCE_VALUE_MAX_INDEX(resourceTable
[resourceIndex
].maxIndex
));
6105 pch
= strtok_r(NULL
, " ", &saveptr
);
6108 ioTag_t
*tag
= getIoTag(resourceTable
[resourceIndex
], index
);
6110 if (strlen(pch
) > 0) {
6111 if (strToPin(pch
, tag
)) {
6112 if (*tag
== IO_TAG_NONE
) {
6114 cliPrintLine("Freed");
6116 cliPrintLine("Resource is freed");
6120 ioRec_t
*rec
= IO_Rec(IOGetByTag(*tag
));
6122 resourceCheck(resourceIndex
, index
, *tag
);
6124 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
6126 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
6129 cliShowParseError(cmdName
);
6136 cliShowParseError(cmdName
);
6140 #ifdef USE_DSHOT_TELEMETRY
6143 static void cliDshotTelemetryInfo(const char *cmdName
, char *cmdline
)
6148 if (useDshotTelemetry
) {
6149 cliPrintLinef("Dshot reads: %u", dshotTelemetryState
.readCount
);
6150 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState
.invalidPacketCount
);
6151 int32_t directionChangeCycles
= cmp32(dshotDMAHandlerCycleCounters
.changeDirectionCompletedAt
, dshotDMAHandlerCycleCounters
.irqAt
);
6152 int32_t directionChangeDurationUs
= clockCyclesToMicros(directionChangeCycles
);
6153 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles
, directionChangeDurationUs
);
6156 #ifdef USE_DSHOT_TELEMETRY_STATS
6157 cliPrintLine("Motor Type eRPM RPM Hz Invalid TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6158 cliPrintLine("===== ====== ====== ====== ====== ======= ====== ====== ====== ====== ====== ====== ======");
6160 cliPrintLine("Motor Type eRPM RPM Hz TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6161 cliPrintLine("===== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======");
6164 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
6165 const uint16_t erpm
= getDshotTelemetry(i
);
6166 const uint16_t rpm
= erpmToRpm(erpm
);
6168 cliPrintf("%5d %c%c%c%c%c %6d %6d %6d",
6170 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_eRPM
)) ? 'R' : '-'),
6171 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE
)) ? 'T' : '-'),
6172 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_VOLTAGE
)) ? 'V' : '-'),
6173 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_CURRENT
)) ? 'C' : '-'),
6174 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_STATE_EVENTS
)) ? 'S' : '-'),
6175 erpm
* 100, rpm
, rpm
/ 60);
6177 #ifdef USE_DSHOT_TELEMETRY_STATS
6178 if (isDshotMotorTelemetryActive(i
)) {
6179 int32_t calcPercent
= getDshotTelemetryMotorInvalidPercent(i
);
6180 cliPrintf(" %3d.%02d%%", calcPercent
/ 100, calcPercent
% 100);
6182 cliPrint(" NO DATA");
6186 cliPrintLinef(" %6d %3d.%02d %6d %6d %6d %6d %6d",
6187 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_TEMPERATURE
],
6188 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_VOLTAGE
] / 4,
6189 25 * (dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_VOLTAGE
] % 4),
6190 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_CURRENT
],
6191 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_STATE_EVENTS
],
6192 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_DEBUG1
],
6193 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_DEBUG2
],
6194 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_DEBUG3
]
6199 const int len
= MAX_GCR_EDGES
;
6200 #ifdef DEBUG_BBDECODE
6201 extern uint16_t bbBuffer
[134];
6202 for (int i
= 0; i
< 134; i
++) {
6203 cliPrintf("%u ", (int)bbBuffer
[i
]);
6207 for (int i
= 0; i
< len
; i
++) {
6208 cliPrintf("%u ", (int)dshotTelemetryState
.inputBuffer
[i
]);
6211 for (int i
= 1; i
< len
; i
++) {
6212 cliPrintf("%u ", (int)(dshotTelemetryState
.inputBuffer
[i
] - dshotTelemetryState
.inputBuffer
[i
-1]));
6216 cliPrintLine("Dshot telemetry not enabled");
6222 static void printConfig(const char *cmdName
, char *cmdline
, bool doDiff
)
6224 dumpFlags_t dumpMask
= DUMP_MASTER
;
6226 if ((options
= checkCommand(cmdline
, "master"))) {
6227 dumpMask
= DUMP_MASTER
; // only
6228 } else if ((options
= checkCommand(cmdline
, "profile"))) {
6229 dumpMask
= DUMP_PROFILE
; // only
6230 } else if ((options
= checkCommand(cmdline
, "rates"))) {
6231 dumpMask
= DUMP_RATES
; // only
6232 } else if ((options
= checkCommand(cmdline
, "hardware"))) {
6233 dumpMask
= DUMP_MASTER
| HARDWARE_ONLY
; // Show only hardware related settings (useful to generate unified target configs).
6234 } else if ((options
= checkCommand(cmdline
, "all"))) {
6235 dumpMask
= DUMP_ALL
; // all profiles and rates
6241 dumpMask
= dumpMask
| DO_DIFF
;
6244 if (checkCommand(options
, "defaults")) {
6245 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
6246 } else if (checkCommand(options
, "bare")) {
6247 dumpMask
= dumpMask
| BARE
; // show the diff / dump without extra commands and board specific data
6250 backupAndResetConfigs((dumpMask
& BARE
) == 0);
6252 #ifdef USE_CLI_BATCH
6253 bool batchModeEnabled
= false;
6255 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
6256 cliPrintHashLine("version");
6257 printVersion(cmdName
, false);
6259 if (!(dumpMask
& BARE
)) {
6260 #ifdef USE_CLI_BATCH
6261 cliPrintHashLine("start the command batch");
6262 cliPrintLine("batch start");
6263 batchModeEnabled
= true;
6266 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
6267 cliPrintHashLine("reset configuration to default settings");
6268 cliPrintLine("defaults nosave");
6272 #if defined(USE_BOARD_INFO)
6274 printBoardName(dumpMask
);
6275 printManufacturerId(dumpMask
);
6278 if ((dumpMask
& DUMP_ALL
) && !(dumpMask
& BARE
)) {
6279 cliMcuId(cmdName
, "");
6280 #if defined(USE_SIGNATURE)
6281 cliSignature(cmdName
, "");
6285 if (!(dumpMask
& HARDWARE_ONLY
)) {
6286 printCraftName(dumpMask
, &pilotConfig_Copy
);
6289 #ifdef USE_RESOURCE_MGMT
6290 printResource(dumpMask
, "resources");
6291 #if defined(USE_TIMER_MGMT)
6292 printTimer(dumpMask
, "timer");
6295 printDmaopt(dumpMask
, "dma");
6299 printFeature(dumpMask
, featureConfig_Copy
.enabledFeatures
, featureConfig()->enabledFeatures
, "feature");
6301 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig(), "serial");
6303 if (!(dumpMask
& HARDWARE_ONLY
)) {
6304 #ifndef USE_QUAD_MIXER_ONLY
6305 const char *mixerHeadingStr
= "mixer";
6306 const bool equalsDefault
= mixerConfig_Copy
.mixerMode
== mixerConfig()->mixerMode
;
6307 mixerHeadingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, mixerHeadingStr
);
6308 const char *formatMixer
= "mixer %s";
6309 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig()->mixerMode
- 1]);
6310 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig_Copy
.mixerMode
- 1]);
6312 cliDumpPrintLinef(dumpMask
, customMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
6314 printMotorMix(dumpMask
, customMotorMixer_CopyArray
, customMotorMixer(0), mixerHeadingStr
);
6317 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0), "servo");
6319 const char *servoMixHeadingStr
= "servo mixer";
6320 if (!(dumpMask
& DO_DIFF
) || customServoMixers(0)->rate
!= 0) {
6321 cliPrintHashLine(servoMixHeadingStr
);
6322 cliPrintLine("smix reset\r\n");
6323 servoMixHeadingStr
= NULL
;
6325 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0), servoMixHeadingStr
);
6329 #if defined(USE_BEEPER)
6330 printBeeper(dumpMask
, beeperConfig_Copy
.beeper_off_flags
, beeperConfig()->beeper_off_flags
, "beeper", BEEPER_ALLOWED_MODES
, "beeper");
6332 #if defined(USE_DSHOT)
6333 printBeeper(dumpMask
, beeperConfig_Copy
.dshotBeaconOffFlags
, beeperConfig()->dshotBeaconOffFlags
, "beacon", DSHOT_BEACON_ALLOWED_MODES
, "beacon");
6335 #endif // USE_BEEPER
6337 printMap(dumpMask
, &rxConfig_Copy
, rxConfig(), "map");
6339 #ifdef USE_LED_STRIP_STATUS_MODE
6340 printLed(dumpMask
, ledStripStatusModeConfig_Copy
.ledConfigs
, ledStripStatusModeConfig()->ledConfigs
, "led");
6342 printColor(dumpMask
, ledStripStatusModeConfig_Copy
.colors
, ledStripStatusModeConfig()->colors
, "color");
6344 printModeColor(dumpMask
, &ledStripStatusModeConfig_Copy
, ledStripStatusModeConfig(), "mode_color");
6347 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0), "aux");
6349 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0), "adjrange");
6351 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0), "rxrange");
6353 #ifdef USE_VTX_TABLE
6354 printVtxTable(dumpMask
, &vtxTableConfig_Copy
, vtxTableConfig(), "vtxtable");
6357 #ifdef USE_VTX_CONTROL
6358 printVtx(dumpMask
, &vtxConfig_Copy
, vtxConfig(), "vtx");
6361 printRxFailsafe(dumpMask
, rxFailsafeChannelConfigs_CopyArray
, rxFailsafeChannelConfigs(0), "rxfail");
6364 if (dumpMask
& HARDWARE_ONLY
) {
6365 dumpAllValues(cmdName
, HARDWARE_VALUE
, dumpMask
, "master");
6367 dumpAllValues(cmdName
, MASTER_VALUE
, dumpMask
, "master");
6369 if (dumpMask
& DUMP_ALL
) {
6370 for (uint32_t pidProfileIndex
= 0; pidProfileIndex
< PID_PROFILE_COUNT
; pidProfileIndex
++) {
6371 cliDumpPidProfile(cmdName
, pidProfileIndex
, dumpMask
);
6374 pidProfileIndexToUse
= systemConfig_Copy
.pidProfileIndex
;
6376 if (!(dumpMask
& BARE
)) {
6377 cliPrintHashLine("restore original profile selection");
6379 cliProfile(cmdName
, "");
6382 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
6384 for (uint32_t rateIndex
= 0; rateIndex
< CONTROL_RATE_PROFILE_COUNT
; rateIndex
++) {
6385 cliDumpRateProfile(cmdName
, rateIndex
, dumpMask
);
6388 rateProfileIndexToUse
= systemConfig_Copy
.activeRateProfile
;
6390 if (!(dumpMask
& BARE
)) {
6391 cliPrintHashLine("restore original rateprofile selection");
6393 cliRateProfile(cmdName
, "");
6395 cliPrintHashLine("save configuration");
6397 #ifdef USE_CLI_BATCH
6398 batchModeEnabled
= false;
6402 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
6404 cliDumpPidProfile(cmdName
, systemConfig_Copy
.pidProfileIndex
, dumpMask
);
6406 cliDumpRateProfile(cmdName
, systemConfig_Copy
.activeRateProfile
, dumpMask
);
6409 } else if (dumpMask
& DUMP_PROFILE
) {
6410 cliDumpPidProfile(cmdName
, systemConfig_Copy
.pidProfileIndex
, dumpMask
);
6411 } else if (dumpMask
& DUMP_RATES
) {
6412 cliDumpRateProfile(cmdName
, systemConfig_Copy
.activeRateProfile
, dumpMask
);
6415 #ifdef USE_CLI_BATCH
6416 if (batchModeEnabled
) {
6417 cliPrintHashLine("end the command batch");
6418 cliPrintLine("batch end");
6422 // restore configs from copies
6426 static void cliDump(const char *cmdName
, char *cmdline
)
6428 printConfig(cmdName
, cmdline
, false);
6431 static void cliDiff(const char *cmdName
, char *cmdline
)
6433 printConfig(cmdName
, cmdline
, true);
6436 #if defined(USE_USB_MSC)
6437 static void cliMsc(const char *cmdName
, char *cmdline
)
6439 if (mscCheckFilesystemReady()) {
6441 int timezoneOffsetMinutes
= timeConfig()->tz_offsetMinutes
;
6442 if (!isEmpty(cmdline
)) {
6443 timezoneOffsetMinutes
= atoi(cmdline
);
6444 if ((timezoneOffsetMinutes
< TIMEZONE_OFFSET_MINUTES_MIN
) || (timezoneOffsetMinutes
> TIMEZONE_OFFSET_MINUTES_MAX
)) {
6445 cliPrintErrorLinef(cmdName
, "INVALID TIMEZONE OFFSET");
6450 int timezoneOffsetMinutes
= 0;
6453 cliPrintHashLine("Restarting in mass storage mode");
6454 cliPrint("\r\nRebooting");
6456 waitForSerialPortToFinishTransmitting(cliPort
);
6459 systemResetToMsc(timezoneOffsetMinutes
);
6461 cliPrintHashLine("Storage not present or failed to initialize!");
6466 typedef void cliCommandFn(const char* name
, char *cmdline
);
6471 const char *description
;
6474 cliCommandFn
*cliCommand
;
6478 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6486 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6493 static void cliHelp(const char *cmdName
, char *cmdline
);
6495 // should be sorted a..z for bsearch()
6496 const clicmd_t cmdTable
[] = {
6497 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange
),
6498 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux
),
6499 #ifdef USE_CLI_BATCH
6500 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch
),
6502 #if defined(USE_BEEPER)
6503 #if defined(USE_DSHOT)
6504 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6505 "\t<->[name]", cliBeacon
),
6507 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6508 "\t<->[name]", cliBeeper
),
6509 #endif // USE_BEEPER
6510 #if defined(USE_RX_BIND)
6511 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL
, cliRxBind
),
6513 #if defined(USE_FLASH_BOOT_LOADER)
6514 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader
),
6516 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader
),
6518 #if defined(USE_BOARD_INFO)
6519 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName
),
6521 #ifdef USE_LED_STRIP_STATUS_MODE
6522 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
6524 #if defined(USE_CUSTOM_DEFAULTS)
6525 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{show} {nosave} {bare} {group_id <id>}", cliDefaults
),
6527 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{nosave}", cliDefaults
),
6529 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff
),
6530 #ifdef USE_RESOURCE_MGMT
6534 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma
),
6536 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma
),
6541 #ifdef USE_DSHOT_TELEMETRY
6542 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL
, cliDshotTelemetryInfo
),
6545 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg
),
6547 CLI_COMMAND_DEF("dump", "dump configuration",
6548 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump
),
6549 #ifdef USE_ESCSERIAL
6550 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough
),
6552 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
6553 CLI_COMMAND_DEF("feature", "configure features",
6555 "\t<->[name]", cliFeature
),
6557 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
6558 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
6559 #ifdef USE_FLASH_TOOLS
6560 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
6561 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL
, cliFlashVerify
),
6562 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
6565 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
6567 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
6569 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6570 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL
, cliDumpGyroRegisters
),
6572 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp
),
6573 #ifdef USE_LED_STRIP_STATUS_MODE
6574 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
6576 #if defined(USE_BOARD_INFO)
6577 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId
),
6579 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
6580 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL
, cliMcuId
),
6581 #ifndef USE_QUAD_MIXER_ONLY
6582 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer
),
6584 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
6585 #ifdef USE_LED_STRIP_STATUS_MODE
6586 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
6588 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
6591 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc
),
6593 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
6597 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]", cliPlaySound
),
6599 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile
),
6600 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile
),
6601 #ifdef USE_RC_SMOOTHING_FILTER
6602 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL
, cliRcSmoothing
),
6603 #endif // USE_RC_SMOOTHING_FILTER
6604 #ifdef USE_RESOURCE_MGMT
6605 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource
),
6607 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL
, cliRxFailsafe
),
6608 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
6609 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
6611 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
6613 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
6614 #if defined(USE_SERIAL_PASSTHROUGH)
6615 #if defined(USE_PINIO)
6616 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|<dtr pinio>|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough
),
6618 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough
),
6622 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
6624 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
6625 #if defined(USE_SIGNATURE)
6626 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature
),
6628 #if defined(USE_SIMPLIFIED_TUNING)
6629 CLI_COMMAND_DEF("simplified_tuning", "applies or disables simplified tuning", "apply | disable", cliSimplifiedTuning
),
6632 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6634 "\tload <mixer>\r\n"
6635 "\treverse <servo> <source> r|n", cliServoMix
),
6637 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
6638 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
6639 #ifdef USE_TIMER_MGMT
6640 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer
),
6642 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
6643 #ifdef USE_VTX_CONTROL
6645 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL
, cliVtx
),
6647 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx
),
6650 #ifdef USE_VTX_TABLE
6651 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL
, cliVtxInfo
),
6652 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable
),
6656 static void cliHelp(const char *cmdName
, char *cmdline
)
6658 bool anyMatches
= false;
6660 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
6661 bool printEntry
= false;
6662 if (isEmpty(cmdline
)) {
6665 if (strcasestr(cmdTable
[i
].name
, cmdline
)
6667 || strcasestr(cmdTable
[i
].description
, cmdline
)
6676 cliPrint(cmdTable
[i
].name
);
6678 if (cmdTable
[i
].description
) {
6679 cliPrintf(" - %s", cmdTable
[i
].description
);
6681 if (cmdTable
[i
].args
) {
6682 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
6688 if (!isEmpty(cmdline
) && !anyMatches
) {
6689 cliPrintErrorLinef(cmdName
, "NO MATCHES FOR '%s'", cmdline
);
6693 static void processCharacter(const char c
)
6695 if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
6699 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
6700 if (processingCustomDefaults
) {
6705 // Strip comment starting with # from line
6706 char *p
= cliBuffer
;
6709 bufferIndex
= (uint32_t)(p
- cliBuffer
);
6712 // Strip trailing whitespace
6713 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
6717 // Process non-empty lines
6718 if (bufferIndex
> 0) {
6719 cliBuffer
[bufferIndex
] = 0; // null terminate
6721 const clicmd_t
*cmd
;
6723 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
6724 if ((options
= checkCommand(cliBuffer
, cmd
->name
))) {
6728 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
)) {
6729 cmd
->cliCommand(cmd
->name
, options
);
6731 cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
6736 memset(cliBuffer
, 0, sizeof(cliBuffer
));
6738 // 'exit' will reset this flag, so we don't need to print prompt again
6744 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
6745 if (!bufferIndex
&& c
== ' ')
6746 return; // Ignore leading spaces
6747 cliBuffer
[bufferIndex
++] = c
;
6752 static void processCharacterInteractive(const char c
)
6754 if (c
== '\t' || c
== '?') {
6755 // do tab completion
6756 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
6757 uint32_t i
= bufferIndex
;
6758 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
6759 if (bufferIndex
&& (strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0)) {
6767 if (pstart
) { /* Buffer matches one or more commands */
6768 for (; ; bufferIndex
++) {
6769 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
6771 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
6772 /* Unambiguous -- append a space */
6773 cliBuffer
[bufferIndex
++] = ' ';
6774 cliBuffer
[bufferIndex
] = '\0';
6777 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
6780 if (!bufferIndex
|| pstart
!= pend
) {
6781 /* Print list of ambiguous matches */
6782 cliPrint("\r\n\033[K");
6783 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
6784 cliPrint(cmd
->name
);
6788 i
= 0; /* Redraw prompt */
6790 for (; i
< bufferIndex
; i
++)
6791 cliWrite(cliBuffer
[i
]);
6792 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
6793 cliExit("", cliBuffer
);
6795 } else if (c
== 12) { // NewPage / CTRL-L
6797 cliPrint("\033[2J\033[1;1H");
6799 } else if (c
== 127) {
6802 cliBuffer
[--bufferIndex
] = 0;
6803 cliPrint("\010 \010");
6806 processCharacter(c
);
6810 void cliProcess(void)
6816 // Flush the buffer to get rid of any MSP data polls sent by configurator after CLI was invoked
6819 while (serialRxBytesWaiting(cliPort
)) {
6820 uint8_t c
= serialRead(cliPort
);
6822 processCharacterInteractive(c
);
6826 #if defined(USE_CUSTOM_DEFAULTS)
6827 static bool cliProcessCustomDefaults(bool quiet
)
6829 if (processingCustomDefaults
|| !hasCustomDefaults()) {
6833 bufWriter_t
*cliWriterTemp
= NULL
;
6835 #if !defined(DEBUG_CUSTOM_DEFAULTS)
6839 cliWriterTemp
= cliWriter
;
6843 cliErrorWriter
= NULL
;
6846 memcpy(cliBufferTemp
, cliBuffer
, sizeof(cliBuffer
));
6847 uint32_t bufferIndexTemp
= bufferIndex
;
6849 processingCustomDefaults
= true;
6851 char *customDefaultsPtr
= customDefaultsStart
;
6852 while (customDefaultsHasNext(customDefaultsPtr
)) {
6853 processCharacter(*customDefaultsPtr
++);
6856 // Process a newline at the very end so that the last command gets executed,
6857 // even when the file did not contain a trailing newline
6858 processCharacter('\r');
6860 processingCustomDefaults
= false;
6862 if (cliWriterTemp
) {
6863 cliWriter
= cliWriterTemp
;
6864 cliErrorWriter
= cliWriter
;
6867 memcpy(cliBuffer
, cliBufferTemp
, sizeof(cliBuffer
));
6868 bufferIndex
= bufferIndexTemp
;
6870 systemConfigMutable()->configurationState
= CONFIGURATION_STATE_DEFAULTS_CUSTOM
;
6876 void cliEnter(serialPort_t
*serialPort
)
6879 cliPort
= serialPort
;
6880 setPrintfSerialPort(cliPort
);
6881 bufWriterInit(&cliWriterDesc
, cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
6882 cliErrorWriter
= cliWriter
= &cliWriterDesc
;
6885 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6887 cliPrintLine("\r\nCLI");
6889 setArmingDisabled(ARMING_DISABLED_CLI
);
6893 #ifdef USE_CLI_BATCH
6894 resetCommandBatch();