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_impl.h"
74 #include "drivers/compass/compass.h"
75 #include "drivers/display.h"
76 #include "drivers/dma.h"
77 #include "drivers/flash/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/pilot.h"
143 #include "pg/pinio.h"
144 #include "pg/pin_pull_up_down.h"
146 #include "pg/pg_ids.h"
148 #include "pg/rx_pwm.h"
149 #include "pg/rx_spi_cc2500.h"
150 #include "pg/rx_spi_expresslrs.h"
151 #include "pg/serial_uart.h"
153 #include "pg/timerio.h"
154 #include "pg/timerup.h"
156 #include "pg/vtx_table.h"
158 #include "rx/rx_bind.h"
159 #include "rx/rx_spi.h"
161 #include "scheduler/scheduler.h"
163 #include "sensors/acceleration.h"
164 #include "sensors/adcinternal.h"
165 #include "sensors/barometer.h"
166 #include "sensors/battery.h"
167 #include "sensors/boardalignment.h"
168 #include "sensors/compass.h"
169 #include "sensors/gyro.h"
170 #include "sensors/gyro_init.h"
171 #include "sensors/sensors.h"
173 #include "telemetry/frsky_hub.h"
174 #include "telemetry/telemetry.h"
178 static serialPort_t
*cliPort
= NULL
;
179 static bool cliInteractive
= false;
180 static timeMs_t cliEntryTime
= 0;
182 // Space required to set array parameters
183 #define CLI_IN_BUFFER_SIZE 256
184 #define CLI_OUT_BUFFER_SIZE 64
186 static bufWriter_t cliWriterDesc
;
187 static bufWriter_t
*cliWriter
= NULL
;
188 static bufWriter_t
*cliErrorWriter
= NULL
;
189 static uint8_t cliWriteBuffer
[CLI_OUT_BUFFER_SIZE
];
191 static char cliBuffer
[CLI_IN_BUFFER_SIZE
];
192 static uint32_t bufferIndex
= 0;
194 static bool configIsInCopy
= false;
196 #define CURRENT_PROFILE_INDEX -1
197 static int8_t pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
198 static int8_t rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
201 static bool commandBatchActive
= false;
202 static bool commandBatchError
= false;
205 #if defined(USE_BOARD_INFO)
206 static bool boardInformationUpdated
= false;
207 #if defined(USE_SIGNATURE)
208 static bool signatureUpdated
= false;
210 #endif // USE_BOARD_INFO
212 static const char* const emptyName
= "-";
213 static const char* const invalidName
= "INVALID";
215 #define MAX_CHANGESET_ID_LENGTH 8
216 #define MAX_DATE_LENGTH 20
218 #define ERROR_INVALID_NAME "INVALID NAME: %s"
219 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
221 #ifndef USE_QUAD_MIXER_ONLY
222 // sync this with mixerMode_e
223 static const char * const mixerNames
[] = {
224 "TRI", "QUADP", "QUADX", "BI",
225 "GIMBAL", "Y6", "HEX6",
226 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
227 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
228 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
229 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
233 // sync this with features_e
234 #define _R(_flag, _name) [LOG2(_flag)] = _name
235 static const char * const featureNames
[] = {
236 _R(FEATURE_RX_PPM
, "RX_PPM"),
237 _R(FEATURE_INFLIGHT_ACC_CAL
, "INFLIGHT_ACC_CAL"),
238 _R(FEATURE_RX_SERIAL
, "RX_SERIAL"),
239 _R(FEATURE_MOTOR_STOP
, "MOTOR_STOP"),
240 _R(FEATURE_SERVO_TILT
, "SERVO_TILT"),
241 _R(FEATURE_SOFTSERIAL
, "SOFTSERIAL"),
242 _R(FEATURE_GPS
, "GPS"),
243 _R(FEATURE_RANGEFINDER
, "RANGEFINDER"),
244 _R(FEATURE_OPTICALFLOW
, "OPTICALFLOW"),
245 _R(FEATURE_TELEMETRY
, "TELEMETRY"),
246 _R(FEATURE_3D
, "3D"),
247 _R(FEATURE_RX_PARALLEL_PWM
, "RX_PARALLEL_PWM"),
248 _R(FEATURE_RSSI_ADC
, "RSSI_ADC"),
249 _R(FEATURE_LED_STRIP
, "LED_STRIP"),
250 _R(FEATURE_DASHBOARD
, "DISPLAY"),
251 _R(FEATURE_OSD
, "OSD"),
252 _R(FEATURE_CHANNEL_FORWARDING
, "CHANNEL_FORWARDING"),
253 _R(FEATURE_TRANSPONDER
, "TRANSPONDER"),
254 _R(FEATURE_AIRMODE
, "AIRMODE"),
255 _R(FEATURE_RX_SPI
, "RX_SPI"),
256 _R(FEATURE_ESC_SENSOR
, "ESC_SENSOR"),
257 _R(FEATURE_ANTI_GRAVITY
, "ANTI_GRAVITY"),
261 // sync this with rxFailsafeChannelMode_e
262 static const char rxFailsafeModeCharacters
[] = "ahs";
264 static const rxFailsafeChannelMode_e rxFailsafeModesTable
[RX_FAILSAFE_TYPE_COUNT
][RX_FAILSAFE_MODE_COUNT
] = {
265 { RX_FAILSAFE_MODE_AUTO
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
},
266 { RX_FAILSAFE_MODE_INVALID
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
}
269 #if defined(USE_SENSOR_NAMES)
270 // sync this with sensors_e
271 static const char *const sensorTypeNames
[] = {
272 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
275 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
277 static const char * const *sensorHardwareNames
[] = {
278 lookupTableGyroHardware
, lookupTableAccHardware
, lookupTableBaroHardware
, lookupTableMagHardware
, lookupTableRangefinderHardware
, lookupTableOpticalflowHardware
280 #endif // USE_SENSOR_NAMES
282 // Needs to be aligned with mcuTypeId_e
283 static const char *mcuTypeNames
[] = {
293 "H743 (Rev Unknown)",
306 static const char *configurationStates
[] = {
307 [CONFIGURATION_STATE_UNCONFIGURED
] = "UNCONFIGURED",
308 [CONFIGURATION_STATE_CONFIGURED
] = "CONFIGURED"
311 typedef enum dumpFlags_e
{
312 DUMP_MASTER
= (1 << 0),
313 DUMP_PROFILE
= (1 << 1),
314 DUMP_RATES
= (1 << 2),
317 SHOW_DEFAULTS
= (1 << 5),
318 HIDE_UNUSED
= (1 << 6),
319 HARDWARE_ONLY
= (1 << 7),
323 static void cliExit(const bool reboot
);
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
{
333 serialPortIdentifier_e id
;
336 portOptions_e options
;
338 } serialPassthroughPort_t
;
340 static void cliClearInputBuffer(void)
342 memset(cliBuffer
, 0, sizeof(cliBuffer
));
346 static void cliWriterFlushInternal(bufWriter_t
*writer
)
349 bufWriterFlush(writer
);
353 static void cliPrintInternal(bufWriter_t
*writer
, const char *str
)
357 bufWriterAppend(writer
, *str
++);
359 cliWriterFlushInternal(writer
);
363 static void cliWriterFlush(void)
365 cliWriterFlushInternal(cliWriter
);
368 void cliPrint(const char *str
)
370 cliPrintInternal(cliWriter
, str
);
373 void cliPrintLinefeed(void)
378 void cliPrintLine(const char *str
)
385 #define cliPrintHashLine(str)
387 static void cliPrintHashLine(const char *str
)
394 static void cliPutp(void *p
, char ch
)
396 bufWriterAppend(p
, ch
);
399 static void cliPrintfva(const char *format
, va_list va
)
402 tfp_format(cliWriter
, cliPutp
, format
, va
);
407 static bool cliDumpPrintLinef(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...)
409 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
411 va_start(va
, format
);
412 cliPrintfva(format
, va
);
421 static void cliWrite(uint8_t ch
)
424 bufWriterAppend(cliWriter
, ch
);
428 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...)
430 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
434 va_start(va
, format
);
435 cliPrintfva(format
, va
);
444 void cliPrintf(const char *format
, ...)
447 va_start(va
, format
);
448 cliPrintfva(format
, va
);
452 void cliPrintLinef(const char *format
, ...)
455 va_start(va
, format
);
456 cliPrintfva(format
, va
);
461 static void cliPrintErrorVa(const char *cmdName
, const char *format
, va_list va
)
463 if (cliErrorWriter
) {
464 cliPrintInternal(cliErrorWriter
, "###ERROR IN ");
465 cliPrintInternal(cliErrorWriter
, cmdName
);
466 cliPrintInternal(cliErrorWriter
, ": ");
468 tfp_format(cliErrorWriter
, cliPutp
, format
, va
);
471 cliPrintInternal(cliErrorWriter
, "###");
475 if (commandBatchActive
) {
476 commandBatchError
= true;
481 static void cliPrintError(const char *cmdName
, const char *format
, ...)
484 va_start(va
, format
);
485 cliPrintErrorVa(cmdName
, format
, va
);
489 // Supply our own linefeed in case we are printing inside a custom defaults operation
490 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
491 // instead of expecting the directly following command to supply the line feed.
492 cliPrintInternal(cliErrorWriter
, "\r\n");
496 static void cliPrintErrorLinef(const char *cmdName
, const char *format
, ...)
499 va_start(va
, format
);
500 cliPrintErrorVa(cmdName
, format
, va
);
502 cliPrintInternal(cliErrorWriter
, "\r\n");
505 static void getMinMax(const clivalue_t
*var
, int *min
, int *max
)
507 switch (var
->type
& VALUE_TYPE_MASK
) {
510 *min
= var
->config
.minmaxUnsigned
.min
;
511 *max
= var
->config
.minmaxUnsigned
.max
;
515 *min
= var
->config
.minmax
.min
;
516 *max
= var
->config
.minmax
.max
;
522 static void printValuePointer(const char *cmdName
, const clivalue_t
*var
, const void *valuePointer
, bool full
)
524 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
525 for (int i
= 0; i
< var
->config
.array
.length
; i
++) {
526 switch (var
->type
& VALUE_TYPE_MASK
) {
530 cliPrintf("%d", ((uint8_t *)valuePointer
)[i
]);
535 cliPrintf("%d", ((int8_t *)valuePointer
)[i
]);
540 cliPrintf("%d", ((uint16_t *)valuePointer
)[i
]);
545 cliPrintf("%d", ((int16_t *)valuePointer
)[i
]);
550 cliPrintf("%u", ((uint32_t *)valuePointer
)[i
]);
555 cliPrintf("%d", ((int32_t *)valuePointer
)[i
]);
559 if (i
< var
->config
.array
.length
- 1) {
566 switch (var
->type
& VALUE_TYPE_MASK
) {
568 value
= *(uint8_t *)valuePointer
;
572 value
= *(int8_t *)valuePointer
;
576 value
= *(uint16_t *)valuePointer
;
580 value
= *(int16_t *)valuePointer
;
584 value
= *(uint32_t *)valuePointer
;
588 value
= *(int32_t *)valuePointer
;
593 bool valueIsCorrupted
= false;
594 switch (var
->type
& VALUE_MODE_MASK
) {
596 if ((var
->type
& VALUE_TYPE_MASK
) == VAR_UINT32
) {
597 cliPrintf("%u", (uint32_t)value
);
598 if ((uint32_t)value
> var
->config
.u32Max
) {
599 valueIsCorrupted
= true;
601 cliPrintf(" 0 %u", var
->config
.u32Max
);
603 } else if ((var
->type
& VALUE_TYPE_MASK
) == VAR_INT32
) {
604 cliPrintf("%d", (int32_t)value
);
605 if ((int32_t)value
> var
->config
.d32Max
|| (int32_t)value
< -var
->config
.d32Max
) {
606 valueIsCorrupted
= true;
608 cliPrintf(" 0 %u", var
->config
.u32Max
);
613 getMinMax(var
, &min
, &max
);
615 cliPrintf("%d", value
);
616 if ((value
< min
) || (value
> max
)) {
617 valueIsCorrupted
= true;
619 cliPrintf(" %d %d", min
, max
);
624 if (value
< lookupTables
[var
->config
.lookup
.tableIndex
].valueCount
) {
625 cliPrint(lookupTables
[var
->config
.lookup
.tableIndex
].values
[value
]);
627 valueIsCorrupted
= true;
631 if (value
& 1 << var
->config
.bitpos
) {
638 cliPrintf("%s", (strlen((char *)valuePointer
) == 0) ? "-" : (char *)valuePointer
);
642 if (valueIsCorrupted
) {
644 cliPrintError(cmdName
, "CORRUPTED CONFIG: %s = %d", var
->name
, value
);
649 static bool valuePtrEqualsDefault(const clivalue_t
*var
, const void *ptr
, const void *ptrDefault
)
652 int elementCount
= 1;
653 uint32_t mask
= 0xffffffff;
655 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
656 elementCount
= var
->config
.array
.length
;
658 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
659 mask
= 1 << var
->config
.bitpos
;
661 for (int i
= 0; i
< elementCount
; i
++) {
662 switch (var
->type
& VALUE_TYPE_MASK
) {
664 result
= result
&& (((uint8_t *)ptr
)[i
] & mask
) == (((uint8_t *)ptrDefault
)[i
] & mask
);
668 result
= result
&& ((int8_t *)ptr
)[i
] == ((int8_t *)ptrDefault
)[i
];
672 result
= result
&& (((uint16_t *)ptr
)[i
] & mask
) == (((uint16_t *)ptrDefault
)[i
] & mask
);
675 result
= result
&& ((int16_t *)ptr
)[i
] == ((int16_t *)ptrDefault
)[i
];
678 result
= result
&& (((uint32_t *)ptr
)[i
] & mask
) == (((uint32_t *)ptrDefault
)[i
] & mask
);
681 result
= result
&& (((int32_t *)ptr
)[i
] & mask
) == (((int32_t *)ptrDefault
)[i
] & mask
);
689 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask
, bool outputFlag
, const char *headingStr
)
691 if (headingStr
&& (!(dumpMask
& DO_DIFF
) || outputFlag
)) {
692 cliPrintHashLine(headingStr
);
699 static void backupPgConfig(const pgRegistry_t
*pg
)
701 memcpy(pg
->copy
, pg
->address
, pg
->size
);
704 static void restorePgConfig(const pgRegistry_t
*pg
, uint16_t notToRestoreGroupId
)
706 if (!notToRestoreGroupId
|| pgN(pg
) != notToRestoreGroupId
) {
707 memcpy(pg
->address
, pg
->copy
, pg
->size
);
711 static void backupConfigs(void)
713 if (configIsInCopy
) {
721 configIsInCopy
= true;
724 static void restoreConfigs(uint16_t notToRestoreGroupId
)
726 if (!configIsInCopy
) {
731 restorePgConfig(pg
, notToRestoreGroupId
);
734 configIsInCopy
= false;
737 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
738 static bool isReadingConfigFromCopy(void)
740 return configIsInCopy
;
744 static bool isWritingConfigToCopy(void)
746 return configIsInCopy
;
749 static void backupAndResetConfigs(void)
752 // reset all configs to defaults to do differencing
756 static uint8_t getPidProfileIndexToUse(void)
758 return pidProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentPidProfileIndex() : pidProfileIndexToUse
;
761 static uint8_t getRateProfileIndexToUse(void)
763 return rateProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentControlRateProfileIndex() : rateProfileIndexToUse
;
766 static uint16_t getValueOffset(const clivalue_t
*value
)
768 switch (value
->type
& VALUE_SECTION_MASK
) {
771 return value
->offset
;
773 return value
->offset
+ sizeof(pidProfile_t
) * getPidProfileIndexToUse();
774 case PROFILE_RATE_VALUE
:
775 return value
->offset
+ sizeof(controlRateConfig_t
) * getRateProfileIndexToUse();
780 STATIC_UNIT_TESTED
void *cliGetValuePointer(const clivalue_t
*value
)
782 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
783 if (isWritingConfigToCopy()) {
784 return CONST_CAST(void *, rec
->copy
+ getValueOffset(value
));
786 return CONST_CAST(void *, rec
->address
+ getValueOffset(value
));
790 static const char *dumpPgValue(const char *cmdName
, const clivalue_t
*value
, dumpFlags_t dumpMask
, const char *headingStr
)
792 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
795 cliPrintLinef("VALUE %s ERROR", value
->name
);
796 return headingStr
; // if it's not found, the pgn shouldn't be in the value table!
800 const char *format
= "set %s = ";
801 const char *defaultFormat
= "#set %s = ";
802 const int valueOffset
= getValueOffset(value
);
803 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
805 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
806 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
807 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
808 cliPrintf(defaultFormat
, value
->name
);
809 printValuePointer(cmdName
, value
, (uint8_t*)pg
->address
+ valueOffset
, false);
812 cliPrintf(format
, value
->name
);
813 printValuePointer(cmdName
, value
, pg
->copy
+ valueOffset
, false);
819 static void dumpAllValues(const char *cmdName
, uint16_t valueSection
, dumpFlags_t dumpMask
, const char *headingStr
)
821 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
823 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
824 const clivalue_t
*value
= &valueTable
[i
];
826 if ((value
->type
& VALUE_SECTION_MASK
) == valueSection
|| ((valueSection
== MASTER_VALUE
) && (value
->type
& VALUE_SECTION_MASK
) == HARDWARE_VALUE
)) {
827 headingStr
= dumpPgValue(cmdName
, value
, dumpMask
, headingStr
);
832 static void cliPrintVar(const char *cmdName
, const clivalue_t
*var
, bool full
)
834 const void *ptr
= cliGetValuePointer(var
);
836 printValuePointer(cmdName
, var
, ptr
, full
);
839 static void cliPrintVarRange(const clivalue_t
*var
)
841 switch (var
->type
& VALUE_MODE_MASK
) {
842 case (MODE_DIRECT
): {
843 switch (var
->type
& VALUE_TYPE_MASK
) {
845 cliPrintLinef("Allowed range: 0 - %u", var
->config
.u32Max
);
849 cliPrintLinef("Allowed range: %d - %d", -var
->config
.d32Max
, var
->config
.d32Max
);
854 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmaxUnsigned
.min
, var
->config
.minmaxUnsigned
.max
);
858 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
864 case (MODE_LOOKUP
): {
865 const lookupTableEntry_t
*tableEntry
= &lookupTables
[var
->config
.lookup
.tableIndex
];
866 cliPrint("Allowed values: ");
867 bool firstEntry
= true;
868 for (unsigned i
= 0; i
< tableEntry
->valueCount
; i
++) {
869 if (tableEntry
->values
[i
]) {
873 cliPrintf("%s", tableEntry
->values
[i
]);
881 cliPrintLinef("Array length: %d", var
->config
.array
.length
);
884 case (MODE_STRING
): {
885 cliPrintLinef("String length: %d - %d", var
->config
.string
.minlength
, var
->config
.string
.maxlength
);
888 case (MODE_BITSET
): {
889 cliPrintLinef("Allowed values: OFF, ON");
895 static void cliSetVar(const clivalue_t
*var
, const uint32_t value
)
897 void *ptr
= cliGetValuePointer(var
);
901 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
902 switch (var
->type
& VALUE_TYPE_MASK
) {
904 mask
= (1 << var
->config
.bitpos
) & 0xff;
906 workValue
= *(uint8_t *)ptr
| mask
;
908 workValue
= *(uint8_t *)ptr
& ~mask
;
910 *(uint8_t *)ptr
= workValue
;
914 mask
= (1 << var
->config
.bitpos
) & 0xffff;
916 workValue
= *(uint16_t *)ptr
| mask
;
918 workValue
= *(uint16_t *)ptr
& ~mask
;
920 *(uint16_t *)ptr
= workValue
;
924 mask
= 1 << var
->config
.bitpos
;
926 workValue
= *(uint32_t *)ptr
| mask
;
928 workValue
= *(uint32_t *)ptr
& ~mask
;
930 *(uint32_t *)ptr
= workValue
;
934 mask
= 1 << var
->config
.bitpos
;
936 workValue
= *(int32_t *)ptr
| mask
;
938 workValue
= *(int32_t *)ptr
& ~mask
;
940 *(int32_t *)ptr
= workValue
;
944 switch (var
->type
& VALUE_TYPE_MASK
) {
946 *(uint8_t *)ptr
= value
;
950 *(int8_t *)ptr
= value
;
954 *(uint16_t *)ptr
= value
;
958 *(int16_t *)ptr
= value
;
962 *(uint32_t *)ptr
= value
;
966 *(int32_t *)ptr
= value
;
972 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
973 static void cliRepeat(char ch
, uint8_t len
)
976 for (int i
= 0; i
< len
; i
++) {
977 bufWriterAppend(cliWriter
, ch
);
984 static void cliPrompt(void)
989 static void cliShowParseError(const char *cmdName
)
991 cliPrintErrorLinef(cmdName
, "PARSING FAILED");
994 static void cliShowInvalidArgumentCountError(const char *cmdName
)
996 cliPrintErrorLinef(cmdName
, "INVALID ARGUMENT COUNT");
999 static void cliShowArgumentRangeError(const char *cmdName
, char *name
, int min
, int max
)
1002 cliPrintErrorLinef(cmdName
, "%s NOT BETWEEN %d AND %d", name
, min
, max
);
1004 cliPrintErrorLinef(cmdName
, "ARGUMENT OUT OF RANGE");
1008 static const char *nextArg(const char *currentArg
)
1010 const char *ptr
= strchr(currentArg
, ' ');
1011 while (ptr
&& *ptr
== ' ') {
1018 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
1020 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
1023 int val
= atoi(ptr
);
1024 val
= CHANNEL_VALUE_TO_STEP(val
);
1025 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
1026 if (argIndex
== 0) {
1027 range
->startStep
= val
;
1029 range
->endStep
= val
;
1031 (*validArgumentCount
)++;
1039 // Check if a string's length is zero
1040 static bool isEmpty(const char *string
)
1042 return (string
== NULL
|| *string
== '\0') ? true : false;
1045 static void printRxFailsafe(dumpFlags_t dumpMask
, const rxFailsafeChannelConfig_t
*rxFailsafeChannelConfigs
, const rxFailsafeChannelConfig_t
*defaultRxFailsafeChannelConfigs
, const char *headingStr
)
1047 // print out rxConfig failsafe settings
1048 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1049 for (uint32_t channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
1050 const rxFailsafeChannelConfig_t
*channelFailsafeConfig
= &rxFailsafeChannelConfigs
[channel
];
1051 const rxFailsafeChannelConfig_t
*defaultChannelFailsafeConfig
= &defaultRxFailsafeChannelConfigs
[channel
];
1052 const bool equalsDefault
= !memcmp(channelFailsafeConfig
, defaultChannelFailsafeConfig
, sizeof(*channelFailsafeConfig
));
1053 const bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
1054 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1056 const char *format
= "rxfail %u %c %d";
1057 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1059 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
],
1060 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig
->step
)
1062 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1064 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
],
1065 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
1068 const char *format
= "rxfail %u %c";
1069 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1071 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
]
1073 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1075 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
]
1081 static void cliRxFailsafe(const char *cmdName
, char *cmdline
)
1086 if (isEmpty(cmdline
)) {
1087 // print out rxConfig failsafe settings
1088 for (channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
1089 cliRxFailsafe(cmdName
, itoa(channel
, buf
, 10));
1092 const char *ptr
= cmdline
;
1093 channel
= atoi(ptr
++);
1094 if ((channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
)) {
1096 rxFailsafeChannelConfig_t
*channelFailsafeConfig
= rxFailsafeChannelConfigsMutable(channel
);
1098 const rxFailsafeChannelType_e type
= (channel
< NON_AUX_CHANNEL_COUNT
) ? RX_FAILSAFE_TYPE_FLIGHT
: RX_FAILSAFE_TYPE_AUX
;
1099 rxFailsafeChannelMode_e mode
= channelFailsafeConfig
->mode
;
1100 bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
1104 const char *p
= strchr(rxFailsafeModeCharacters
, *(ptr
));
1106 const uint8_t requestedMode
= p
- rxFailsafeModeCharacters
;
1107 mode
= rxFailsafeModesTable
[type
][requestedMode
];
1109 mode
= RX_FAILSAFE_MODE_INVALID
;
1111 if (mode
== RX_FAILSAFE_MODE_INVALID
) {
1112 cliShowParseError(cmdName
);
1116 requireValue
= mode
== RX_FAILSAFE_MODE_SET
;
1120 if (!requireValue
) {
1121 cliShowParseError(cmdName
);
1124 uint16_t value
= atoi(ptr
);
1125 value
= CHANNEL_VALUE_TO_RXFAIL_STEP(value
);
1126 if (value
> MAX_RXFAIL_RANGE_STEP
) {
1127 cliPrintErrorLinef(cmdName
, "value out of range: %d", value
);
1131 channelFailsafeConfig
->step
= value
;
1132 } else if (requireValue
) {
1133 cliShowInvalidArgumentCountError(cmdName
);
1136 channelFailsafeConfig
->mode
= mode
;
1139 char modeCharacter
= rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
];
1141 // double use of cliPrintf below
1142 // 1. acknowledge interpretation on command,
1143 // 2. query current setting on single item,
1146 cliPrintLinef("rxfail %u %c %d",
1149 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
1152 cliPrintLinef("rxfail %u %c",
1158 cliShowArgumentRangeError(cmdName
, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT
- 1);
1163 static void printAux(dumpFlags_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
, const char *headingStr
)
1165 const char *format
= "aux %u %u %u %u %u %u %u";
1166 // print out aux channel settings
1167 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1168 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
1169 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
1170 bool equalsDefault
= false;
1171 if (defaultModeActivationConditions
) {
1172 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
1173 equalsDefault
= !isModeActivationConditionConfigured(mac
, macDefault
);
1174 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1175 const box_t
*box
= findBoxByBoxId(macDefault
->modeId
);
1176 const box_t
*linkedTo
= findBoxByBoxId(macDefault
->linkedTo
);
1178 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1181 macDefault
->auxChannelIndex
,
1182 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
1183 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
),
1184 macDefault
->modeLogic
,
1185 linkedTo
? linkedTo
->permanentId
: 0
1189 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
1190 const box_t
*linkedTo
= findBoxByBoxId(mac
->linkedTo
);
1192 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1195 mac
->auxChannelIndex
,
1196 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
1197 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
1199 linkedTo
? linkedTo
->permanentId
: 0
1205 static void cliAux(const char *cmdName
, char *cmdline
)
1210 if (isEmpty(cmdline
)) {
1211 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
, NULL
);
1215 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
1216 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
1217 uint8_t validArgumentCount
= 0;
1221 const box_t
*box
= findBoxByPermanentId(val
);
1223 mac
->modeId
= box
->boxId
;
1224 validArgumentCount
++;
1230 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1231 mac
->auxChannelIndex
= val
;
1232 validArgumentCount
++;
1235 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
1239 if (val
== MODELOGIC_OR
|| val
== MODELOGIC_AND
) {
1240 mac
->modeLogic
= val
;
1241 validArgumentCount
++;
1247 const box_t
*box
= findBoxByPermanentId(val
);
1249 mac
->linkedTo
= box
->boxId
;
1250 validArgumentCount
++;
1253 if (validArgumentCount
== 4) { // for backwards compatibility
1254 mac
->modeLogic
= MODELOGIC_OR
;
1256 } else if (validArgumentCount
== 5) { // for backwards compatibility
1258 } else if (validArgumentCount
!= 6) {
1259 memset(mac
, 0, sizeof(modeActivationCondition_t
));
1261 analyzeModeActivationConditions();
1262 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1264 findBoxByBoxId(mac
->modeId
)->permanentId
,
1265 mac
->auxChannelIndex
,
1266 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
1267 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
1269 findBoxByBoxId(mac
->linkedTo
)->permanentId
1272 cliShowArgumentRangeError(cmdName
, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
1277 static void printSerial(dumpFlags_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
, const char *headingStr
)
1279 const char *format
= "serial %s %d %ld %ld %ld %ld";
1280 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1281 for (unsigned i
= 0; i
< ARRAYLEN(serialConfig
->portConfigs
); i
++) {
1282 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
1285 bool equalsDefault
= false;
1286 if (serialConfigDefault
) {
1287 equalsDefault
= !memcmp(&serialConfig
->portConfigs
[i
], &serialConfigDefault
->portConfigs
[i
], sizeof(serialConfig
->portConfigs
[i
]));
1288 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1289 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1290 serialName(serialConfigDefault
->portConfigs
[i
].identifier
, invalidName
),
1291 serialConfigDefault
->portConfigs
[i
].functionMask
,
1292 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
1293 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
1294 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
1295 baudRates
[serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
]
1298 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1299 serialName(serialConfig
->portConfigs
[i
].identifier
, invalidName
),
1300 serialConfig
->portConfigs
[i
].functionMask
,
1301 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
1302 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
1303 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
1304 baudRates
[serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
]
1309 static void cliSerial(const char *cmdName
, char *cmdline
)
1311 const char *format
= "serial %s %d %ld %ld %ld %ld";
1312 if (isEmpty(cmdline
)) {
1313 printSerial(DUMP_MASTER
, serialConfig(), NULL
, NULL
);
1317 serialPortConfig_t portConfig
;
1318 memset(&portConfig
, 0 , sizeof(portConfig
));
1320 uint8_t validArgumentCount
= 0;
1322 char *ptr
= cmdline
;
1323 char *tok
= strsep(&ptr
, " ");
1324 serialPortIdentifier_e identifier
= findSerialPortByName(tok
, strcasecmp
);
1325 if (identifier
== SERIAL_PORT_NONE
) {
1327 identifier
= strtoul(tok
, &eptr
, 10);
1329 // parsing ended before end of token indicating an invalid identifier
1330 identifier
= SERIAL_PORT_NONE
;
1332 // correction for legacy configuration where UART1 == 0
1333 if (identifier
>= SERIAL_PORT_LEGACY_START_IDENTIFIER
&& identifier
< SERIAL_PORT_START_IDENTIFIER
) {
1334 identifier
+= SERIAL_PORT_UART1
;
1339 serialPortConfig_t
*currentConfig
= serialFindPortConfigurationMutable(identifier
);
1341 if (!currentConfig
) {
1342 cliShowParseError(cmdName
);
1346 portConfig
.identifier
= identifier
;
1347 validArgumentCount
++;
1349 tok
= strsep(&ptr
, " ");
1351 int val
= strtoul(tok
, NULL
, 10);
1352 portConfig
.functionMask
= val
;
1353 validArgumentCount
++;
1356 for (int i
= 0; i
< 4; i
++) {
1357 tok
= strsep(&ptr
, " ");
1362 int val
= atoi(tok
);
1364 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
1365 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
1371 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_1000000
) {
1374 portConfig
.msp_baudrateIndex
= baudRateIndex
;
1377 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
1380 portConfig
.gps_baudrateIndex
= baudRateIndex
;
1383 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
1386 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
1389 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_2470000
) {
1392 portConfig
.blackbox_baudrateIndex
= baudRateIndex
;
1396 validArgumentCount
++;
1399 if (validArgumentCount
< 6) {
1400 cliShowInvalidArgumentCountError(cmdName
);
1404 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
1406 cliDumpPrintLinef(0, false, format
,
1407 serialName(portConfig
.identifier
, invalidName
),
1408 portConfig
.functionMask
,
1409 baudRates
[portConfig
.msp_baudrateIndex
],
1410 baudRates
[portConfig
.gps_baudrateIndex
],
1411 baudRates
[portConfig
.telemetry_baudrateIndex
],
1412 baudRates
[portConfig
.blackbox_baudrateIndex
]
1416 #if defined(USE_SERIAL_PASSTHROUGH)
1417 static void cbCtrlLine_reset(void *context
, uint16_t ctrl
)
1420 if (!(ctrl
& CTRL_LINE_STATE_DTR
)) {
1426 static void cbCtrlLine_pinIO(void *context
, uint16_t ctrl
)
1428 pinioSet((intptr_t)context
, !(ctrl
& CTRL_LINE_STATE_DTR
));
1432 static portMode_e
cliParseSerialMode(const char *tok
)
1434 portMode_e mode
= 0;
1436 if (strcasestr(tok
, "rx")) {
1439 if (strcasestr(tok
, "tx")) {
1446 static portOptions_e
cliParseSerialOptions(const char *tok
)
1452 {"Invert", SERIAL_INVERTED
},
1453 {"Stop2", SERIAL_STOPBITS_2
},
1454 {"Even", SERIAL_PARITY_EVEN
},
1455 {"Bidir", SERIAL_BIDIR
},
1456 {"Pushpull", SERIAL_BIDIR_PP
},
1457 {"Saudio", SERIAL_PULL_SMARTAUDIO
},
1458 {"Check", SERIAL_CHECK_TX
},
1460 portOptions_e options
= 0;
1461 for (unsigned i
= 0; i
< ARRAYLEN(map
); i
++) {
1462 if (strstr(tok
, map
[i
].tag
) != 0) {
1463 options
|= map
[i
].val
;
1469 static void cliSerialPassthrough(const char *cmdName
, char *cmdline
)
1471 if (isEmpty(cmdline
)) {
1472 cliShowInvalidArgumentCountError(cmdName
);
1476 serialPassthroughPort_t ports
[2] = { {SERIAL_PORT_NONE
, 0, 0, 0, NULL
}, {cliPort
->identifier
, 0, 0, 0, cliPort
} };
1477 bool enableBaudCb
= false;
1479 int port1PinioDtr
= -1; // route port2 USB DTR to pinio
1481 bool port1ResetOnDtr
= false; // reset board with DTR
1482 bool escSensorPassthrough
= false;
1484 char* nexttok
= cmdline
;
1487 while ((tok
= strsep(&nexttok
, " ")) != NULL
) {
1488 if (*tok
== '\0') { // skip adjacent delimiters
1491 unsigned portN
= (index
< 4) ? 0 : 1; // port1 / port2
1493 case 0: // port1 to open: esc_sensor, portName, port ID port1
1494 case 4: // port2 to use (defaults to CLI serial if no more arguments)
1496 serialPortIdentifier_e portId
;
1498 if (portN
== 0 && strcasestr(tok
, "esc_sensor") != NULL
) {
1499 escSensorPassthrough
= true;
1500 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_ESC_SENSOR
);
1501 portId
= portConfig
? portConfig
->identifier
: SERIAL_PORT_NONE
;
1502 } else if (strcasecmp(tok
, "cli") == 0) {
1503 portId
= cliPort
->identifier
;
1504 } else if ((portId
= findSerialPortByName(tok
, strcasecmp
)) >= 0) {
1506 } else if ((portId
= strtol(tok
, &endptr
, 10)) >= 0 && *endptr
== '\0') {
1509 cliPrintLinef("Failed parsing port%d (%s)", portN
+ 1, tok
);
1512 if (portN
== 1) { // port1 is specified, don't use CLI port
1513 ports
[portN
].port
= NULL
;
1515 ports
[portN
].id
= portId
;
1520 int baud
= atoi(tok
);
1521 ports
[portN
].baud
= baud
;
1524 case 2: // port1 mode (rx/tx/rxtx) + options
1525 case 6: // port2 mode + options
1526 ports
[portN
].mode
= cliParseSerialMode(tok
);
1527 ports
[portN
].options
= cliParseSerialOptions(tok
);
1529 case 3: // DTR action
1530 if (strcasecmp(tok
, "reset") == 0) {
1531 port1ResetOnDtr
= true;
1534 if (strcasecmp(tok
, "none") == 0) {
1538 port1PinioDtr
= atoi(tok
);
1539 if (port1PinioDtr
< 0 || port1PinioDtr
>= PINIO_COUNT
) {
1540 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr
);
1543 #endif /* USE_PINIO */
1546 cliPrintLinef("Unexpected argument %d (%s)", index
+ 1, tok
);
1552 for (unsigned i
= 0; i
< ARRAYLEN(ports
); i
++) {
1553 if (findSerialPortIndexByIdentifier(ports
[i
].id
) < 0) {
1554 cliPrintLinef("Invalid port%d %d", i
+ 1, ports
[i
].id
);
1557 cliPrintLinef("Port%d: %s", i
+ 1, serialName(ports
[i
].id
, "<invalid>"));
1562 if (ports
[0].id
== ports
[1].id
) {
1563 cliPrintLinef("Port1 and port2 are same");
1567 if (ports
[0].baud
== 0 && ports
[1].id
== SERIAL_PORT_USB_VCP
) {
1568 enableBaudCb
= true;
1571 for (int i
= 0; i
< 2; i
++) {
1572 serialPassthroughPort_t
* cfg
= &ports
[i
];
1573 if (cfg
->port
!= NULL
) { // port already selected, don't touch it (used when port2 defaults to cli)
1577 int portIndex
= i
+ 1;
1578 serialPortUsage_t
*portUsage
= findSerialPortUsageByIdentifier(cfg
->id
);
1579 if (!portUsage
|| portUsage
->serialPort
== NULL
) {
1580 // serial port is not open yet
1581 const bool isUseDefaultBaud
= cfg
->baud
== 0;
1582 if (isUseDefaultBaud
) {
1588 cliPrintLinef("Using RXTX mode as default");
1589 cfg
->mode
= MODE_RXTX
;
1593 cliPrintLinef("Port%d: using options 0x%x",
1594 portIndex
, cfg
->options
);
1596 cfg
->port
= openSerialPort(cfg
->id
, FUNCTION_NONE
,
1597 NULL
, NULL
, // rxCallback
1598 cfg
->baud
, cfg
->mode
, cfg
->options
);
1600 cliPrintLinef("Port%d could not be opened.", portIndex
);
1604 cliPrintf("Port%d opened, %sbaud = %d.\r\n", portIndex
, isUseDefaultBaud
? "default ":"", cfg
->baud
);
1606 cfg
->port
= portUsage
->serialPort
;
1607 // If the user supplied a mode, override the port's mode, otherwise
1608 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1609 // Set the baud rate if specified
1611 serialSetBaudRate(cfg
->port
, cfg
->baud
);
1613 cliPrintLinef("Port%d is already open, %sbaud = %d.", portIndex
, cfg
->baud
? "new " : "", cfg
->port
->baudRate
);
1615 if (cfg
->mode
&& cfg
->port
->mode
!= cfg
->mode
) {
1616 cliPrintLinef("Port%d mode changed from %d to %d.",
1617 portIndex
, cfg
->port
->mode
, cfg
->mode
);
1618 serialSetMode(cfg
->port
, cfg
->mode
);
1622 cliPrintLinef("Port%d is open, can't change options from 0x%x to 0x%x",
1623 portIndex
, cfg
->port
->options
, cfg
->options
);
1626 // If this port has a rx callback associated we need to remove it now.
1627 // Otherwise no data will be pushed in the serial port buffer!
1628 if (cfg
->port
->rxCallback
) {
1629 cliPrintLinef("Port%d: Callback removed", portIndex
);
1630 cfg
->port
->rxCallback
= NULL
;
1635 // If no baud rate is specified allow to be set via USB
1637 cliPrintLine("Port1 baud rate change over USB enabled.");
1638 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1639 // baud rate over USB without setting it using the serialpassthrough command
1640 serialSetBaudRateCb(ports
[1].port
, serialSetBaudRate
, ports
[0].port
);
1643 const char *resetMessage
= "";
1644 if (port1ResetOnDtr
&& ports
[1].id
== SERIAL_PORT_USB_VCP
) {
1645 resetMessage
= "or drop DTR ";
1648 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage
);
1650 if ((ports
[1].id
== SERIAL_PORT_USB_VCP
)) {
1652 if (port1ResetOnDtr
) {
1653 serialSetCtrlLineStateCb(ports
[1].port
, cbCtrlLine_reset
, NULL
);
1657 if (port1PinioDtr
>= 0) {
1658 serialSetCtrlLineStateCb(ports
[1].port
, cbCtrlLine_pinIO
, (void *)(intptr_t)(port1PinioDtr
));
1661 #endif /* USE_PINIO */
1665 // XXX Review ESC pass through under refactored motor handling
1666 #ifdef USE_PWM_OUTPUT
1667 if (escSensorPassthrough
) {
1668 // pwmDisableMotors();
1671 for (unsigned i
= 0; i
< getMotorCount(); i
++) {
1672 const ioTag_t tag
= motorConfig()->dev
.ioTags
[i
];
1674 const timerHardware_t
*timerHardware
= timerGetConfiguredByTag(tag
);
1675 if (timerHardware
) {
1676 IO_t io
= IOGetByTag(tag
);
1677 IOInit(io
, OWNER_MOTOR
, i
);
1678 IOConfigGPIO(io
, IOCFG_OUT_PP
);
1679 if (timerHardware
->output
& TIMER_OUTPUT_INVERTED
) {
1690 serialPassthrough(ports
[0].port
, ports
[1].port
, NULL
, NULL
);
1694 static void printAdjustmentRange(dumpFlags_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
, const char *headingStr
)
1696 const char *format
= "adjrange %u 0 %u %u %u %u %u %u %u";
1697 // print out adjustment ranges channel settings
1698 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1699 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
1700 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
1701 bool equalsDefault
= false;
1702 if (defaultAdjustmentRanges
) {
1703 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
1704 equalsDefault
= !memcmp(ar
, arDefault
, sizeof(*ar
));
1705 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1706 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1708 arDefault
->auxChannelIndex
,
1709 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1710 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1711 arDefault
->adjustmentConfig
,
1712 arDefault
->auxSwitchChannelIndex
,
1713 arDefault
->adjustmentCenter
,
1714 arDefault
->adjustmentScale
1717 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1719 ar
->auxChannelIndex
,
1720 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1721 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1722 ar
->adjustmentConfig
,
1723 ar
->auxSwitchChannelIndex
,
1724 ar
->adjustmentCenter
,
1730 static void cliAdjustmentRange(const char *cmdName
, char *cmdline
)
1732 const char *format
= "adjrange %u 0 %u %u %u %u %u %u %u";
1735 if (isEmpty(cmdline
)) {
1736 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
, NULL
);
1739 int i
= atoi(ptr
++);
1740 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1741 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1742 uint8_t validArgumentCount
= 0;
1747 // Keeping the parameter to retain backwards compatibility for the command format.
1748 validArgumentCount
++;
1752 int val
= atoi(ptr
);
1753 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1754 ar
->auxChannelIndex
= val
;
1755 validArgumentCount
++;
1759 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1763 int val
= atoi(ptr
);
1764 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1765 ar
->adjustmentConfig
= val
;
1766 validArgumentCount
++;
1771 int val
= atoi(ptr
);
1772 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1773 ar
->auxSwitchChannelIndex
= val
;
1774 validArgumentCount
++;
1778 if (validArgumentCount
!= 6) {
1779 memset(ar
, 0, sizeof(adjustmentRange_t
));
1780 cliShowInvalidArgumentCountError(cmdName
);
1784 // Optional arguments
1785 ar
->adjustmentCenter
= 0;
1786 ar
->adjustmentScale
= 0;
1790 int val
= atoi(ptr
);
1791 ar
->adjustmentCenter
= val
;
1792 validArgumentCount
++;
1796 int val
= atoi(ptr
);
1797 ar
->adjustmentScale
= val
;
1798 validArgumentCount
++;
1801 activeAdjustmentRangeReset();
1803 cliDumpPrintLinef(0, false, format
,
1805 ar
->auxChannelIndex
,
1806 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1807 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1808 ar
->adjustmentConfig
,
1809 ar
->auxSwitchChannelIndex
,
1810 ar
->adjustmentCenter
,
1815 cliShowArgumentRangeError(cmdName
, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1820 #ifndef USE_QUAD_MIXER_ONLY
1821 static void printMotorMix(dumpFlags_t dumpMask
, const motorMixer_t
*customMotorMixer
, const motorMixer_t
*defaultCustomMotorMixer
, const char *headingStr
)
1823 const char *format
= "mmix %d %s %s %s %s";
1824 char buf0
[FTOA_BUFFER_LENGTH
];
1825 char buf1
[FTOA_BUFFER_LENGTH
];
1826 char buf2
[FTOA_BUFFER_LENGTH
];
1827 char buf3
[FTOA_BUFFER_LENGTH
];
1828 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1829 if (customMotorMixer
[i
].throttle
== 0.0f
)
1831 const float thr
= customMotorMixer
[i
].throttle
;
1832 const float roll
= customMotorMixer
[i
].roll
;
1833 const float pitch
= customMotorMixer
[i
].pitch
;
1834 const float yaw
= customMotorMixer
[i
].yaw
;
1835 bool equalsDefault
= false;
1836 if (defaultCustomMotorMixer
) {
1837 const float thrDefault
= defaultCustomMotorMixer
[i
].throttle
;
1838 const float rollDefault
= defaultCustomMotorMixer
[i
].roll
;
1839 const float pitchDefault
= defaultCustomMotorMixer
[i
].pitch
;
1840 const float yawDefault
= defaultCustomMotorMixer
[i
].yaw
;
1841 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1843 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1844 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1846 ftoa(thrDefault
, buf0
),
1847 ftoa(rollDefault
, buf1
),
1848 ftoa(pitchDefault
, buf2
),
1849 ftoa(yawDefault
, buf3
));
1851 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1859 #endif // USE_QUAD_MIXER_ONLY
1861 static void cliMotorMix(const char *cmdName
, char *cmdline
)
1863 #ifdef USE_QUAD_MIXER_ONLY
1871 if (isEmpty(cmdline
)) {
1872 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
, NULL
);
1873 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1874 // erase custom mixer
1875 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1876 customMotorMixerMutable(i
)->throttle
= 0.0f
;
1878 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1879 ptr
= nextArg(cmdline
);
1882 for (uint32_t i
= 0; ; i
++) {
1883 if (mixerNames
[i
] == NULL
) {
1884 cliPrintErrorLinef(cmdName
, ERROR_INVALID_NAME
, cmdline
);
1887 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1888 mixerLoadMix(i
, customMotorMixerMutable(0));
1889 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1890 cliMotorMix(cmdName
, "");
1897 uint32_t i
= atoi(ptr
); // get motor number
1898 if (i
< MAX_SUPPORTED_MOTORS
) {
1901 customMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1906 customMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1911 customMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1916 customMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1920 cliShowInvalidArgumentCountError(cmdName
);
1922 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
, NULL
);
1925 cliShowArgumentRangeError(cmdName
, "INDEX", 0, MAX_SUPPORTED_MOTORS
- 1);
1931 static void printRxRange(dumpFlags_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
, const char *headingStr
)
1933 const char *format
= "rxrange %u %u %u";
1934 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1935 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1936 bool equalsDefault
= false;
1937 if (defaultChannelRangeConfigs
) {
1938 equalsDefault
= !memcmp(&channelRangeConfigs
[i
], &defaultChannelRangeConfigs
[i
], sizeof(channelRangeConfigs
[i
]));
1939 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1940 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1942 defaultChannelRangeConfigs
[i
].min
,
1943 defaultChannelRangeConfigs
[i
].max
1946 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1948 channelRangeConfigs
[i
].min
,
1949 channelRangeConfigs
[i
].max
1954 static void cliRxRange(const char *cmdName
, char *cmdline
)
1956 const char *format
= "rxrange %u %u %u";
1957 int i
, validArgumentCount
= 0;
1960 if (isEmpty(cmdline
)) {
1961 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
, NULL
);
1962 } else if (strcasecmp(cmdline
, "reset") == 0) {
1963 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1967 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1968 int rangeMin
= PWM_PULSE_MIN
, rangeMax
= PWM_PULSE_MAX
;
1972 rangeMin
= atoi(ptr
);
1973 validArgumentCount
++;
1978 rangeMax
= atoi(ptr
);
1979 validArgumentCount
++;
1982 if (validArgumentCount
!= 2) {
1983 cliShowInvalidArgumentCountError(cmdName
);
1984 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1985 cliShowArgumentRangeError(cmdName
, "range min/max", PWM_PULSE_MIN
, PWM_PULSE_MAX
);
1987 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1988 channelRangeConfig
->min
= rangeMin
;
1989 channelRangeConfig
->max
= rangeMax
;
1990 cliDumpPrintLinef(0, false, format
,
1992 channelRangeConfig
->min
,
1993 channelRangeConfig
->max
1998 cliShowArgumentRangeError(cmdName
, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT
- 1);
2003 #ifdef USE_LED_STRIP_STATUS_MODE
2004 static void printLed(dumpFlags_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
, const char *headingStr
)
2006 const char *format
= "led %u %s";
2007 char ledConfigBuffer
[20];
2008 char ledConfigDefaultBuffer
[20];
2009 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2010 for (uint32_t i
= 0; i
< LED_STRIP_MAX_LENGTH
; i
++) {
2011 ledConfig_t ledConfig
= ledConfigs
[i
];
2012 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
2013 bool equalsDefault
= false;
2014 if (defaultLedConfigs
) {
2015 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
2016 equalsDefault
= ledConfig
== ledConfigDefault
;
2017 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2018 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
2019 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
2021 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
2025 static void cliLed(const char *cmdName
, char *cmdline
)
2027 const char *format
= "led %u %s";
2028 char ledConfigBuffer
[20];
2032 if (isEmpty(cmdline
)) {
2033 printLed(DUMP_MASTER
, ledStripStatusModeConfig()->ledConfigs
, NULL
, NULL
);
2037 if (i
>= 0 && i
< LED_STRIP_MAX_LENGTH
) {
2038 ptr
= nextArg(cmdline
);
2039 if (parseLedStripConfig(i
, ptr
)) {
2040 generateLedConfig((ledConfig_t
*)&ledStripStatusModeConfig()->ledConfigs
[i
], ledConfigBuffer
, sizeof(ledConfigBuffer
));
2041 cliDumpPrintLinef(0, false, format
, i
, ledConfigBuffer
);
2043 cliShowParseError(cmdName
);
2046 cliShowArgumentRangeError(cmdName
, "INDEX", 0, LED_STRIP_MAX_LENGTH
- 1);
2051 static void printColor(dumpFlags_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
, const char *headingStr
)
2053 const char *format
= "color %u %d,%u,%u";
2054 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2055 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
2056 const hsvColor_t
*color
= &colors
[i
];
2057 bool equalsDefault
= false;
2058 if (defaultColors
) {
2059 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
2060 equalsDefault
= !memcmp(color
, colorDefault
, sizeof(*color
));
2061 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2062 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
2064 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
2068 static void cliColor(const char *cmdName
, char *cmdline
)
2070 const char *format
= "color %u %d,%u,%u";
2071 if (isEmpty(cmdline
)) {
2072 printColor(DUMP_MASTER
, ledStripStatusModeConfig()->colors
, NULL
, NULL
);
2074 const char *ptr
= cmdline
;
2075 const int i
= atoi(ptr
);
2076 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
2077 ptr
= nextArg(cmdline
);
2078 if (parseColor(i
, ptr
)) {
2079 const hsvColor_t
*color
= &ledStripStatusModeConfig()->colors
[i
];
2080 cliDumpPrintLinef(0, false, format
, i
, color
->h
, color
->s
, color
->v
);
2082 cliShowParseError(cmdName
);
2085 cliShowArgumentRangeError(cmdName
, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
2090 static void printModeColor(dumpFlags_t dumpMask
, const ledStripStatusModeConfig_t
*ledStripStatusModeConfig
, const ledStripStatusModeConfig_t
*defaultLedStripConfig
, const char *headingStr
)
2092 const char *format
= "mode_color %u %u %u";
2093 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2094 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
2095 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
2096 int colorIndex
= ledStripStatusModeConfig
->modeColors
[i
].color
[j
];
2097 bool equalsDefault
= false;
2098 if (defaultLedStripConfig
) {
2099 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
2100 equalsDefault
= colorIndex
== colorIndexDefault
;
2101 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2102 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
2104 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
2108 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
2109 const int colorIndex
= ledStripStatusModeConfig
->specialColors
.color
[j
];
2110 bool equalsDefault
= false;
2111 if (defaultLedStripConfig
) {
2112 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
2113 equalsDefault
= colorIndex
== colorIndexDefault
;
2114 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2115 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
2117 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
2120 const int ledStripAuxChannel
= ledStripStatusModeConfig
->ledstrip_aux_channel
;
2121 bool equalsDefault
= false;
2122 if (defaultLedStripConfig
) {
2123 const int ledStripAuxChannelDefault
= defaultLedStripConfig
->ledstrip_aux_channel
;
2124 equalsDefault
= ledStripAuxChannel
== ledStripAuxChannelDefault
;
2125 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2126 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannelDefault
);
2128 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannel
);
2131 static void cliModeColor(const char *cmdName
, char *cmdline
)
2133 if (isEmpty(cmdline
)) {
2134 printModeColor(DUMP_MASTER
, ledStripStatusModeConfig(), NULL
, NULL
);
2136 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
2137 int args
[ARGS_COUNT
];
2140 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
2141 while (ptr
&& argNo
< ARGS_COUNT
) {
2142 args
[argNo
++] = atoi(ptr
);
2143 ptr
= strtok_r(NULL
, " ", &saveptr
);
2146 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
2147 cliShowInvalidArgumentCountError(cmdName
);
2151 int modeIdx
= args
[MODE
];
2152 int funIdx
= args
[FUNCTION
];
2153 int color
= args
[COLOR
];
2154 if (!setModeColor(modeIdx
, funIdx
, color
)) {
2155 cliShowParseError(cmdName
);
2158 // values are validated
2159 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
2165 static void printServo(dumpFlags_t dumpMask
, const servoParam_t
*servoParams
, const servoParam_t
*defaultServoParams
, const char *headingStr
)
2167 // print out servo settings
2168 const char *format
= "servo %u %d %d %d %d %d";
2169 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2170 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2171 const servoParam_t
*servoConf
= &servoParams
[i
];
2172 bool equalsDefault
= false;
2173 if (defaultServoParams
) {
2174 const servoParam_t
*defaultServoConf
= &defaultServoParams
[i
];
2175 equalsDefault
= !memcmp(servoConf
, defaultServoConf
, sizeof(*servoConf
));
2176 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2177 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2179 defaultServoConf
->min
,
2180 defaultServoConf
->max
,
2181 defaultServoConf
->middle
,
2182 defaultServoConf
->rate
,
2183 defaultServoConf
->forwardFromChannel
2186 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2192 servoConf
->forwardFromChannel
2195 // print servo directions
2196 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2197 const char *format
= "smix reverse %d %d r";
2198 const servoParam_t
*servoConf
= &servoParams
[i
];
2199 const servoParam_t
*servoConfDefault
= &defaultServoParams
[i
];
2200 if (defaultServoParams
) {
2201 bool equalsDefault
= servoConf
->reversedSources
== servoConfDefault
->reversedSources
;
2202 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
2203 equalsDefault
= ~(servoConf
->reversedSources
^ servoConfDefault
->reversedSources
) & (1 << channel
);
2204 if (servoConfDefault
->reversedSources
& (1 << channel
)) {
2205 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
2207 if (servoConf
->reversedSources
& (1 << channel
)) {
2208 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
2212 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
2213 if (servoConf
->reversedSources
& (1 << channel
)) {
2214 cliDumpPrintLinef(dumpMask
, true, format
, i
, channel
);
2221 static void cliServo(const char *cmdName
, char *cmdline
)
2223 const char *format
= "servo %u %d %d %d %d %d";
2224 enum { SERVO_ARGUMENT_COUNT
= 6 };
2225 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
2227 servoParam_t
*servo
;
2232 if (isEmpty(cmdline
)) {
2233 printServo(DUMP_MASTER
, servoParams(0), NULL
, NULL
);
2235 int validArgumentCount
= 0;
2239 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2241 // If command line doesn't fit the format, don't modify the config
2243 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
2244 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
2245 cliShowInvalidArgumentCountError(cmdName
);
2249 arguments
[validArgumentCount
++] = atoi(ptr
);
2253 } while (*ptr
>= '0' && *ptr
<= '9');
2254 } else if (*ptr
== ' ') {
2257 cliShowParseError(cmdName
);
2262 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
, FORWARD
};
2264 i
= arguments
[INDEX
];
2266 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2267 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
2268 cliShowInvalidArgumentCountError(cmdName
);
2272 servo
= servoParamsMutable(i
);
2275 arguments
[MIN
] < PWM_SERVO_MIN
|| arguments
[MIN
] > PWM_SERVO_MAX
||
2276 arguments
[MAX
] < PWM_SERVO_MIN
|| arguments
[MAX
] > PWM_SERVO_MAX
||
2277 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
2278 arguments
[MIN
] > arguments
[MAX
] ||
2279 arguments
[RATE
] < -100 || arguments
[RATE
] > 100 ||
2280 arguments
[FORWARD
] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2282 cliShowArgumentRangeError(cmdName
, NULL
, 0, 0);
2286 servo
->min
= arguments
[MIN
];
2287 servo
->max
= arguments
[MAX
];
2288 servo
->middle
= arguments
[MIDDLE
];
2289 servo
->rate
= arguments
[RATE
];
2290 servo
->forwardFromChannel
= arguments
[FORWARD
];
2292 cliDumpPrintLinef(0, false, format
,
2298 servo
->forwardFromChannel
2306 static void printServoMix(dumpFlags_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
, const char *headingStr
)
2308 const char *format
= "smix %d %d %d %d %d %d %d %d";
2309 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2310 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
2311 const servoMixer_t customServoMixer
= customServoMixers
[i
];
2312 if (customServoMixer
.rate
== 0) {
2316 bool equalsDefault
= false;
2317 if (defaultCustomServoMixers
) {
2318 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
2319 equalsDefault
= !memcmp(&customServoMixer
, &customServoMixerDefault
, sizeof(customServoMixer
));
2321 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2322 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2324 customServoMixerDefault
.targetChannel
,
2325 customServoMixerDefault
.inputSource
,
2326 customServoMixerDefault
.rate
,
2327 customServoMixerDefault
.speed
,
2328 customServoMixerDefault
.min
,
2329 customServoMixerDefault
.max
,
2330 customServoMixerDefault
.box
2333 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2335 customServoMixer
.targetChannel
,
2336 customServoMixer
.inputSource
,
2337 customServoMixer
.rate
,
2338 customServoMixer
.speed
,
2339 customServoMixer
.min
,
2340 customServoMixer
.max
,
2341 customServoMixer
.box
2346 static void cliServoMix(const char *cmdName
, char *cmdline
)
2348 int args
[8], check
= 0;
2349 int len
= strlen(cmdline
);
2352 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
, NULL
);
2353 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
2354 // erase custom mixer
2355 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2356 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2357 servoParamsMutable(i
)->reversedSources
= 0;
2359 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
2360 const char *ptr
= nextArg(cmdline
);
2363 for (uint32_t i
= 0; ; i
++) {
2364 if (mixerNames
[i
] == NULL
) {
2365 cliPrintErrorLinef(cmdName
, ERROR_INVALID_NAME
, cmdline
);
2368 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
2369 servoMixerLoadMix(i
);
2370 cliPrintLinef("Loaded %s", mixerNames
[i
]);
2371 cliServoMix(cmdName
, "");
2376 } else if (strncasecmp(cmdline
, "reverse", 7) == 0) {
2377 enum {SERVO
= 0, INPUT
, REVERSE
, ARGS_COUNT
};
2378 char *ptr
= strchr(cmdline
, ' ');
2382 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
2383 cliPrintf("\ti%d", inputSource
);
2386 for (uint32_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
2387 cliPrintf("%d", servoIndex
);
2388 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++) {
2389 cliPrintf("\t%s ", (servoParams(servoIndex
)->reversedSources
& (1 << inputSource
)) ? "r" : "n");
2397 ptr
= strtok_r(ptr
, " ", &saveptr
);
2398 while (ptr
!= NULL
&& check
< ARGS_COUNT
- 1) {
2399 args
[check
++] = atoi(ptr
);
2400 ptr
= strtok_r(NULL
, " ", &saveptr
);
2403 if (ptr
== NULL
|| check
!= ARGS_COUNT
- 1) {
2404 cliShowInvalidArgumentCountError(cmdName
);
2408 if (args
[SERVO
] >= 0 && args
[SERVO
] < MAX_SUPPORTED_SERVOS
2409 && args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
2410 && (*ptr
== 'r' || *ptr
== 'n')) {
2412 servoParamsMutable(args
[SERVO
])->reversedSources
|= 1 << args
[INPUT
];
2414 servoParamsMutable(args
[SERVO
])->reversedSources
&= ~(1 << args
[INPUT
]);
2417 cliShowArgumentRangeError(cmdName
, "servo", 0, MAX_SUPPORTED_SERVOS
);
2421 cliServoMix(cmdName
, "reverse");
2423 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, MIN
, MAX
, BOX
, ARGS_COUNT
};
2425 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2426 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2427 args
[check
++] = atoi(ptr
);
2428 ptr
= strtok_r(NULL
, " ", &saveptr
);
2431 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2432 cliShowInvalidArgumentCountError(cmdName
);
2436 int32_t i
= args
[RULE
];
2437 if (i
>= 0 && i
< MAX_SERVO_RULES
&&
2438 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
2439 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
2440 args
[RATE
] >= -100 && args
[RATE
] <= 100 &&
2441 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
2442 args
[MIN
] >= 0 && args
[MIN
] <= 100 &&
2443 args
[MAX
] >= 0 && args
[MAX
] <= 100 && args
[MIN
] < args
[MAX
] &&
2444 args
[BOX
] >= 0 && args
[BOX
] <= MAX_SERVO_BOXES
) {
2445 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
2446 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
2447 customServoMixersMutable(i
)->rate
= args
[RATE
];
2448 customServoMixersMutable(i
)->speed
= args
[SPEED
];
2449 customServoMixersMutable(i
)->min
= args
[MIN
];
2450 customServoMixersMutable(i
)->max
= args
[MAX
];
2451 customServoMixersMutable(i
)->box
= args
[BOX
];
2452 cliServoMix(cmdName
, "");
2454 cliShowArgumentRangeError(cmdName
, NULL
, 0, 0);
2462 static void cliWriteBytes(const uint8_t *buffer
, int count
)
2471 static void cliSdInfo(const char *cmdName
, char *cmdline
)
2476 cliPrint("SD card: ");
2478 if (sdcardConfig()->mode
== SDCARD_MODE_NONE
) {
2479 cliPrintLine("Not configured");
2484 if (!sdcard_isInserted()) {
2485 cliPrintLine("None inserted");
2489 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2490 cliPrintLine("Startup failed");
2494 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
2496 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2497 metadata
->manufacturerID
,
2498 metadata
->numBlocks
/ 2, /* One block is half a kB */
2499 metadata
->productionMonth
,
2500 metadata
->productionYear
,
2501 metadata
->productRevisionMajor
,
2502 metadata
->productRevisionMinor
2505 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
2507 cliPrint("'\r\n" "Filesystem: ");
2509 switch (afatfs_getFilesystemState()) {
2510 case AFATFS_FILESYSTEM_STATE_READY
:
2513 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
2514 cliPrint("Initializing");
2516 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
2517 case AFATFS_FILESYSTEM_STATE_FATAL
:
2520 switch (afatfs_getLastError()) {
2521 case AFATFS_ERROR_BAD_MBR
:
2522 cliPrint(" - no FAT MBR partitions");
2524 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
2525 cliPrint(" - bad FAT header");
2527 case AFATFS_ERROR_GENERIC
:
2528 case AFATFS_ERROR_NONE
:
2529 ; // Nothing more detailed to print
2539 #ifdef USE_FLASH_CHIP
2540 static void cliFlashInfo(const char *cmdName
, char *cmdline
)
2545 const flashGeometry_t
*layout
= flashGetGeometry();
2547 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u JEDEC ID=0x%08x",
2548 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
, layout
->jedecId
);
2550 for (uint8_t index
= 0; index
< FLASH_MAX_PARTITIONS
; index
++) {
2551 const flashPartition_t
*partition
;
2553 cliPrintLine("Partitions:");
2555 partition
= flashPartitionFindByIndex(index
);
2559 cliPrintLinef(" %d: %s %u %u", index
, flashPartitionGetTypeName(partition
->type
), partition
->startSector
, partition
->endSector
);
2562 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
2564 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2565 FLASH_PARTITION_SECTOR_COUNT(flashPartition
) * layout
->sectorSize
,
2570 #endif // USE_FLASH_CHIP
2572 #if defined(USE_FLASHFS) && defined(USE_FLASH_CHIP)
2573 static void cliFlashErase(const char *cmdName
, char *cmdline
)
2578 if (!flashfsIsSupported()) {
2584 cliPrintLine("Erasing, please wait ... ");
2586 cliPrintLine("Erasing,");
2590 flashfsEraseCompletely();
2592 while (!flashfsIsReady()) {
2604 beeper(BEEPER_BLACKBOX_ERASE
);
2606 cliPrintLine("Done.");
2609 #ifdef USE_FLASH_TOOLS
2610 static void cliFlashVerify(const char *cmdName
, char *cmdline
)
2614 cliPrintLine("Verifying");
2615 if (flashfsVerifyEntireFlash()) {
2616 cliPrintLine("Success");
2618 cliPrintErrorLinef(cmdName
, "Failed");
2622 static void cliFlashWrite(const char *cmdName
, char *cmdline
)
2624 const uint32_t address
= atoi(cmdline
);
2625 const char *text
= strchr(cmdline
, ' ');
2628 cliShowInvalidArgumentCountError(cmdName
);
2630 flashfsSeekAbs(address
);
2631 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2634 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2638 static void cliFlashRead(const char *cmdName
, char *cmdline
)
2640 uint32_t address
= atoi(cmdline
);
2642 const char *nextArg
= strchr(cmdline
, ' ');
2645 cliShowInvalidArgumentCountError(cmdName
);
2647 uint32_t length
= atoi(nextArg
);
2649 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2652 while (length
> 0) {
2653 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2655 for (int i
= 0; i
< bytesRead
; i
++) {
2656 cliWrite(buffer
[i
]);
2659 length
-= bytesRead
;
2660 address
+= bytesRead
;
2662 if (bytesRead
== 0) {
2663 //Assume we reached the end of the volume or something fatal happened
2670 #endif // USE_FLASH_TOOLS
2671 #endif // USE_FLASHFS
2673 #ifdef USE_VTX_CONTROL
2674 static void printVtx(dumpFlags_t dumpMask
, const vtxConfig_t
*vtxConfig
, const vtxConfig_t
*vtxConfigDefault
, const char *headingStr
)
2676 // print out vtx channel settings
2677 const char *format
= "vtx %u %u %u %u %u %u %u";
2678 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2679 bool equalsDefault
= false;
2680 for (uint32_t i
= 0; i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
; i
++) {
2681 const vtxChannelActivationCondition_t
*cac
= &vtxConfig
->vtxChannelActivationConditions
[i
];
2682 if (vtxConfigDefault
) {
2683 const vtxChannelActivationCondition_t
*cacDefault
= &vtxConfigDefault
->vtxChannelActivationConditions
[i
];
2684 equalsDefault
= !memcmp(cac
, cacDefault
, sizeof(*cac
));
2685 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2686 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2688 cacDefault
->auxChannelIndex
,
2690 cacDefault
->channel
,
2692 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.startStep
),
2693 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.endStep
)
2696 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2698 cac
->auxChannelIndex
,
2702 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2703 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2708 static void cliVtx(const char *cmdName
, char *cmdline
)
2710 const char *format
= "vtx %u %u %u %u %u %u %u";
2713 if (isEmpty(cmdline
)) {
2714 printVtx(DUMP_MASTER
, vtxConfig(), NULL
, NULL
);
2716 #ifdef USE_VTX_TABLE
2717 const uint8_t maxBandIndex
= vtxTableConfig()->bands
;
2718 const uint8_t maxChannelIndex
= vtxTableConfig()->channels
;
2719 const uint8_t maxPowerIndex
= vtxTableConfig()->powerLevels
;
2721 const uint8_t maxBandIndex
= VTX_TABLE_MAX_BANDS
;
2722 const uint8_t maxChannelIndex
= VTX_TABLE_MAX_CHANNELS
;
2723 const uint8_t maxPowerIndex
= VTX_TABLE_MAX_POWER_LEVELS
;
2726 int i
= atoi(ptr
++);
2727 if (i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
) {
2728 vtxChannelActivationCondition_t
*cac
= &vtxConfigMutable()->vtxChannelActivationConditions
[i
];
2729 uint8_t validArgumentCount
= 0;
2732 int val
= atoi(ptr
);
2733 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
2734 cac
->auxChannelIndex
= val
;
2735 validArgumentCount
++;
2740 int val
= atoi(ptr
);
2741 if (val
>= 0 && val
<= maxBandIndex
) {
2743 validArgumentCount
++;
2748 int val
= atoi(ptr
);
2749 if (val
>= 0 && val
<= maxChannelIndex
) {
2751 validArgumentCount
++;
2756 int val
= atoi(ptr
);
2757 if (val
>= 0 && val
<= maxPowerIndex
) {
2759 validArgumentCount
++;
2762 ptr
= processChannelRangeArgs(ptr
, &cac
->range
, &validArgumentCount
);
2764 if (validArgumentCount
!= 6) {
2765 memset(cac
, 0, sizeof(vtxChannelActivationCondition_t
));
2766 cliShowInvalidArgumentCountError(cmdName
);
2768 cliDumpPrintLinef(0, false, format
,
2770 cac
->auxChannelIndex
,
2774 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2775 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2779 cliShowArgumentRangeError(cmdName
, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
- 1);
2784 #endif // VTX_CONTROL
2786 #ifdef USE_VTX_TABLE
2788 static char *formatVtxTableBandFrequency(const bool isFactory
, const uint16_t *frequency
, int channels
)
2790 static char freqbuf
[5 * VTX_TABLE_MAX_CHANNELS
+ 8 + 1];
2791 char freqtmp
[5 + 1];
2793 strcat(freqbuf
, isFactory
? " FACTORY" : " CUSTOM ");
2794 for (int channel
= 0; channel
< channels
; channel
++) {
2795 tfp_sprintf(freqtmp
, " %4d", frequency
[channel
]);
2796 strcat(freqbuf
, freqtmp
);
2801 static const char *printVtxTableBand(dumpFlags_t dumpMask
, int band
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2803 char *fmt
= "vtxtable band %d %s %c%s";
2804 bool equalsDefault
= false;
2806 if (defaultConfig
) {
2807 equalsDefault
= true;
2808 if (strcasecmp(currentConfig
->bandNames
[band
], defaultConfig
->bandNames
[band
])) {
2809 equalsDefault
= false;
2811 if (currentConfig
->bandLetters
[band
] != defaultConfig
->bandLetters
[band
]) {
2812 equalsDefault
= false;
2814 for (int channel
= 0; channel
< VTX_TABLE_MAX_CHANNELS
; channel
++) {
2815 if (currentConfig
->frequency
[band
][channel
] != defaultConfig
->frequency
[band
][channel
]) {
2816 equalsDefault
= false;
2819 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2820 char *freqbuf
= formatVtxTableBandFrequency(defaultConfig
->isFactoryBand
[band
], defaultConfig
->frequency
[band
], defaultConfig
->channels
);
2821 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, band
+ 1, defaultConfig
->bandNames
[band
], defaultConfig
->bandLetters
[band
], freqbuf
);
2824 char *freqbuf
= formatVtxTableBandFrequency(currentConfig
->isFactoryBand
[band
], currentConfig
->frequency
[band
], currentConfig
->channels
);
2825 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, band
+ 1, currentConfig
->bandNames
[band
], currentConfig
->bandLetters
[band
], freqbuf
);
2829 static char *formatVtxTablePowerValues(const uint16_t *levels
, int count
)
2831 // (max 4 digit + 1 space) per level
2832 static char pwrbuf
[5 * VTX_TABLE_MAX_POWER_LEVELS
+ 1];
2835 for (int pwrindex
= 0; pwrindex
< count
; pwrindex
++) {
2836 tfp_sprintf(pwrtmp
, " %d", levels
[pwrindex
]);
2837 strcat(pwrbuf
, pwrtmp
);
2842 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2844 char *fmt
= "vtxtable powervalues%s";
2845 bool equalsDefault
= false;
2846 if (defaultConfig
) {
2847 equalsDefault
= true;
2848 for (int pwrindex
= 0; pwrindex
< VTX_TABLE_MAX_POWER_LEVELS
; pwrindex
++) {
2849 if (defaultConfig
->powerValues
[pwrindex
] != currentConfig
->powerValues
[pwrindex
]) {
2850 equalsDefault
= false;
2853 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2854 char *pwrbuf
= formatVtxTablePowerValues(defaultConfig
->powerValues
, VTX_TABLE_MAX_POWER_LEVELS
);
2855 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2858 char *pwrbuf
= formatVtxTablePowerValues(currentConfig
->powerValues
, currentConfig
->powerLevels
);
2859 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2863 static char *formatVtxTablePowerLabels(const char labels
[VTX_TABLE_MAX_POWER_LEVELS
][VTX_TABLE_POWER_LABEL_LENGTH
+ 1], int count
)
2865 static char pwrbuf
[(VTX_TABLE_POWER_LABEL_LENGTH
+ 1) * VTX_TABLE_MAX_POWER_LEVELS
+ 1];
2866 char pwrtmp
[(VTX_TABLE_POWER_LABEL_LENGTH
+ 1) + 1];
2868 for (int pwrindex
= 0; pwrindex
< count
; pwrindex
++) {
2869 strcat(pwrbuf
, " ");
2870 strcpy(pwrtmp
, labels
[pwrindex
]);
2871 // trim trailing space
2873 while ((sp
= strchr(pwrtmp
, ' '))) {
2876 strcat(pwrbuf
, pwrtmp
);
2881 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2883 char *fmt
= "vtxtable powerlabels%s";
2884 bool equalsDefault
= false;
2885 if (defaultConfig
) {
2886 equalsDefault
= true;
2887 for (int pwrindex
= 0; pwrindex
< VTX_TABLE_MAX_POWER_LEVELS
; pwrindex
++) {
2888 if (strcasecmp(defaultConfig
->powerLabels
[pwrindex
], currentConfig
->powerLabels
[pwrindex
])) {
2889 equalsDefault
= false;
2892 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2893 char *pwrbuf
= formatVtxTablePowerLabels(defaultConfig
->powerLabels
, VTX_TABLE_MAX_POWER_LEVELS
);
2894 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2897 char *pwrbuf
= formatVtxTablePowerLabels(currentConfig
->powerLabels
, currentConfig
->powerLevels
);
2898 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2902 static void printVtxTable(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2907 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2910 equalsDefault
= false;
2911 fmt
= "vtxtable bands %d";
2912 if (defaultConfig
) {
2913 equalsDefault
= (defaultConfig
->bands
== currentConfig
->bands
);
2914 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2915 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->bands
);
2917 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->bands
);
2920 equalsDefault
= false;
2921 fmt
= "vtxtable channels %d";
2922 if (defaultConfig
) {
2923 equalsDefault
= (defaultConfig
->channels
== currentConfig
->channels
);
2924 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2925 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->channels
);
2927 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->channels
);
2931 for (int band
= 0; band
< currentConfig
->bands
; band
++) {
2932 headingStr
= printVtxTableBand(dumpMask
, band
, currentConfig
, defaultConfig
, headingStr
);
2937 equalsDefault
= false;
2938 fmt
= "vtxtable powerlevels %d";
2939 if (defaultConfig
) {
2940 equalsDefault
= (defaultConfig
->powerLevels
== currentConfig
->powerLevels
);
2941 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2942 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->powerLevels
);
2944 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->powerLevels
);
2949 headingStr
= printVtxTablePowerValues(dumpMask
, currentConfig
, defaultConfig
, headingStr
);
2950 headingStr
= printVtxTablePowerLabels(dumpMask
, currentConfig
, defaultConfig
, headingStr
);
2953 static void cliVtxTable(const char *cmdName
, char *cmdline
)
2958 // Band number or nothing
2959 tok
= strtok_r(cmdline
, " ", &saveptr
);
2962 printVtxTable(DUMP_MASTER
| HIDE_UNUSED
, vtxTableConfigMutable(), NULL
, NULL
);
2966 if (strcasecmp(tok
, "bands") == 0) {
2967 tok
= strtok_r(NULL
, " ", &saveptr
);
2968 int bands
= atoi(tok
);
2969 if (bands
< 0 || bands
> VTX_TABLE_MAX_BANDS
) {
2970 cliShowArgumentRangeError(cmdName
, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS
);
2973 if (bands
< vtxTableConfigMutable()->bands
) {
2974 for (int i
= bands
; i
< vtxTableConfigMutable()->bands
; i
++) {
2975 vtxTableConfigClearBand(vtxTableConfigMutable(), i
);
2978 vtxTableConfigMutable()->bands
= bands
;
2980 } else if (strcasecmp(tok
, "channels") == 0) {
2981 tok
= strtok_r(NULL
, " ", &saveptr
);
2983 int channels
= atoi(tok
);
2984 if (channels
< 0 || channels
> VTX_TABLE_MAX_CHANNELS
) {
2985 cliShowArgumentRangeError(cmdName
, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS
);
2988 if (channels
< vtxTableConfigMutable()->channels
) {
2989 for (int i
= 0; i
< VTX_TABLE_MAX_BANDS
; i
++) {
2990 vtxTableConfigClearChannels(vtxTableConfigMutable(), i
, channels
);
2993 vtxTableConfigMutable()->channels
= channels
;
2995 } else if (strcasecmp(tok
, "powerlevels") == 0) {
2996 // Number of power levels
2997 tok
= strtok_r(NULL
, " ", &saveptr
);
2999 int levels
= atoi(tok
);
3000 if (levels
< 0 || levels
> VTX_TABLE_MAX_POWER_LEVELS
) {
3001 cliShowArgumentRangeError(cmdName
, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS
);
3003 if (levels
< vtxTableConfigMutable()->powerLevels
) {
3004 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels
);
3005 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels
);
3007 vtxTableConfigMutable()->powerLevels
= levels
;
3010 // XXX Show current level count?
3014 } else if (strcasecmp(tok
, "powervalues") == 0) {
3016 uint16_t power
[VTX_TABLE_MAX_POWER_LEVELS
];
3018 int levels
= vtxTableConfigMutable()->powerLevels
;
3020 memset(power
, 0, sizeof(power
));
3022 for (count
= 0; count
< levels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); count
++) {
3023 int value
= atoi(tok
);
3024 power
[count
] = value
;
3027 // Check remaining tokens
3029 if (count
< levels
) {
3030 cliPrintErrorLinef(cmdName
, "NOT ENOUGH VALUES (EXPECTED %d)", levels
);
3032 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
3033 cliPrintErrorLinef(cmdName
, "TOO MANY VALUES (EXPECTED %d)", levels
);
3037 for (int i
= 0; i
< VTX_TABLE_MAX_POWER_LEVELS
; i
++) {
3038 vtxTableConfigMutable()->powerValues
[i
] = power
[i
];
3041 } else if (strcasecmp(tok
, "powerlabels") == 0) {
3043 char label
[VTX_TABLE_MAX_POWER_LEVELS
][VTX_TABLE_POWER_LABEL_LENGTH
+ 1];
3044 int levels
= vtxTableConfigMutable()->powerLevels
;
3046 for (count
= 0; count
< levels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); count
++) {
3047 strncpy(label
[count
], tok
, VTX_TABLE_POWER_LABEL_LENGTH
);
3048 for (unsigned i
= 0; i
< strlen(label
[count
]); i
++) {
3049 label
[count
][i
] = toupper(label
[count
][i
]);
3053 // Check remaining tokens
3055 if (count
< levels
) {
3056 cliPrintErrorLinef(cmdName
, "NOT ENOUGH LABELS (EXPECTED %d)", levels
);
3058 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
3059 cliPrintErrorLinef(cmdName
, "TOO MANY LABELS (EXPECTED %d)", levels
);
3063 for (int i
= 0; i
< count
; i
++) {
3064 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels
[i
], label
[i
], VTX_TABLE_POWER_LABEL_LENGTH
);
3066 } else if (strcasecmp(tok
, "band") == 0) {
3068 int bands
= vtxTableConfigMutable()->bands
;
3070 tok
= strtok_r(NULL
, " ", &saveptr
);
3075 int band
= atoi(tok
);
3078 if (band
< 0 || band
>= bands
) {
3079 cliShowArgumentRangeError(cmdName
, "BAND NUMBER", 1, bands
);
3084 tok
= strtok_r(NULL
, " ", &saveptr
);
3090 char bandname
[VTX_TABLE_BAND_NAME_LENGTH
+ 1];
3091 memset(bandname
, 0, VTX_TABLE_BAND_NAME_LENGTH
+ 1);
3092 strncpy(bandname
, tok
, VTX_TABLE_BAND_NAME_LENGTH
);
3093 for (unsigned i
= 0; i
< strlen(bandname
); i
++) {
3094 bandname
[i
] = toupper(bandname
[i
]);
3098 tok
= strtok_r(NULL
, " ", &saveptr
);
3104 char bandletter
= toupper(tok
[0]);
3106 uint16_t bandfreq
[VTX_TABLE_MAX_CHANNELS
];
3108 int channels
= vtxTableConfigMutable()->channels
;
3109 bool isFactory
= false;
3111 for (channel
= 0; channel
< channels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); channel
++) {
3112 if (channel
== 0 && !isdigit(tok
[0])) {
3114 if (strcasecmp(tok
, "FACTORY") == 0) {
3116 } else if (strcasecmp(tok
, "CUSTOM") == 0) {
3119 cliPrintErrorLinef(cmdName
, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok
);
3123 int freq
= atoi(tok
);
3125 cliPrintErrorLinef(cmdName
, "INVALID FREQUENCY %s", tok
);
3128 bandfreq
[channel
] = freq
;
3131 if (channel
< channels
) {
3132 cliPrintErrorLinef(cmdName
, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels
);
3134 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
3135 cliPrintErrorLinef(cmdName
, "TOO MANY FREQUENCIES (EXPECTED %d)", channels
);
3139 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames
[band
], bandname
, VTX_TABLE_BAND_NAME_LENGTH
);
3140 vtxTableConfigMutable()->bandLetters
[band
] = bandletter
;
3142 for (int i
= 0; i
< channel
; i
++) {
3143 vtxTableConfigMutable()->frequency
[band
][i
] = bandfreq
[i
];
3145 vtxTableConfigMutable()->isFactoryBand
[band
] = isFactory
;
3148 cliPrintErrorLinef(cmdName
, "INVALID SUBCOMMAND %s", tok
);
3152 static void cliVtxInfo(const char *cmdName
, char *cmdline
)
3156 // Display the available power levels
3157 uint16_t levels
[VTX_TABLE_MAX_POWER_LEVELS
];
3158 uint16_t powers
[VTX_TABLE_MAX_POWER_LEVELS
];
3159 vtxDevice_t
*vtxDevice
= vtxCommonDevice();
3161 uint8_t level_count
= vtxCommonGetVTXPowerLevels(vtxDevice
, levels
, powers
);
3164 for (int i
= 0; i
< level_count
; i
++) {
3165 cliPrintLinef("level %d dBm, power %d mW", levels
[i
], powers
[i
]);
3168 cliPrintErrorLinef(cmdName
, "NO POWER VALUES DEFINED");
3171 cliPrintErrorLinef(cmdName
, "NO VTX");
3174 #endif // USE_VTX_TABLE
3176 #if defined(USE_SIMPLIFIED_TUNING)
3177 static void applySimplifiedTuningAllProfiles(void)
3179 for (unsigned pidProfileIndex
= 0; pidProfileIndex
< PID_PROFILE_COUNT
; pidProfileIndex
++) {
3180 applySimplifiedTuning(pidProfilesMutable(pidProfileIndex
), gyroConfigMutable());
3184 static void cliSimplifiedTuning(const char *cmdName
, char *cmdline
)
3186 if (strcasecmp(cmdline
, "apply") == 0) {
3187 applySimplifiedTuningAllProfiles();
3189 cliPrintLine("Applied simplified tuning.");
3190 } else if (strcasecmp(cmdline
, "disable") == 0) {
3191 for (unsigned pidProfileIndex
= 0; pidProfileIndex
< PID_PROFILE_COUNT
; pidProfileIndex
++) {
3192 disableSimplifiedTuning(pidProfilesMutable(pidProfileIndex
), gyroConfigMutable());
3195 cliPrintLine("Disabled simplified tuning.");
3197 cliShowParseError(cmdName
);
3202 static void printCraftName(dumpFlags_t dumpMask
, const pilotConfig_t
*pilotConfig
)
3204 const bool equalsDefault
= strlen(pilotConfig
->craftName
) == 0;
3205 cliDumpPrintLinef(dumpMask
, equalsDefault
, "\r\n# name: %s", equalsDefault
? emptyName
: pilotConfig
->craftName
);
3208 #if defined(USE_BOARD_INFO)
3210 static void printBoardName(dumpFlags_t dumpMask
)
3212 if (!(dumpMask
& DO_DIFF
) || strlen(getBoardName())) {
3213 cliPrintLinef("board_name %s", getBoardName());
3217 static void cliBoardName(const char *cmdName
, char *cmdline
)
3219 const unsigned int len
= strlen(cmdline
);
3220 const char *boardName
= getBoardName();
3221 if (len
> 0 && strlen(boardName
) != 0 && boardInformationIsSet() && (len
!= strlen(boardName
) || strncmp(boardName
, cmdline
, len
))) {
3222 cliPrintErrorLinef(cmdName
, ERROR_MESSAGE
, "BOARD_NAME", boardName
);
3224 if (len
> 0 && !configIsInCopy
&& setBoardName(cmdline
)) {
3225 boardInformationUpdated
= true;
3227 cliPrintHashLine("Set board_name.");
3229 printBoardName(DUMP_ALL
);
3233 static void printManufacturerId(dumpFlags_t dumpMask
)
3235 if (!(dumpMask
& DO_DIFF
) || strlen(getManufacturerId())) {
3236 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3240 static void cliManufacturerId(const char *cmdName
, char *cmdline
)
3242 const unsigned int len
= strlen(cmdline
);
3243 const char *manufacturerId
= getManufacturerId();
3244 if (len
> 0 && boardInformationIsSet() && strlen(manufacturerId
) != 0 && (len
!= strlen(manufacturerId
) || strncmp(manufacturerId
, cmdline
, len
))) {
3245 cliPrintErrorLinef(cmdName
, ERROR_MESSAGE
, "MANUFACTURER_ID", manufacturerId
);
3247 if (len
> 0 && !configIsInCopy
&& setManufacturerId(cmdline
)) {
3248 boardInformationUpdated
= true;
3250 cliPrintHashLine("Set manufacturer_id.");
3252 printManufacturerId(DUMP_ALL
);
3256 #if defined(USE_SIGNATURE)
3257 static void writeSignature(char *signatureStr
, uint8_t *signature
)
3259 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
3260 tfp_sprintf(&signatureStr
[2 * i
], "%02x", signature
[i
]);
3264 static void cliSignature(const char *cmdName
, char *cmdline
)
3266 const int len
= strlen(cmdline
);
3268 uint8_t signature
[SIGNATURE_LENGTH
] = {0};
3270 if (len
!= 2 * SIGNATURE_LENGTH
) {
3271 cliPrintErrorLinef(cmdName
, "INVALID LENGTH: %d (EXPECTED: %d)", len
, 2 * SIGNATURE_LENGTH
);
3276 #define BLOCK_SIZE 2
3277 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
3278 char temp
[BLOCK_SIZE
+ 1];
3279 strncpy(temp
, &cmdline
[i
* BLOCK_SIZE
], BLOCK_SIZE
);
3280 temp
[BLOCK_SIZE
] = '\0';
3282 unsigned result
= strtoul(temp
, &end
, 16);
3283 if (end
== &temp
[BLOCK_SIZE
]) {
3284 signature
[i
] = result
;
3286 cliPrintErrorLinef(cmdName
, "INVALID CHARACTER FOUND: %c", end
[0]);
3294 char signatureStr
[SIGNATURE_LENGTH
* 2 + 1] = {0};
3295 if (len
> 0 && signatureIsSet() && memcmp(signature
, getSignature(), SIGNATURE_LENGTH
)) {
3296 writeSignature(signatureStr
, getSignature());
3297 cliPrintErrorLinef(cmdName
, ERROR_MESSAGE
, "SIGNATURE", signatureStr
);
3299 if (len
> 0 && !configIsInCopy
&& setSignature(signature
)) {
3300 signatureUpdated
= true;
3302 writeSignature(signatureStr
, getSignature());
3304 cliPrintHashLine("Set signature.");
3305 } else if (signatureUpdated
|| signatureIsSet()) {
3306 writeSignature(signatureStr
, getSignature());
3309 cliPrintLinef("signature %s", signatureStr
);
3314 #undef ERROR_MESSAGE
3316 #endif // USE_BOARD_INFO
3318 static void cliMcuId(const char *cmdName
, char *cmdline
)
3323 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0
, U_ID_1
, U_ID_2
);
3326 static void printFeature(dumpFlags_t dumpMask
, const uint32_t mask
, const uint32_t defaultMask
, const char *headingStr
)
3328 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3329 for (unsigned i
= 0; i
< ARRAYLEN(featureNames
); i
++) { // disabled features first
3330 if (featureNames
[i
]) { //Skip unused
3331 const char *format
= "feature -%s";
3332 const bool equalsDefault
= (~defaultMask
| mask
) & (1U << i
);
3333 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3334 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1U << i
), format
, featureNames
[i
]);
3335 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, featureNames
[i
]);
3338 for (unsigned i
= 0; i
< ARRAYLEN(featureNames
); i
++) { // enabled features
3339 if (featureNames
[i
]) { //Skip unused
3340 const char *format
= "feature %s";
3341 if (defaultMask
& (1U << i
)) {
3342 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1U << i
), format
, featureNames
[i
]);
3344 if (mask
& (1U << i
)) {
3345 const bool equalsDefault
= (defaultMask
| ~mask
) & (1U << i
);
3346 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3347 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, featureNames
[i
]);
3353 static void printFeatureList(const char* header
, uint32_t mask
, const char* delimiter
, bool lineFeed
)
3358 for (unsigned i
= 0; i
< ARRAYLEN(featureNames
); i
++) {
3359 if (featureNames
[i
] && (mask
& (1U << i
))) {
3360 cliPrintf("%s%s", i
? delimiter
: "", featureNames
[i
]);
3368 static void cliFeature(const char *cmdName
, char *cmdline
)
3370 uint32_t len
= strlen(cmdline
);
3371 const uint32_t mask
= featureConfig()->enabledFeatures
;
3372 if (len
== 0 // `feature`
3373 || strncasecmp(cmdline
, "list", len
) == 0) { // old `feature list` invocation
3374 printFeatureList("Enabled: ", mask
, " ", true);
3375 printFeatureList("Available: ", ~mask
& featuresSupportedByBuild
, " ", true);
3376 printFeatureList("Unavailable: ", ~featuresSupportedByBuild
, " ", true);
3378 bool remove
= false;
3379 if (cmdline
[0] == '-') {
3382 cmdline
++; // skip over -
3387 int featureIdx
= -1;
3388 for (unsigned i
= 0; !found
&& i
< ARRAYLEN(featureNames
); i
++) {
3389 if (featureNames
[i
] && strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
3395 uint32_t feature
= 1U << featureIdx
;
3397 if ((feature
& featuresSupportedByBuild
) == 0) {
3398 verb
= "Unavailable";
3399 } else if (remove
) {
3400 featureConfigClear(feature
);
3401 verb
= (mask
& feature
) ? "Disabled" : "AlreadyDisabled";
3403 featureConfigSet(feature
);
3404 verb
= (mask
& feature
) ? "AlreadyEnabled" : "Enabled";
3406 cliPrintLinef("%s %s", verb
, featureNames
[featureIdx
]);
3407 } else if (found
> 1) {
3408 cliPrintErrorLinef(cmdName
, "Multiple features match %s", cmdline
);
3409 } else /* found <= 0 */ {
3410 cliPrintErrorLinef(cmdName
, ERROR_INVALID_NAME
, cmdline
);
3415 #if defined(USE_BEEPER)
3416 static void printBeeper(dumpFlags_t dumpMask
, const uint32_t offFlags
, const uint32_t offFlagsDefault
, const char *name
, const uint32_t allowedFlags
, const char *headingStr
)
3418 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3419 const uint8_t beeperCount
= beeperTableEntryCount();
3420 for (int32_t i
= 0; i
< beeperCount
- 1; i
++) {
3421 const uint32_t beeperModeMask
= beeperModeMaskForTableIndex(i
);
3422 if (beeperModeMask
& allowedFlags
) {
3423 const char *formatOff
= "%s -%s";
3424 const char *formatOn
= "%s %s";
3425 const bool equalsDefault
= ~(offFlags
^ offFlagsDefault
) & beeperModeMask
;
3426 cliDefaultPrintLinef(dumpMask
, equalsDefault
, offFlags
& beeperModeMask
? formatOn
: formatOff
, name
, beeperNameForTableIndex(i
));
3427 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3428 cliDumpPrintLinef(dumpMask
, equalsDefault
, offFlags
& beeperModeMask
? formatOff
: formatOn
, name
, beeperNameForTableIndex(i
));
3433 static void processBeeperCommand(const char *cmdName
, char *cmdline
, uint32_t *offFlags
, const uint32_t allowedFlags
)
3435 uint32_t len
= strlen(cmdline
);
3436 uint8_t beeperCount
= beeperTableEntryCount();
3439 cliPrintf("Disabled:");
3440 for (int32_t i
= 0; ; i
++) {
3441 if (i
== beeperCount
- 1) {
3447 if (beeperModeMaskForTableIndex(i
) & *offFlags
)
3448 cliPrintf(" %s", beeperNameForTableIndex(i
));
3451 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3452 cliPrint("Available:");
3453 for (uint32_t i
= 0; i
< beeperCount
; i
++) {
3454 if (beeperModeMaskForTableIndex(i
) & allowedFlags
) {
3455 cliPrintf(" %s", beeperNameForTableIndex(i
));
3460 bool remove
= false;
3461 if (cmdline
[0] == '-') {
3462 remove
= true; // this is for beeper OFF condition
3467 for (uint32_t i
= 0; ; i
++) {
3468 if (i
== beeperCount
) {
3469 cliPrintErrorLinef(cmdName
, ERROR_INVALID_NAME
, cmdline
);
3472 if (strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0 && beeperModeMaskForTableIndex(i
) & (allowedFlags
| BEEPER_GET_FLAG(BEEPER_ALL
))) {
3473 if (remove
) { // beeper off
3474 if (i
== BEEPER_ALL
- 1) {
3475 *offFlags
= allowedFlags
;
3477 *offFlags
|= beeperModeMaskForTableIndex(i
);
3479 cliPrint("Disabled");
3482 if (i
== BEEPER_ALL
- 1) {
3485 *offFlags
&= ~beeperModeMaskForTableIndex(i
);
3487 cliPrint("Enabled");
3489 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
3496 #if defined(USE_DSHOT)
3497 static void cliBeacon(const char *cmdName
, char *cmdline
)
3499 processBeeperCommand(cmdName
, cmdline
, &(beeperConfigMutable()->dshotBeaconOffFlags
), DSHOT_BEACON_ALLOWED_MODES
);
3503 static void cliBeeper(const char *cmdName
, char *cmdline
)
3505 processBeeperCommand(cmdName
, cmdline
, &(beeperConfigMutable()->beeper_off_flags
), BEEPER_ALLOWED_MODES
);
3509 #if defined(USE_RX_BIND)
3510 static void cliRxBind(const char *cmdName
, char *cmdline
)
3513 if (!startRxBind()) {
3514 cliPrintErrorLinef(cmdName
, "Not supported.");
3516 cliPrintLinef("Binding...");
3521 static void printMap(dumpFlags_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
, const char *headingStr
)
3523 bool equalsDefault
= true;
3525 char bufDefault
[16];
3528 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3529 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3530 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3531 if (defaultRxConfig
) {
3532 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3533 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
3538 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3539 const char *formatMap
= "map %s";
3540 if (defaultRxConfig
) {
3541 bufDefault
[i
] = '\0';
3542 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
3544 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
3547 static void cliMap(const char *cmdName
, char *cmdline
)
3550 char buf
[RX_MAPPABLE_CHANNEL_COUNT
+ 1];
3552 uint32_t len
= strlen(cmdline
);
3553 if (len
== RX_MAPPABLE_CHANNEL_COUNT
) {
3555 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3556 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
3560 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3561 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
3563 if (strchr(rcChannelLetters
, buf
[i
]) && !strchr(buf
+ i
+ 1, buf
[i
]))
3566 cliShowParseError(cmdName
);
3569 parseRcChannels(buf
, rxConfigMutable());
3570 } else if (len
> 0) {
3571 cliShowInvalidArgumentCountError(cmdName
);
3575 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3576 buf
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
3580 cliPrintLinef("map %s", buf
);
3583 static char *skipSpace(char *buffer
)
3585 while (*(buffer
) == ' ') {
3592 static char *checkCommand(char *cmdline
, const char *command
)
3594 if (!strncasecmp(cmdline
, command
, strlen(command
)) // command names match
3595 && (isspace((unsigned)cmdline
[strlen(command
)]) || cmdline
[strlen(command
)] == 0)) {
3596 return skipSpace(cmdline
+ strlen(command
) + 1);
3602 static void cliRebootEx(rebootTarget_e rebootTarget
)
3604 cliPrint("\r\nRebooting");
3606 waitForSerialPortToFinishTransmitting(cliPort
);
3609 switch (rebootTarget
) {
3610 case REBOOT_TARGET_BOOTLOADER_ROM
:
3611 systemResetToBootloader(BOOTLOADER_REQUEST_ROM
);
3614 #if defined(USE_FLASH_BOOT_LOADER)
3615 case REBOOT_TARGET_BOOTLOADER_FLASH
:
3616 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH
);
3620 case REBOOT_TARGET_FIRMWARE
:
3628 static void cliReboot(void)
3630 cliRebootEx(REBOOT_TARGET_FIRMWARE
);
3633 static void cliBootloader(const char *cmdName
, char *cmdline
)
3635 rebootTarget_e rebootTarget
;
3637 #if !defined(USE_FLASH_BOOT_LOADER)
3640 strncasecmp(cmdline
, "rom", 3) == 0) {
3641 rebootTarget
= REBOOT_TARGET_BOOTLOADER_ROM
;
3643 cliPrintHashLine("restarting in ROM bootloader mode");
3644 #if defined(USE_FLASH_BOOT_LOADER)
3645 } else if (isEmpty(cmdline
) || strncasecmp(cmdline
, "flash", 5) == 0) {
3646 rebootTarget
= REBOOT_TARGET_BOOTLOADER_FLASH
;
3648 cliPrintHashLine("restarting in flash bootloader mode");
3651 cliPrintErrorLinef(cmdName
, "Invalid option");
3656 cliRebootEx(rebootTarget
);
3659 static void cliExitCmd(const char *cmdName
, char *cmdline
)
3663 const bool reboot
= strcasecmp(cmdline
, "noreboot") != 0;
3665 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3667 cliPrintHashLine("leaving CLI mode, no reboot");
3673 static void cliGpsPassthrough(const char *cmdName
, char *cmdline
)
3678 if (!gpsPassthrough(cliPort
)) {
3679 cliPrintErrorLinef(cmdName
, "GPS forwarding failed");
3684 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3685 static void cliPrintGyroRegisters(uint8_t whichSensor
)
3687 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor
, MPU_RA_WHO_AM_I
));
3688 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_CONFIG
));
3689 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_GYRO_CONFIG
));
3692 static void cliDumpGyroRegisters(const char *cmdName
, char *cmdline
)
3697 #ifdef USE_MULTI_GYRO
3698 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_1
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
3699 cliPrintLinef("\r\n# Gyro 1");
3700 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
3702 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_2
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
3703 cliPrintLinef("\r\n# Gyro 2");
3704 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2
);
3707 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
3712 static int parseOutputIndex(const char *cmdName
, char *pch
, bool allowAllEscs
)
3714 int outputIndex
= atoi(pch
);
3715 if ((outputIndex
>= 0) && (outputIndex
< getMotorCount())) {
3716 cliPrintLinef("Using output %d.", outputIndex
);
3717 } else if (allowAllEscs
&& outputIndex
== ALL_MOTORS
) {
3718 cliPrintLinef("Using all outputs.");
3720 cliPrintErrorLinef(cmdName
, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3728 #if defined(USE_DSHOT)
3729 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3731 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3732 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3733 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3741 #define ESC_INFO_VERSION_POSITION 12
3743 static void printEscInfo(const char *cmdName
, const uint8_t *escInfoBuffer
, uint8_t bytesRead
)
3745 bool escInfoReceived
= false;
3746 if (bytesRead
> ESC_INFO_VERSION_POSITION
) {
3747 uint8_t escInfoVersion
;
3748 uint8_t frameLength
;
3749 if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 254) {
3750 escInfoVersion
= ESC_INFO_BLHELI32
;
3751 frameLength
= ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
;
3752 } else if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 255) {
3753 escInfoVersion
= ESC_INFO_KISS_V2
;
3754 frameLength
= ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE
;
3756 escInfoVersion
= ESC_INFO_KISS_V1
;
3757 frameLength
= ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE
;
3760 if (bytesRead
== frameLength
) {
3761 escInfoReceived
= true;
3763 if (calculateCrc8(escInfoBuffer
, frameLength
- 1) == escInfoBuffer
[frameLength
- 1]) {
3764 uint8_t firmwareVersion
= 0;
3765 uint8_t firmwareSubVersion
= 0;
3766 uint8_t escType
= 0;
3767 switch (escInfoVersion
) {
3768 case ESC_INFO_KISS_V1
:
3769 firmwareVersion
= escInfoBuffer
[12];
3770 firmwareSubVersion
= (escInfoBuffer
[13] & 0x1f) + 97;
3771 escType
= (escInfoBuffer
[13] & 0xe0) >> 5;
3774 case ESC_INFO_KISS_V2
:
3775 firmwareVersion
= escInfoBuffer
[13];
3776 firmwareSubVersion
= escInfoBuffer
[14];
3777 escType
= escInfoBuffer
[15];
3780 case ESC_INFO_BLHELI32
:
3781 firmwareVersion
= escInfoBuffer
[13];
3782 firmwareSubVersion
= escInfoBuffer
[14];
3783 escType
= escInfoBuffer
[15];
3788 cliPrint("ESC Type: ");
3789 switch (escInfoVersion
) {
3790 case ESC_INFO_KISS_V1
:
3791 case ESC_INFO_KISS_V2
:
3794 cliPrintLine("KISS8A");
3798 cliPrintLine("KISS16A");
3802 cliPrintLine("KISS24A");
3806 cliPrintLine("KISS Ultralite");
3810 cliPrintLine("unknown");
3816 case ESC_INFO_BLHELI32
:
3818 char *escType
= (char *)(escInfoBuffer
+ 31);
3820 cliPrintLine(escType
);
3826 cliPrint("MCU Serial No: 0x");
3827 for (int i
= 0; i
< 12; i
++) {
3828 if (i
&& (i
% 3 == 0)) {
3831 cliPrintf("%02x", escInfoBuffer
[i
]);
3835 switch (escInfoVersion
) {
3836 case ESC_INFO_KISS_V1
:
3837 case ESC_INFO_KISS_V2
:
3838 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion
/ 100, firmwareVersion
% 100, (char)firmwareSubVersion
);
3841 case ESC_INFO_BLHELI32
:
3842 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion
, firmwareSubVersion
);
3846 if (escInfoVersion
== ESC_INFO_KISS_V2
|| escInfoVersion
== ESC_INFO_BLHELI32
) {
3847 cliPrintLinef("Rotation Direction: %s", escInfoBuffer
[16] ? "reversed" : "normal");
3848 cliPrintLinef("3D: %s", escInfoBuffer
[17] ? "on" : "off");
3849 if (escInfoVersion
== ESC_INFO_BLHELI32
) {
3850 uint8_t setting
= escInfoBuffer
[18];
3851 cliPrint("Low voltage Limit: ");
3854 cliPrintLine("off");
3858 cliPrintLine("unsupported");
3862 cliPrintLinef("%d.%01d", setting
/ 10, setting
% 10);
3867 setting
= escInfoBuffer
[19];
3868 cliPrint("Current Limit: ");
3871 cliPrintLine("off");
3875 cliPrintLine("unsupported");
3879 cliPrintLinef("%d", setting
);
3884 for (int i
= 0; i
< 4; i
++) {
3885 setting
= escInfoBuffer
[i
+ 20];
3886 cliPrintLinef("LED %d: %s", i
, setting
? (setting
== 255) ? "unsupported" : "on" : "off");
3891 cliPrintErrorLinef(cmdName
, "CHECKSUM ERROR.");
3896 if (!escInfoReceived
) {
3897 cliPrintLine("No Info.");
3901 static void executeEscInfoCommand(const char *cmdName
, uint8_t escIndex
)
3903 cliPrintLinef("Info for ESC %d:", escIndex
);
3905 uint8_t escInfoBuffer
[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
];
3907 startEscDataRead(escInfoBuffer
, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
);
3909 dshotCommandWrite(escIndex
, getMotorCount(), DSHOT_CMD_ESC_INFO
, DSHOT_CMD_TYPE_BLOCKING
);
3913 printEscInfo(cmdName
, escInfoBuffer
, getNumberEscBytesRead());
3915 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3917 static void cliDshotProg(const char *cmdName
, char *cmdline
)
3919 if (isEmpty(cmdline
) || !isMotorProtocolDshot()) {
3920 cliShowParseError(cmdName
);
3926 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3929 bool firstCommand
= true;
3930 while (pch
!= NULL
) {
3933 escIndex
= parseOutputIndex(cmdName
, pch
, true);
3934 if (escIndex
== -1) {
3941 int command
= atoi(pch
);
3942 if (command
>= 0 && command
< DSHOT_MIN_THROTTLE
) {
3944 // pwmDisableMotors();
3947 firstCommand
= false;
3950 if (command
!= DSHOT_CMD_ESC_INFO
) {
3951 dshotCommandWrite(escIndex
, getMotorCount(), command
, DSHOT_CMD_TYPE_BLOCKING
);
3953 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3954 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
3955 if (escIndex
!= ALL_MOTORS
) {
3956 executeEscInfoCommand(cmdName
, escIndex
);
3958 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
3959 executeEscInfoCommand(cmdName
, i
);
3965 cliPrintLine("Not supported.");
3969 cliPrintLinef("Command Sent: %d", command
);
3972 cliPrintErrorLinef(cmdName
, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE
- 1);
3980 pch
= strtok_r(NULL
, " ", &saveptr
);
3987 #ifdef USE_ESCSERIAL
3988 static void cliEscPassthrough(const char *cmdName
, char *cmdline
)
3990 if (isEmpty(cmdline
)) {
3991 cliShowInvalidArgumentCountError(cmdName
);
3997 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
4001 while (pch
!= NULL
) {
4004 if (strncasecmp(pch
, "sk", strlen(pch
)) == 0) {
4005 mode
= PROTOCOL_SIMONK
;
4006 } else if (strncasecmp(pch
, "bl", strlen(pch
)) == 0) {
4007 mode
= PROTOCOL_BLHELI
;
4008 } else if (strncasecmp(pch
, "ki", strlen(pch
)) == 0) {
4009 mode
= PROTOCOL_KISS
;
4010 } else if (strncasecmp(pch
, "cc", strlen(pch
)) == 0) {
4011 mode
= PROTOCOL_CASTLE
;
4013 cliShowParseError(cmdName
);
4019 escIndex
= parseOutputIndex(cmdName
, pch
, mode
== PROTOCOL_KISS
);
4020 if (escIndex
== -1) {
4026 cliShowInvalidArgumentCountError(cmdName
);
4034 pch
= strtok_r(NULL
, " ", &saveptr
);
4037 if (!escEnablePassthrough(cliPort
, &motorConfig()->dev
, escIndex
, mode
)) {
4038 cliPrintErrorLinef(cmdName
, "Error starting ESC connection");
4043 #ifndef USE_QUAD_MIXER_ONLY
4044 static void cliMixer(const char *cmdName
, char *cmdline
)
4048 len
= strlen(cmdline
);
4051 cliPrintLinef("Mixer: %s", mixerNames
[mixerConfig()->mixerMode
- 1]);
4053 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
4054 cliPrint("Available:");
4055 for (uint32_t i
= 0; ; i
++) {
4056 if (mixerNames
[i
] == NULL
)
4058 cliPrintf(" %s", mixerNames
[i
]);
4064 for (uint32_t i
= 0; ; i
++) {
4065 if (mixerNames
[i
] == NULL
) {
4066 cliPrintErrorLinef(cmdName
, ERROR_INVALID_NAME
, cmdline
);
4069 if (strncasecmp(cmdline
, mixerNames
[i
], len
) == 0) {
4070 mixerConfigMutable()->mixerMode
= i
+ 1;
4075 cliMixer(cmdName
, "");
4079 static void cliMotor(const char *cmdName
, char *cmdline
)
4081 if (isEmpty(cmdline
)) {
4082 cliShowInvalidArgumentCountError(cmdName
);
4091 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
4093 while (pch
!= NULL
) {
4096 motorIndex
= parseOutputIndex(cmdName
, pch
, true);
4097 if (motorIndex
== -1) {
4103 motorValue
= atoi(pch
);
4108 pch
= strtok_r(NULL
, " ", &saveptr
);
4112 if (motorValue
< PWM_RANGE_MIN
|| motorValue
> PWM_RANGE_MAX
) {
4113 cliShowArgumentRangeError(cmdName
, "VALUE", 1000, 2000);
4115 uint32_t motorOutputValue
= motorConvertFromExternal(motorValue
);
4117 if (motorIndex
!= ALL_MOTORS
) {
4118 motor_disarmed
[motorIndex
] = motorOutputValue
;
4120 cliPrintLinef("motor %d: %d", motorIndex
, motorOutputValue
);
4122 for (int i
= 0; i
< getMotorCount(); i
++) {
4123 motor_disarmed
[i
] = motorOutputValue
;
4126 cliPrintLinef("all motors: %d", motorOutputValue
);
4130 cliShowInvalidArgumentCountError(cmdName
);
4135 static void cliPlaySound(const char *cmdName
, char *cmdline
)
4139 static int lastSoundIdx
= -1;
4141 if (isEmpty(cmdline
)) {
4142 i
= lastSoundIdx
+ 1; //next sound index
4143 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
4144 while (true) { //no name for index; try next one
4145 if (++i
>= beeperTableEntryCount())
4146 i
= 0; //if end then wrap around to first entry
4147 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
4148 break; //if name OK then play sound below
4149 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
4150 cliPrintErrorLinef(cmdName
, "ERROR PLAYING SOUND");
4155 } else { //index value was given
4157 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
4158 cliPrintLinef("No sound for index %d", i
);
4164 cliPrintLinef("Playing sound %d: %s", i
, name
);
4165 beeper(beeperModeForTableIndex(i
));
4169 static void cliProfile(const char *cmdName
, char *cmdline
)
4171 if (isEmpty(cmdline
)) {
4172 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4175 const int i
= atoi(cmdline
);
4176 if (i
>= 0 && i
< PID_PROFILE_COUNT
) {
4177 changePidProfile(i
);
4178 cliProfile(cmdName
, "");
4180 cliPrintErrorLinef(cmdName
, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT
- 1);
4185 static void cliRateProfile(const char *cmdName
, char *cmdline
)
4187 if (isEmpty(cmdline
)) {
4188 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4191 const int i
= atoi(cmdline
);
4192 if (i
>= 0 && i
< CONTROL_RATE_PROFILE_COUNT
) {
4193 changeControlRateProfile(i
);
4194 cliRateProfile(cmdName
, "");
4196 cliPrintErrorLinef(cmdName
, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT
- 1);
4201 static void cliDumpPidProfile(const char *cmdName
, uint8_t pidProfileIndex
, dumpFlags_t dumpMask
)
4203 if (pidProfileIndex
>= PID_PROFILE_COUNT
) {
4208 pidProfileIndexToUse
= pidProfileIndex
;
4211 cliProfile(cmdName
, "");
4213 char profileStr
[10];
4214 tfp_sprintf(profileStr
, "profile %d", pidProfileIndex
);
4215 dumpAllValues(cmdName
, PROFILE_VALUE
, dumpMask
, profileStr
);
4217 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4220 static void cliDumpRateProfile(const char *cmdName
, uint8_t rateProfileIndex
, dumpFlags_t dumpMask
)
4222 if (rateProfileIndex
>= CONTROL_RATE_PROFILE_COUNT
) {
4227 rateProfileIndexToUse
= rateProfileIndex
;
4230 cliRateProfile(cmdName
, "");
4232 char rateProfileStr
[14];
4233 tfp_sprintf(rateProfileStr
, "rateprofile %d", rateProfileIndex
);
4234 dumpAllValues(cmdName
, PROFILE_RATE_VALUE
, dumpMask
, rateProfileStr
);
4236 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4239 #ifdef USE_CLI_BATCH
4240 static void cliPrintCommandBatchWarning(const char *cmdName
, const char *warning
)
4242 cliPrintErrorLinef(cmdName
, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4244 cliPrintErrorLinef(cmdName
, warning
);
4248 static void resetCommandBatch(void)
4250 commandBatchActive
= false;
4251 commandBatchError
= false;
4254 static void cliBatch(const char *cmdName
, char *cmdline
)
4256 if (strncasecmp(cmdline
, "start", 5) == 0) {
4257 if (!commandBatchActive
) {
4258 commandBatchActive
= true;
4259 commandBatchError
= false;
4261 cliPrintLine("Command batch started");
4262 } else if (strncasecmp(cmdline
, "end", 3) == 0) {
4263 if (commandBatchActive
&& commandBatchError
) {
4264 cliPrintCommandBatchWarning(cmdName
, NULL
);
4266 cliPrintLine("Command batch ended");
4268 resetCommandBatch();
4270 cliPrintErrorLinef(cmdName
, "Invalid option");
4275 static bool prepareSave(void)
4278 #ifdef USE_CLI_BATCH
4279 if (commandBatchActive
&& commandBatchError
) {
4284 #if defined(USE_BOARD_INFO)
4285 if (boardInformationUpdated
) {
4286 persistBoardInformation();
4288 #if defined(USE_SIGNATURE)
4289 if (signatureUpdated
) {
4293 #endif // USE_BOARD_INFO
4298 bool tryPrepareSave(const char *cmdName
)
4300 bool success
= prepareSave();
4301 #if defined(USE_CLI_BATCH)
4303 cliPrintCommandBatchWarning(cmdName
, "PLEASE FIX ERRORS THEN 'SAVE'");
4304 resetCommandBatch();
4316 static void cliSave(const char *cmdName
, char *cmdline
)
4320 if (tryPrepareSave(cmdName
)) {
4322 cliPrintHashLine("saving");
4324 if (strcasecmp(cmdline
, "noreboot") == 0) {
4331 static void cliDefaults(const char *cmdName
, char *cmdline
)
4333 bool saveConfigs
= true;
4334 uint16_t parameterGroupId
= 0;
4337 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
4338 bool expectParameterGroupId
= false;
4339 while (tok
!= NULL
) {
4340 if (expectParameterGroupId
) {
4341 parameterGroupId
= atoi(tok
);
4342 expectParameterGroupId
= false;
4344 if (!parameterGroupId
) {
4345 cliShowParseError(cmdName
);
4348 } else if (strcasestr(tok
, "group_id")) {
4349 expectParameterGroupId
= true;
4350 } else if (strcasestr(tok
, "nosave")) {
4351 saveConfigs
= false;
4353 cliShowParseError(cmdName
);
4358 tok
= strtok_r(NULL
, " ", &saveptr
);
4361 if (expectParameterGroupId
) {
4362 cliShowParseError(cmdName
);
4367 if (parameterGroupId
) {
4368 cliPrintLinef("\r\n# resetting group %d to defaults", parameterGroupId
);
4371 cliPrintHashLine("resetting to defaults");
4376 #ifdef USE_CLI_BATCH
4377 // Reset only the error state and allow the batch active state to remain.
4378 // This way if a "defaults nosave" was issued after the "batch on" we'll
4379 // only reset the current error state but the batch will still be active
4380 // for subsequent commands.
4381 commandBatchError
= false;
4384 #if defined(USE_SIMPLIFIED_TUNING)
4385 applySimplifiedTuningAllProfiles();
4388 if (parameterGroupId
) {
4389 restoreConfigs(parameterGroupId
);
4392 if (saveConfigs
&& tryPrepareSave(cmdName
)) {
4393 writeUnmodifiedConfigToEEPROM();
4399 static void cliPrintVarDefault(const char *cmdName
, const clivalue_t
*value
)
4401 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
4403 const char *defaultFormat
= "Default value: ";
4404 const int valueOffset
= getValueOffset(value
);
4405 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
4406 if (!equalsDefault
) {
4407 cliPrintf(defaultFormat
, value
->name
);
4408 printValuePointer(cmdName
, value
, (uint8_t*)pg
->address
+ valueOffset
, false);
4414 STATIC_UNIT_TESTED
void cliGet(const char *cmdName
, char *cmdline
)
4416 const clivalue_t
*val
;
4417 int matchedCommands
= 0;
4419 pidProfileIndexToUse
= getCurrentPidProfileIndex();
4420 rateProfileIndexToUse
= getCurrentControlRateProfileIndex();
4422 backupAndResetConfigs();
4424 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4425 if (strcasestr(valueTable
[i
].name
, cmdline
)) {
4426 val
= &valueTable
[i
];
4427 if (matchedCommands
> 0) {
4430 cliPrintf("%s = ", valueTable
[i
].name
);
4431 cliPrintVar(cmdName
, val
, 0);
4433 switch (val
->type
& VALUE_SECTION_MASK
) {
4435 cliProfile(cmdName
, "");
4438 case PROFILE_RATE_VALUE
:
4439 cliRateProfile(cmdName
, "");
4446 cliPrintVarRange(val
);
4447 cliPrintVarDefault(cmdName
, val
);
4455 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4456 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4458 if (!matchedCommands
) {
4459 cliPrintErrorLinef(cmdName
, ERROR_INVALID_NAME
, cmdline
);
4463 static uint8_t getWordLength(const char *bufBegin
, const char *bufEnd
)
4465 while (*(bufEnd
- 1) == ' ') {
4469 return bufEnd
- bufBegin
;
4472 uint16_t cliGetSettingIndex(const char *name
, size_t length
)
4474 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4475 const char *settingName
= valueTable
[i
].name
;
4477 // ensure exact match when setting to prevent setting variables with longer names
4478 if (strncasecmp(name
, settingName
, length
) == 0 && length
== strlen(settingName
)) {
4482 return valueTableEntryCount
;
4485 STATIC_UNIT_TESTED
void cliSet(const char *cmdName
, char *cmdline
)
4487 const uint32_t len
= strlen(cmdline
);
4490 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
4491 cliPrintLine("Current settings: ");
4493 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4494 const clivalue_t
*val
= &valueTable
[i
];
4495 cliPrintf("%s = ", valueTable
[i
].name
);
4496 cliPrintVar(cmdName
, val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4499 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
4502 uint8_t variableNameLength
= getWordLength(cmdline
, eqptr
);
4504 // skip the '=' and any ' ' characters
4506 eqptr
= skipSpace(eqptr
);
4508 const uint16_t index
= cliGetSettingIndex(cmdline
, variableNameLength
);
4509 if (index
>= valueTableEntryCount
) {
4510 cliPrintErrorLinef(cmdName
, ERROR_INVALID_NAME
, cmdline
);
4513 const clivalue_t
*val
= &valueTable
[index
];
4515 bool valueChanged
= false;
4517 switch (val
->type
& VALUE_MODE_MASK
) {
4519 if ((val
->type
& VALUE_TYPE_MASK
) == VAR_UINT32
) {
4520 uint32_t value
= strtoul(eqptr
, NULL
, 10);
4522 if (value
<= val
->config
.u32Max
) {
4523 cliSetVar(val
, value
);
4524 valueChanged
= true;
4526 } else if ((val
->type
& VALUE_TYPE_MASK
) == VAR_INT32
) {
4527 int32_t value
= strtol(eqptr
, NULL
, 10);
4529 // INT32s are limited to being symmetric, so we test both bounds with the same magnitude
4530 if (value
<= val
->config
.d32Max
&& value
>= -val
->config
.d32Max
) {
4531 cliSetVar(val
, value
);
4532 valueChanged
= true;
4535 int value
= atoi(eqptr
);
4539 getMinMax(val
, &min
, &max
);
4541 if (value
>= min
&& value
<= max
) {
4542 cliSetVar(val
, value
);
4543 valueChanged
= true;
4552 if ((val
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
4553 tableIndex
= TABLE_OFF_ON
;
4555 tableIndex
= val
->config
.lookup
.tableIndex
;
4557 const lookupTableEntry_t
*tableEntry
= &lookupTables
[tableIndex
];
4558 bool matched
= false;
4559 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
4560 matched
= tableEntry
->values
[tableValueIndex
] && strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
4563 value
= tableValueIndex
;
4565 cliSetVar(val
, value
);
4566 valueChanged
= true;
4574 const uint8_t arrayLength
= val
->config
.array
.length
;
4575 char *valPtr
= eqptr
;
4578 while (i
< arrayLength
&& valPtr
!= NULL
) {
4580 valPtr
= skipSpace(valPtr
);
4582 // process substring starting at valPtr
4583 // note: no need to copy substrings for atoi()
4584 // it stops at the first character that cannot be converted...
4585 switch (val
->type
& VALUE_TYPE_MASK
) {
4589 // fetch data pointer
4590 uint8_t *data
= (uint8_t *)cliGetValuePointer(val
) + i
;
4592 *data
= (uint8_t)atoi((const char*) valPtr
);
4598 // fetch data pointer
4599 int8_t *data
= (int8_t *)cliGetValuePointer(val
) + i
;
4601 *data
= (int8_t)atoi((const char*) valPtr
);
4607 // fetch data pointer
4608 uint16_t *data
= (uint16_t *)cliGetValuePointer(val
) + i
;
4610 *data
= (uint16_t)atoi((const char*) valPtr
);
4616 // fetch data pointer
4617 int16_t *data
= (int16_t *)cliGetValuePointer(val
) + i
;
4619 *data
= (int16_t)atoi((const char*) valPtr
);
4625 // fetch data pointer
4626 uint32_t *data
= (uint32_t *)cliGetValuePointer(val
) + i
;
4628 *data
= (uint32_t)strtoul((const char*) valPtr
, NULL
, 10);
4634 // fetch data pointer
4635 int32_t *data
= (int32_t *)cliGetValuePointer(val
) + i
;
4637 *data
= (int32_t)strtol((const char*) valPtr
, NULL
, 10);
4643 // find next comma (or end of string)
4644 valPtr
= strchr(valPtr
, ',') + 1;
4651 valueChanged
= true;
4655 char *valPtr
= eqptr
;
4656 valPtr
= skipSpace(valPtr
);
4658 const unsigned int len
= strlen(valPtr
);
4659 const uint8_t min
= val
->config
.string
.minlength
;
4660 const uint8_t max
= val
->config
.string
.maxlength
;
4661 const bool updatable
= ((val
->config
.string
.flags
& STRING_FLAGS_WRITEONCE
) == 0 ||
4662 strlen((char *)cliGetValuePointer(val
)) == 0 ||
4663 strncmp(valPtr
, (char *)cliGetValuePointer(val
), len
) == 0);
4665 if (updatable
&& len
> 0 && len
<= max
) {
4666 memset((char *)cliGetValuePointer(val
), 0, max
);
4667 if (len
>= min
&& strncmp(valPtr
, emptyName
, len
)) {
4668 memcpy((char *)cliGetValuePointer(val
), valPtr
, len
);
4670 valueChanged
= true;
4672 cliPrintErrorLinef(cmdName
, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max
);
4679 cliPrintf("%s set to ", val
->name
);
4680 cliPrintVar(cmdName
, val
, 0);
4682 cliPrintErrorLinef(cmdName
, "INVALID VALUE");
4683 cliPrintVarRange(val
);
4688 // no equals, check for matching variables.
4689 cliGet(cmdName
, cmdline
);
4693 static const char *getMcuTypeById(mcuTypeId_e id
)
4695 if (id
< ARRAYLEN(mcuTypeNames
)) {
4696 return mcuTypeNames
[id
];
4702 static void cliStatus(const char *cmdName
, char *cmdline
)
4707 // MCU type, clock, vrefint, core temperature
4709 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock
/ 1000000));
4711 #if defined(STM32F4) || defined(STM32G4) || defined(APM32F4)
4712 // Only F4 and G4 is capable of switching between HSE/HSI (for now)
4713 int sysclkSource
= SystemSYSCLKSource();
4715 const char *SYSCLKSource
[] = { "HSI", "HSE", "PLLP", "PLLR" };
4716 const char *PLLSource
[] = { "-HSI", "-HSE" };
4720 if (sysclkSource
>= 2) {
4721 pllSource
= SystemPLLSource();
4724 cliPrintf(" (%s%s)", SYSCLKSource
[sysclkSource
], (sysclkSource
< 2) ? "" : PLLSource
[pllSource
]);
4727 #ifdef USE_ADC_INTERNAL
4728 uint16_t vrefintMv
= getVrefMv();
4729 int16_t coretemp
= getCoreTemperatureCelsius();
4730 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv
/ 1000, (vrefintMv
% 1000) / 10, coretemp
);
4735 // Stack and config sizes and usages
4737 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4738 #ifdef USE_STACK_CHECK
4739 cliPrintf(", Stack used: %d", stackUsedSize());
4743 cliPrintLinef("Configuration: %s, size: %d, max available: %d", configurationStates
[systemConfigMutable()->configurationState
], getEEPROMConfigSize(), getEEPROMStorageSize());
4746 #if defined(USE_SPI) || defined(USE_I2C)
4747 cliPrint("Devices detected:");
4748 #if defined(USE_SPI)
4749 cliPrintf(" SPI:%d", spiGetRegisteredDeviceCount());
4750 #if defined(USE_I2C)
4754 #if defined(USE_I2C)
4755 cliPrintf(" I2C:%d", i2cGetRegisteredDeviceCount());
4761 cliPrint("Gyros detected:");
4763 for (unsigned pos
= 0; pos
< 7; pos
++) {
4764 if (gyroConfig()->gyrosDetected
& BIT(pos
)) {
4770 cliPrintf(" gyro %d", pos
+ 1);
4774 if (gyroActiveDev()->gyroModeSPI
!= GYRO_EXTI_NO_INT
) {
4775 cliPrintf(" locked");
4777 if (gyroActiveDev()->gyroModeSPI
== GYRO_EXTI_INT_DMA
) {
4780 if (spiGetExtDeviceCount(&gyroActiveDev()->dev
) > 1) {
4781 cliPrintf(" shared");
4786 #if defined(USE_SENSOR_NAMES)
4787 const uint32_t detectedSensorsMask
= sensorsMask();
4788 for (uint32_t i
= 0; ; i
++) {
4789 if (sensorTypeNames
[i
] == NULL
) {
4792 const uint32_t mask
= (1 << i
);
4793 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
4794 const uint8_t sensorHardwareIndex
= detectedSensors
[i
];
4795 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
4799 cliPrintf("%s=%s", sensorTypeNames
[i
], sensorHardware
);
4800 #if defined(USE_ACC)
4801 if (mask
== SENSOR_ACC
&& acc
.dev
.revisionCode
) {
4802 cliPrintf(".%c", acc
.dev
.revisionCode
);
4808 #endif /* USE_SENSOR_NAMES */
4810 #if defined(USE_OSD)
4811 osdDisplayPortDevice_e displayPortDeviceType
;
4812 displayPort_t
*osdDisplayPort
= osdGetDisplayPort(&displayPortDeviceType
);
4814 cliPrintLinef("OSD: %s (%u x %u)", lookupTableOsdDisplayPortDevice
[displayPortDeviceType
], osdDisplayPort
->cols
, osdDisplayPort
->rows
);
4818 cliPrintf("BUILD KEY: %s", buildKey
);
4820 cliPrintf(" (%s)", releaseName
);
4824 // Uptime and wall clock
4826 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4829 char buf
[FORMATTED_DATE_TIME_BUFSIZE
];
4831 if (rtcGetDateTime(&dt
)) {
4832 dateTimeFormatLocal(buf
, &dt
);
4833 cliPrintf(", Current Time: %s", buf
);
4840 const int gyroRate
= getTaskDeltaTimeUs(TASK_GYRO
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTimeUs(TASK_GYRO
)));
4842 int rxRate
= getRxRateValid() ? getCurrentRxRateHz() : 0;
4844 const int systemRate
= getTaskDeltaTimeUs(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTimeUs(TASK_SYSTEM
)));
4845 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4846 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE
), getTaskDeltaTimeUs(TASK_GYRO
), gyroRate
, rxRate
, systemRate
);
4850 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4852 // Other devices and status
4855 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
4857 const uint16_t i2cErrorCounter
= 0;
4859 cliPrintLinef("I2C Errors: %d", i2cErrorCounter
);
4862 cliSdInfo(cmdName
, "");
4865 #ifdef USE_FLASH_CHIP
4866 const flashGeometry_t
*layout
= flashGetGeometry();
4867 if (layout
->jedecId
!= 0) {
4868 cliPrintLinef("FLASH: JEDEC ID=0x%08x %uM", layout
->jedecId
, layout
->totalSize
>> 20);
4874 if (featureIsEnabled(FEATURE_GPS
)) {
4875 if (gpsData
.state
>= GPS_STATE_CONFIGURE
) {
4876 cliPrint("connected, ");
4878 cliPrint("NOT CONNECTED, ");
4880 if (gpsConfig()->provider
== GPS_MSP
) {
4883 const serialPortConfig_t
*gpsPortConfig
= findSerialPortConfig(FUNCTION_GPS
);
4884 if (!gpsPortConfig
) {
4885 cliPrint("NO PORT, ");
4887 cliPrintf("UART%d %ld (set to ", (gpsPortConfig
->identifier
+ 1), baudRates
[getGpsPortActualBaudRateIndex()]);
4888 if (gpsConfig()->autoBaud
== GPS_AUTOBAUD_ON
) {
4891 cliPrintf("%ld", baudRates
[gpsPortConfig
->gps_baudrateIndex
]);
4896 if (gpsData
.state
<= GPS_STATE_CONFIGURE
) {
4897 cliPrint("NOT CONFIGURED");
4899 if (gpsConfig()->autoConfig
== GPS_AUTOCONFIG_OFF
) {
4900 cliPrint("auto config OFF");
4902 cliPrint("configured");
4905 cliPrintf(", version = %s", gpsData
.platformVersion
!= UBX_VERSION_UNDEF
? ubloxVersionMap
[gpsData
.platformVersion
].str
: "unknown");
4907 cliPrint("NOT ENABLED");
4912 cliPrint("Arming disable flags:");
4913 armingDisableFlags_e flags
= getArmingDisableFlags();
4915 const armingDisableFlags_e flag
= 1 << (ffs(flags
) - 1);
4917 cliPrintf(" %s", getArmingDisableFlagName(flag
));
4922 static void cliTasks(const char *cmdName
, char *cmdline
)
4926 int averageLoadSum
= 0;
4929 if (systemConfig()->task_statistics
) {
4930 #if defined(USE_LATE_TASK_STATISTICS)
4931 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms late run reqd/us");
4933 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4936 cliPrintLine("Task list");
4939 for (taskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
4940 taskInfo_t taskInfo
;
4941 getTaskInfo(taskId
, &taskInfo
);
4942 if (taskInfo
.isEnabled
) {
4943 int taskFrequency
= taskInfo
.averageDeltaTime10thUs
== 0 ? 0 : lrintf(1e7f
/ taskInfo
.averageDeltaTime10thUs
);
4944 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
4945 const int maxLoad
= taskInfo
.maxExecutionTimeUs
== 0 ? 0 : (taskInfo
.maxExecutionTimeUs
* taskFrequency
) / 1000;
4946 const int averageLoad
= taskInfo
.averageExecutionTime10thUs
== 0 ? 0 : (taskInfo
.averageExecutionTime10thUs
* taskFrequency
) / 10000;
4947 if (taskId
!= TASK_SERIAL
) {
4948 averageLoadSum
+= averageLoad
;
4950 if (systemConfig()->task_statistics
) {
4951 #if defined(USE_LATE_TASK_STATISTICS)
4952 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d %6d %6d %7d",
4953 taskFrequency
, taskInfo
.maxExecutionTimeUs
, taskInfo
.averageExecutionTime10thUs
/ 10,
4954 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10,
4955 taskInfo
.totalExecutionTimeUs
/ 1000,
4956 taskInfo
.lateCount
, taskInfo
.runCount
, taskInfo
.execTime
);
4958 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4959 taskFrequency
, taskInfo
.maxExecutionTimeUs
, taskInfo
.averageExecutionTime10thUs
/ 10,
4960 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10,
4961 taskInfo
.totalExecutionTimeUs
/ 1000);
4964 cliPrintLinef("%6d", taskFrequency
);
4967 schedulerResetTaskMaxExecutionTime(taskId
);
4970 if (systemConfig()->task_statistics
) {
4971 cfCheckFuncInfo_t checkFuncInfo
;
4972 getCheckFuncInfo(&checkFuncInfo
);
4973 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo
.maxExecutionTimeUs
, checkFuncInfo
.averageExecutionTimeUs
, checkFuncInfo
.totalExecutionTimeUs
/ 1000);
4974 cliPrintLinef("Total (excluding SERIAL) %33d.%1d%%", averageLoadSum
/10, averageLoadSum
%10);
4975 if (debugMode
== DEBUG_SCHEDULER_DETERMINISM
) {
4976 extern int32_t schedLoopStartCycles
, taskGuardCycles
;
4978 cliPrintLinef("Scheduler start cycles %d guard cycles %d", schedLoopStartCycles
, taskGuardCycles
);
4980 schedulerResetCheckFunctionMaxExecutionTime();
4984 static void printVersion(bool printBoardInfo
)
4986 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4989 systemConfig()->boardIdentifier
,
4994 MSP_API_VERSION_STRING
4999 #if defined(__CONFIG_REVISION__)
5000 cliPrintLinef("# config rev: %s", shortConfigGitRevision
);
5003 #if defined(USE_BOARD_INFO)
5004 if (printBoardInfo
&& strlen(getManufacturerId()) && strlen(getBoardName())) {
5005 cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
5008 UNUSED(printBoardInfo
);
5012 static void cliVersion(const char *cmdName
, char *cmdline
)
5020 #ifdef USE_RC_SMOOTHING_FILTER
5021 static void cliRcSmoothing(const char *cmdName
, char *cmdline
)
5025 rcSmoothingFilter_t
*rcSmoothingData
= getRcSmoothingData();
5026 cliPrint("# RC Smoothing Type: ");
5027 if (rxConfig()->rc_smoothing_mode
) {
5028 cliPrintLine("FILTER");
5029 if (rcSmoothingAutoCalculate()) {
5030 cliPrint("# Detected Rx frequency: ");
5031 if (getRxRateValid()) {
5032 cliPrintLinef("%dHz", lrintf(rcSmoothingData
->smoothedRxRateHz
));
5034 cliPrintLine("NO SIGNAL");
5037 cliPrintf("# Active setpoint cutoff: %dhz ", rcSmoothingData
->setpointCutoffFrequency
);
5038 if (rcSmoothingData
->setpointCutoffSetting
) {
5039 cliPrintLine("(manual)");
5041 cliPrintLine("(auto)");
5043 cliPrintf("# Active FF cutoff: %dhz ", rcSmoothingData
->feedforwardCutoffFrequency
);
5044 if (rcSmoothingData
->feedforwardCutoffSetting
) {
5045 cliPrintLine("(manual)");
5047 cliPrintLine("(auto)");
5049 cliPrintf("# Active throttle cutoff: %dhz ", rcSmoothingData
->throttleCutoffFrequency
);
5050 if (rcSmoothingData
->throttleCutoffSetting
) {
5051 cliPrintLine("(manual)");
5053 cliPrintLine("(auto)");
5056 cliPrintLine("OFF");
5059 #endif // USE_RC_SMOOTHING_FILTER
5061 #if defined(USE_RESOURCE_MGMT)
5063 #define RESOURCE_VALUE_MAX_INDEX(x) ((x) == 0 ? 1 : (x))
5066 const uint8_t owner
;
5070 const uint8_t maxIndex
;
5071 } cliResourceValue_t
;
5073 // Handy macros for keeping the table tidy.
5074 // DEFS : Single entry
5075 // DEFA : Array of uint8_t (stride = 1)
5076 // DEFW : Wider stride case; array of structs.
5078 #define DEFS(owner, pgn, type, member) \
5079 { owner, pgn, 0, offsetof(type, member), 0 }
5081 #define DEFA(owner, pgn, type, member, max) \
5082 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
5084 #define DEFW(owner, pgn, type, member, max) \
5085 { owner, pgn, sizeof(type), offsetof(type, member), max }
5087 const cliResourceValue_t resourceTable
[] = {
5088 #if defined(USE_BEEPER)
5089 DEFS( OWNER_BEEPER
, PG_BEEPER_DEV_CONFIG
, beeperDevConfig_t
, ioTag
),
5091 DEFA( OWNER_MOTOR
, PG_MOTOR_CONFIG
, motorConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_MOTORS
),
5092 #if defined(USE_SERVOS)
5093 DEFA( OWNER_SERVO
, PG_SERVO_CONFIG
, servoConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_SERVOS
),
5095 #if defined(USE_RX_PPM)
5096 DEFS( OWNER_PPMINPUT
, PG_PPM_CONFIG
, ppmConfig_t
, ioTag
),
5098 #if defined(USE_RX_PWM)
5099 DEFA( OWNER_PWMINPUT
, PG_PWM_CONFIG
, pwmConfig_t
, ioTags
[0], PWM_INPUT_PORT_COUNT
),
5101 #if defined(USE_RANGEFINDER_HCSR04)
5102 DEFS( OWNER_SONAR_TRIGGER
, PG_SONAR_CONFIG
, sonarConfig_t
, triggerTag
),
5103 DEFS( OWNER_SONAR_ECHO
, PG_SONAR_CONFIG
, sonarConfig_t
, echoTag
),
5105 #if defined(USE_LED_STRIP)
5106 DEFS( OWNER_LED_STRIP
, PG_LED_STRIP_CONFIG
, ledStripConfig_t
, ioTag
),
5108 #if defined(USE_UART)
5109 DEFA( OWNER_SERIAL_TX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagTx
[RESOURCE_UART_OFFSET
], RESOURCE_UART_COUNT
),
5110 DEFA( OWNER_SERIAL_RX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagRx
[RESOURCE_UART_OFFSET
], RESOURCE_UART_COUNT
),
5112 #if defined(USE_INVERTER)
5113 DEFA( OWNER_INVERTER
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagInverter
[RESOURCE_UART_OFFSET
], RESOURCE_UART_COUNT
),
5114 // LPUART and SOFTSERIAL don't need external inversion
5116 #if defined(USE_SOFTSERIAL)
5117 DEFA( OWNER_SOFTSERIAL_TX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagTx
[RESOURCE_SOFTSERIAL_OFFSET
], RESOURCE_SOFTSERIAL_COUNT
),
5118 DEFA( OWNER_SOFTSERIAL_RX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagRx
[RESOURCE_SOFTSERIAL_OFFSET
], RESOURCE_SOFTSERIAL_COUNT
),
5120 #if defined(USE_LPUART)
5121 DEFA( OWNER_LPUART_TX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagTx
[RESOURCE_LPUART_OFFSET
], RESOURCE_LPUART_COUNT
),
5122 DEFA( OWNER_LPUART_RX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagRx
[RESOURCE_LPUART_OFFSET
], RESOURCE_LPUART_COUNT
),
5125 DEFW( OWNER_I2C_SCL
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagScl
, I2CDEV_COUNT
),
5126 DEFW( OWNER_I2C_SDA
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagSda
, I2CDEV_COUNT
),
5128 DEFA( OWNER_LED
, PG_STATUS_LED_CONFIG
, statusLedConfig_t
, ioTags
[0], STATUS_LED_NUMBER
),
5129 #ifdef USE_SPEKTRUM_BIND
5130 DEFS( OWNER_RX_BIND
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_pin_override_ioTag
),
5131 DEFS( OWNER_RX_BIND_PLUG
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_plug_ioTag
),
5133 #ifdef USE_TRANSPONDER
5134 DEFS( OWNER_TRANSPONDER
, PG_TRANSPONDER_CONFIG
, transponderConfig_t
, ioTag
),
5137 DEFW( OWNER_SPI_SCK
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagSck
, SPIDEV_COUNT
),
5138 DEFW( OWNER_SPI_SDI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMiso
, SPIDEV_COUNT
),
5139 DEFW( OWNER_SPI_SDO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMosi
, SPIDEV_COUNT
),
5141 #ifdef USE_ESCSERIAL
5142 DEFS( OWNER_ESCSERIAL
, PG_ESCSERIAL_CONFIG
, escSerialConfig_t
, ioTag
),
5144 #ifdef USE_CAMERA_CONTROL
5145 DEFS( OWNER_CAMERA_CONTROL
, PG_CAMERA_CONTROL_CONFIG
, cameraControlConfig_t
, ioTag
),
5148 DEFS( OWNER_ADC_BATT
, PG_ADC_CONFIG
, adcConfig_t
, vbat
.ioTag
),
5149 DEFS( OWNER_ADC_RSSI
, PG_ADC_CONFIG
, adcConfig_t
, rssi
.ioTag
),
5150 DEFS( OWNER_ADC_CURR
, PG_ADC_CONFIG
, adcConfig_t
, current
.ioTag
),
5151 DEFS( OWNER_ADC_EXT
, PG_ADC_CONFIG
, adcConfig_t
, external1
.ioTag
),
5154 DEFS( OWNER_BARO_CS
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_spi_csn
),
5155 DEFS( OWNER_BARO_EOC
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_eoc_tag
),
5156 DEFS( OWNER_BARO_XCLR
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_xclr_tag
),
5159 DEFS( OWNER_COMPASS_CS
, PG_COMPASS_CONFIG
, compassConfig_t
, mag_spi_csn
),
5160 #ifdef USE_MAG_DATA_READY_SIGNAL
5161 DEFS( OWNER_COMPASS_EXTI
, PG_COMPASS_CONFIG
, compassConfig_t
, interruptTag
),
5164 #ifdef USE_SDCARD_SPI
5165 DEFS( OWNER_SDCARD_CS
, PG_SDCARD_CONFIG
, sdcardConfig_t
, chipSelectTag
),
5168 DEFS( OWNER_SDCARD_DETECT
, PG_SDCARD_CONFIG
, sdcardConfig_t
, cardDetectTag
),
5170 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
5171 DEFS( OWNER_SDIO_CK
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, CKPin
),
5172 DEFS( OWNER_SDIO_CMD
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, CMDPin
),
5173 DEFS( OWNER_SDIO_D0
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D0Pin
),
5174 DEFS( OWNER_SDIO_D1
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D1Pin
),
5175 DEFS( OWNER_SDIO_D2
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D2Pin
),
5176 DEFS( OWNER_SDIO_D3
, PG_SDIO_PIN_CONFIG
, sdioPinConfig_t
, D3Pin
),
5179 DEFA( OWNER_PINIO
, PG_PINIO_CONFIG
, pinioConfig_t
, ioTag
, PINIO_COUNT
),
5181 #if defined(USE_USB_MSC)
5182 DEFS( OWNER_USB_MSC_PIN
, PG_USB_CONFIG
, usbDev_t
, mscButtonPin
),
5184 #ifdef USE_FLASH_CHIP
5185 DEFS( OWNER_FLASH_CS
, PG_FLASH_CONFIG
, flashConfig_t
, csTag
),
5188 DEFS( OWNER_OSD_CS
, PG_MAX7456_CONFIG
, max7456Config_t
, csTag
),
5191 DEFS( OWNER_RX_SPI_CS
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, csnTag
),
5192 DEFS( OWNER_RX_SPI_EXTI
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, extiIoTag
),
5193 DEFS( OWNER_RX_SPI_BIND
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, bindIoTag
),
5194 DEFS( OWNER_RX_SPI_LED
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, ledIoTag
),
5195 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
5196 DEFS( OWNER_RX_SPI_CC2500_TX_EN
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, txEnIoTag
),
5197 DEFS( OWNER_RX_SPI_CC2500_LNA_EN
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, lnaEnIoTag
),
5198 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
5199 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, antSelIoTag
),
5202 #if defined(USE_RX_EXPRESSLRS)
5203 DEFS( OWNER_RX_SPI_EXPRESSLRS_RESET
, PG_RX_EXPRESSLRS_SPI_CONFIG
, rxExpressLrsSpiConfig_t
, resetIoTag
),
5204 DEFS( OWNER_RX_SPI_EXPRESSLRS_BUSY
, PG_RX_EXPRESSLRS_SPI_CONFIG
, rxExpressLrsSpiConfig_t
, busyIoTag
),
5207 DEFW( OWNER_GYRO_EXTI
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, extiTag
, MAX_GYRODEV_COUNT
),
5208 DEFW( OWNER_GYRO_CS
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, csnTag
, MAX_GYRODEV_COUNT
),
5209 #if defined(USE_GYRO_CLKIN)
5210 DEFW( OWNER_GYRO_CLKIN
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, clkIn
, MAX_GYRODEV_COUNT
),
5212 #ifdef USE_USB_DETECT
5213 DEFS( OWNER_USB_DETECT
, PG_USB_CONFIG
, usbDev_t
, detectPin
),
5215 #ifdef USE_VTX_RTC6705
5216 DEFS( OWNER_VTX_POWER
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, powerTag
),
5217 DEFS( OWNER_VTX_CS
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, csTag
),
5218 DEFS( OWNER_VTX_DATA
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, dataTag
),
5219 DEFS( OWNER_VTX_CLK
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, clockTag
),
5221 #ifdef USE_PIN_PULL_UP_DOWN
5222 DEFA( OWNER_PULLUP
, PG_PULLUP_CONFIG
, pinPullUpDownConfig_t
, ioTag
, PIN_PULL_UP_DOWN_COUNT
),
5223 DEFA( OWNER_PULLDOWN
, PG_PULLDOWN_CONFIG
, pinPullUpDownConfig_t
, ioTag
, PIN_PULL_UP_DOWN_COUNT
),
5231 static ioTag_t
* getIoTag(const cliResourceValue_t value
, uint8_t index
)
5233 const pgRegistry_t
* rec
= pgFind(value
.pgn
);
5234 return (ioTag_t
*)(rec
->address
+ value
.stride
* index
+ value
.offset
);
5237 static void printResource(dumpFlags_t dumpMask
, const char *headingStr
)
5239 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5240 for (unsigned int i
= 0; i
< ARRAYLEN(resourceTable
); i
++) {
5241 const char* owner
= ownerNames
[resourceTable
[i
].owner
];
5242 const pgRegistry_t
* pg
= pgFind(resourceTable
[i
].pgn
);
5243 const void *currentConfig
;
5244 const void *defaultConfig
;
5245 if (isReadingConfigFromCopy()) {
5246 currentConfig
= pg
->copy
;
5247 defaultConfig
= pg
->address
;
5249 currentConfig
= pg
->address
;
5250 defaultConfig
= NULL
;
5253 for (int index
= 0; index
< RESOURCE_VALUE_MAX_INDEX(resourceTable
[i
].maxIndex
); index
++) {
5254 const ioTag_t ioTag
= *(ioTag_t
*)((const uint8_t *)currentConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
5255 ioTag_t ioTagDefault
= 0;
5256 if (defaultConfig
) {
5257 ioTagDefault
= *(ioTag_t
*)((const uint8_t *)defaultConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
5260 const bool equalsDefault
= ioTag
== ioTagDefault
;
5261 const char *format
= "resource %s %d %c%02d";
5262 const char *formatUnassigned
= "resource %s %d NONE";
5263 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5265 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTagDefault
) + 'A', IO_GPIOPinIdxByTag(ioTagDefault
));
5266 } else if (defaultConfig
) {
5267 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
5270 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
));
5271 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5272 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
5278 static void printResourceOwner(uint8_t owner
, uint8_t index
)
5280 cliPrintf("%s", ownerNames
[resourceTable
[owner
].owner
]);
5282 if (resourceTable
[owner
].maxIndex
> 0) {
5283 cliPrintf(" %d", RESOURCE_INDEX(index
));
5287 static void resourceCheck(uint8_t resourceIndex
, uint8_t index
, ioTag_t newTag
)
5293 const char * format
= "\r\nNOTE: %c%02d already assigned to ";
5294 for (int r
= 0; r
< (int)ARRAYLEN(resourceTable
); r
++) {
5295 for (int i
= 0; i
< RESOURCE_VALUE_MAX_INDEX(resourceTable
[r
].maxIndex
); i
++) {
5296 ioTag_t
*tag
= getIoTag(resourceTable
[r
], i
);
5297 if (*tag
== newTag
) {
5298 bool cleared
= false;
5299 if (r
== resourceIndex
) {
5307 cliPrintf(format
, DEFIO_TAG_GPIOID(newTag
) + 'A', DEFIO_TAG_PIN(newTag
));
5309 printResourceOwner(r
, i
);
5313 printResourceOwner(r
, i
);
5314 cliPrintf(" disabled");
5323 static bool strToPin(char *ptr
, ioTag_t
*tag
)
5325 if (strcasecmp(ptr
, "NONE") == 0) {
5330 const unsigned port
= (*ptr
>= 'a') ? *ptr
- 'a' : *ptr
- 'A';
5335 const long pin
= strtol(ptr
, &end
, 10);
5336 if (end
!= ptr
&& pin
>= 0 && pin
< 16) {
5337 *tag
= DEFIO_TAG_MAKE(port
, pin
);
5348 static void showDma(void)
5353 cliPrintLine("DMA:");
5355 cliPrintLine("Currently active DMA:");
5358 for (int i
= 1; i
<= DMA_LAST_HANDLER
; i
++) {
5359 const resourceOwner_t
*owner
= dmaGetOwner(i
);
5361 cliPrintf(DMA_OUTPUT_STRING
, DMA_DEVICE_NO(i
), DMA_DEVICE_INDEX(i
));
5362 if (owner
->resourceIndex
> 0) {
5363 cliPrintLinef(" %s %d", ownerNames
[owner
->owner
], owner
->resourceIndex
);
5365 cliPrintLinef(" %s", ownerNames
[owner
->owner
]);
5373 typedef struct dmaoptEntry_s
{
5375 dmaPeripheral_e peripheral
;
5380 uint32_t presenceMask
;
5383 #define MASK_IGNORED (0)
5385 // Handy macros for keeping the table tidy.
5386 // DEFS : Single entry
5387 // DEFA : Array of uint8_t (stride = 1)
5388 // DEFW : Wider stride case; array of structs.
5389 // DEFW_OFS: array of structs, starting at offset ofs
5391 #define DEFS(device, peripheral, pgn, type, member) \
5392 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5394 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5395 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5397 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5398 DEFW_OFS(device, peripheral, pgn, type, member, 0, max, mask)
5400 #define DEFW_OFS(device, peripheral, pgn, type, member, ofs, max, mask) \
5401 { device, peripheral, pgn, sizeof(type), offsetof(type, member) + (ofs) * sizeof(type), max, mask }
5403 dmaoptEntry_t dmaoptEntryTable
[] = {
5404 DEFW("SPI_SDO", DMA_PERIPH_SPI_SDO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, txDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5405 DEFW("SPI_SDI", DMA_PERIPH_SPI_SDI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, rxDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5406 // SPI_TX/SPI_RX for backwards compatibility with unified configs defined for 4.2.x
5407 DEFW("SPI_TX", DMA_PERIPH_SPI_SDO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, txDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5408 DEFW("SPI_RX", DMA_PERIPH_SPI_SDI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, rxDmaopt
, SPIDEV_COUNT
, MASK_IGNORED
),
5409 DEFA("ADC", DMA_PERIPH_ADC
, PG_ADC_CONFIG
, adcConfig_t
, dmaopt
, ADCDEV_COUNT
, MASK_IGNORED
),
5410 DEFS("SDIO", DMA_PERIPH_SDIO
, PG_SDIO_CONFIG
, sdioConfig_t
, dmaopt
),
5412 DEFW_OFS("UART_TX", DMA_PERIPH_UART_TX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, txDmaopt
, RESOURCE_UART_OFFSET
, RESOURCE_UART_COUNT
, MASK_IGNORED
),
5413 DEFW_OFS("UART_RX", DMA_PERIPH_UART_RX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, rxDmaopt
, RESOURCE_UART_OFFSET
, RESOURCE_UART_COUNT
, MASK_IGNORED
),
5416 DEFW_OFS("LPUART_TX", DMA_PERIPH_UART_TX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, txDmaopt
, RESOURCE_LPUART_OFFSET
, RESOURCE_LPUART_COUNT
, MASK_IGNORED
),
5417 DEFW_OFS("LPUART_RX", DMA_PERIPH_UART_RX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, rxDmaopt
, RESOURCE_LPUART_OFFSET
, RESOURCE_LPUART_COUNT
, MASK_IGNORED
),
5419 #if defined(STM32H7) || defined(STM32G4)
5420 DEFW("TIMUP", DMA_PERIPH_TIMUP
, PG_TIMER_UP_CONFIG
, timerUpConfig_t
, dmaopt
, HARDWARE_TIMER_DEFINITION_COUNT
, TIMUP_TIMERS
),
5428 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5429 #define DMA_OPT_STRING_BUFSIZE 5
5431 #if defined(STM32H7) || defined(STM32G4) || defined(AT32F435)
5432 #define DMA_CHANREQ_STRING "Request"
5434 #define DMA_CHANREQ_STRING "Channel"
5437 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(APM32F4)
5438 #define DMA_STCH_STRING "Stream"
5440 #define DMA_STCH_STRING "Channel"
5443 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5445 static void optToString(int optval
, char *buf
)
5447 if (optval
== DMA_OPT_UNUSED
) {
5448 memcpy(buf
, "NONE", DMA_OPT_STRING_BUFSIZE
);
5450 tfp_sprintf(buf
, "%d", optval
);
5454 static void printPeripheralDmaoptDetails(dmaoptEntry_t
*entry
, int index
, const dmaoptValue_t dmaopt
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5456 // We compute number to display for different peripherals in advance.
5457 // This is done to deal with TIMUP which numbered non-contiguously.
5458 // Note that using timerGetNumberByIndex is not a generic solution,
5459 // but we are lucky that TIMUP is the only peripheral with non-contiguous numbering.
5463 if (entry
->presenceMask
) {
5464 uiIndex
= timerGetNumberByIndex(index
);
5466 uiIndex
= DMA_OPT_UI_INDEX(index
);
5469 if (dmaopt
!= DMA_OPT_UNUSED
) {
5470 printValue(dumpMask
, equalsDefault
,
5472 entry
->device
, uiIndex
, dmaopt
);
5474 const dmaChannelSpec_t
*dmaChannelSpec
= dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, dmaopt
);
5475 dmaCode_t dmaCode
= 0;
5476 if (dmaChannelSpec
) {
5477 dmaCode
= dmaChannelSpec
->code
;
5479 printValue(dumpMask
, equalsDefault
,
5480 "# %s %d: " DMASPEC_FORMAT_STRING
,
5481 entry
->device
, uiIndex
, DMA_CODE_CONTROLLER(dmaCode
), DMA_CODE_STREAM(dmaCode
), DMA_CODE_CHANNEL(dmaCode
));
5482 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5483 printValue(dumpMask
, equalsDefault
,
5485 entry
->device
, uiIndex
);
5489 static const char *printPeripheralDmaopt(dmaoptEntry_t
*entry
, int index
, dumpFlags_t dumpMask
, const char *headingStr
)
5491 const pgRegistry_t
* pg
= pgFind(entry
->pgn
);
5492 const void *currentConfig
;
5493 const void *defaultConfig
;
5495 if (isReadingConfigFromCopy()) {
5496 currentConfig
= pg
->copy
;
5497 defaultConfig
= pg
->address
;
5499 currentConfig
= pg
->address
;
5500 defaultConfig
= NULL
;
5503 dmaoptValue_t currentOpt
= *(dmaoptValue_t
*)((uint8_t *)currentConfig
+ entry
->stride
* index
+ entry
->offset
);
5504 dmaoptValue_t defaultOpt
;
5506 if (defaultConfig
) {
5507 defaultOpt
= *(dmaoptValue_t
*)((uint8_t *)defaultConfig
+ entry
->stride
* index
+ entry
->offset
);
5509 defaultOpt
= DMA_OPT_UNUSED
;
5512 bool equalsDefault
= currentOpt
== defaultOpt
;
5513 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5515 if (defaultConfig
) {
5516 printPeripheralDmaoptDetails(entry
, index
, defaultOpt
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5519 printPeripheralDmaoptDetails(entry
, index
, currentOpt
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5523 #if defined(USE_TIMER_MGMT)
5524 static void printTimerDmaoptDetails(const ioTag_t ioTag
, const timerHardware_t
*timer
, const dmaoptValue_t dmaopt
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5526 const char *format
= "dma pin %c%02d %d";
5528 if (dmaopt
!= DMA_OPT_UNUSED
) {
5529 const bool printDetails
= printValue(dumpMask
, equalsDefault
, format
,
5530 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5535 const dmaChannelSpec_t
*dmaChannelSpec
= dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, dmaopt
);
5536 if (dmaChannelSpec
) {
5537 dmaCode_t dmaCode
= dmaChannelSpec
->code
;
5538 printValue(dumpMask
, false,
5539 "# pin %c%02d: " DMASPEC_FORMAT_STRING
,
5540 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5541 DMA_CODE_CONTROLLER(dmaCode
), DMA_CODE_STREAM(dmaCode
), DMA_CODE_CHANNEL(dmaCode
)
5545 } else if (!(dumpMask
& HIDE_UNUSED
)) {
5546 printValue(dumpMask
, equalsDefault
,
5547 "dma pin %c%02d NONE",
5548 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
)
5553 static const char *printTimerDmaopt(const timerIOConfig_t
*currentConfig
, const timerIOConfig_t
*defaultConfig
, unsigned index
, dumpFlags_t dumpMask
, bool tagsInUse
[], const char *headingStr
)
5555 const ioTag_t ioTag
= currentConfig
[index
].ioTag
;
5561 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, currentConfig
[index
].index
);
5562 const dmaoptValue_t dmaopt
= currentConfig
[index
].dmaopt
;
5564 dmaoptValue_t defaultDmaopt
= DMA_OPT_UNUSED
;
5565 bool equalsDefault
= defaultDmaopt
== dmaopt
;
5566 if (defaultConfig
) {
5567 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5568 if (defaultConfig
[i
].ioTag
== ioTag
) {
5569 defaultDmaopt
= defaultConfig
[i
].dmaopt
;
5571 // 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.
5572 equalsDefault
= (defaultDmaopt
== dmaopt
) && (defaultConfig
[i
].index
== currentConfig
[index
].index
|| dmaopt
== DMA_OPT_UNUSED
);
5574 tagsInUse
[index
] = true;
5581 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5583 if (defaultConfig
) {
5584 printTimerDmaoptDetails(ioTag
, timer
, defaultDmaopt
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5587 printTimerDmaoptDetails(ioTag
, timer
, dmaopt
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5592 static void printDmaopt(dumpFlags_t dumpMask
, const char *headingStr
)
5594 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5595 for (size_t i
= 0; i
< ARRAYLEN(dmaoptEntryTable
); i
++) {
5596 dmaoptEntry_t
*entry
= &dmaoptEntryTable
[i
];
5597 for (int index
= 0; index
< entry
->maxIndex
; index
++) {
5598 headingStr
= printPeripheralDmaopt(entry
, index
, dumpMask
, headingStr
);
5602 #if defined(USE_TIMER_MGMT)
5603 const pgRegistry_t
* pg
= pgFind(PG_TIMER_IO_CONFIG
);
5604 const timerIOConfig_t
*currentConfig
;
5605 const timerIOConfig_t
*defaultConfig
;
5607 if (isReadingConfigFromCopy()) {
5608 currentConfig
= (timerIOConfig_t
*)pg
->copy
;
5609 defaultConfig
= (timerIOConfig_t
*)pg
->address
;
5611 currentConfig
= (timerIOConfig_t
*)pg
->address
;
5612 defaultConfig
= NULL
;
5615 bool tagsInUse
[MAX_TIMER_PINMAP_COUNT
] = { false };
5616 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5617 headingStr
= printTimerDmaopt(currentConfig
, defaultConfig
, i
, dumpMask
, tagsInUse
, headingStr
);
5620 if (defaultConfig
) {
5621 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5622 if (!tagsInUse
[i
] && defaultConfig
[i
].ioTag
&& defaultConfig
[i
].dmaopt
!= DMA_OPT_UNUSED
) {
5623 const timerHardware_t
*timer
= timerGetByTagAndIndex(defaultConfig
[i
].ioTag
, defaultConfig
[i
].index
);
5624 headingStr
= cliPrintSectionHeading(dumpMask
, true, headingStr
);
5625 printTimerDmaoptDetails(defaultConfig
[i
].ioTag
, timer
, defaultConfig
[i
].dmaopt
, false, dumpMask
, cliDefaultPrintLinef
);
5627 printTimerDmaoptDetails(defaultConfig
[i
].ioTag
, timer
, DMA_OPT_UNUSED
, false, dumpMask
, cliDumpPrintLinef
);
5634 static void cliDmaopt(const char *cmdName
, char *cmdline
)
5639 // Peripheral name or command option
5640 pch
= strtok_r(cmdline
, " ", &saveptr
);
5642 printDmaopt(DUMP_MASTER
| HIDE_UNUSED
, NULL
);
5645 } else if (strcasecmp(pch
, "list") == 0) {
5646 cliPrintErrorLinef(cmdName
, "NOT IMPLEMENTED YET");
5651 dmaoptEntry_t
*entry
= NULL
;
5652 for (unsigned i
= 0; i
< ARRAYLEN(dmaoptEntryTable
); i
++) {
5653 if (strcasecmp(pch
, dmaoptEntryTable
[i
].device
) == 0) {
5654 entry
= &dmaoptEntryTable
[i
];
5658 if (!entry
&& strcasecmp(pch
, "pin") != 0) {
5659 cliPrintErrorLinef(cmdName
, "BAD DEVICE: %s", pch
);
5664 dmaoptValue_t orgval
= DMA_OPT_UNUSED
;
5667 dmaoptValue_t
*optaddr
= NULL
;
5669 ioTag_t ioTag
= IO_TAG_NONE
;
5670 #if defined(USE_TIMER_MGMT)
5671 timerIOConfig_t
*timerIoConfig
= NULL
;
5673 const timerHardware_t
*timer
= NULL
;
5674 pch
= strtok_r(NULL
, " ", &saveptr
);
5676 index
= pch
? (atoi(pch
) - 1) : -1;
5677 if (index
< 0 || index
>= entry
->maxIndex
|| (entry
->presenceMask
!= MASK_IGNORED
&& !(entry
->presenceMask
& BIT(index
+ 1)))) {
5678 cliPrintErrorLinef(cmdName
, "BAD INDEX: '%s'", pch
? pch
: "");
5682 const pgRegistry_t
* pg
= pgFind(entry
->pgn
);
5683 const void *currentConfig
;
5684 if (isWritingConfigToCopy()) {
5685 currentConfig
= pg
->copy
;
5687 currentConfig
= pg
->address
;
5689 optaddr
= (dmaoptValue_t
*)((uint8_t *)currentConfig
+ entry
->stride
* index
+ entry
->offset
);
5693 if (!pch
|| !(strToPin(pch
, &ioTag
) && IOGetByTag(ioTag
))) {
5694 cliPrintErrorLinef(cmdName
, "INVALID PIN: '%s'", pch
? pch
: "");
5699 orgval
= dmaoptByTag(ioTag
);
5700 #if defined(USE_TIMER_MGMT)
5701 timerIoConfig
= timerIoConfigByTag(ioTag
);
5703 timer
= timerGetConfiguredByTag(ioTag
);
5707 pch
= strtok_r(NULL
, " ", &saveptr
);
5710 printPeripheralDmaoptDetails(entry
, index
, *optaddr
, true, DUMP_MASTER
, cliDumpPrintLinef
);
5712 #if defined(USE_TIMER_MGMT)
5714 printTimerDmaoptDetails(ioTag
, timer
, orgval
, true, DUMP_MASTER
, cliDumpPrintLinef
);
5719 } else if (strcasecmp(pch
, "list") == 0) {
5720 // Show possible opts
5721 const dmaChannelSpec_t
*dmaChannelSpec
;
5723 for (int opt
= 0; (dmaChannelSpec
= dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, opt
)); opt
++) {
5724 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING
, opt
, DMA_CODE_CONTROLLER(dmaChannelSpec
->code
), DMA_CODE_STREAM(dmaChannelSpec
->code
), DMA_CODE_CHANNEL(dmaChannelSpec
->code
));
5727 for (int opt
= 0; (dmaChannelSpec
= dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, opt
)); opt
++) {
5728 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING
, opt
, DMA_CODE_CONTROLLER(dmaChannelSpec
->code
), DMA_CODE_STREAM(dmaChannelSpec
->code
), DMA_CODE_CHANNEL(dmaChannelSpec
->code
));
5735 if (strcasecmp(pch
, "none") == 0) {
5736 optval
= DMA_OPT_UNUSED
;
5741 if (!dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, optval
)) {
5742 cliPrintErrorLinef(cmdName
, "INVALID DMA OPTION FOR %s %d: '%s'", entry
->device
, DMA_OPT_UI_INDEX(index
), pch
);
5747 if (!dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, optval
)) {
5748 cliPrintErrorLinef(cmdName
, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
5755 char optvalString
[DMA_OPT_STRING_BUFSIZE
];
5756 optToString(optval
, optvalString
);
5758 char orgvalString
[DMA_OPT_STRING_BUFSIZE
];
5759 optToString(orgval
, orgvalString
);
5761 if (optval
!= orgval
) {
5765 cliPrintLinef("# dma %s %d: changed from %s to %s", entry
->device
, DMA_OPT_UI_INDEX(index
), orgvalString
, optvalString
);
5767 #if defined(USE_TIMER_MGMT)
5768 timerIoConfig
->dmaopt
= optval
;
5771 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
, optvalString
);
5775 cliPrintLinef("# dma %s %d: no change: %s", entry
->device
, DMA_OPT_UI_INDEX(index
), orgvalString
);
5777 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),orgvalString
);
5782 #endif // USE_DMA_SPEC
5785 static void cliDma(const char *cmdName
, char* cmdline
)
5787 int len
= strlen(cmdline
);
5788 if (len
&& strncasecmp(cmdline
, "show", len
) == 0) {
5794 #if defined(USE_DMA_SPEC)
5795 cliDmaopt(cmdName
, cmdline
);
5797 cliShowParseError(cmdName
);
5801 #endif // USE_RESOURCE_MGMT
5803 #ifdef USE_TIMER_MGMT
5804 static void printTimerDetails(const ioTag_t ioTag
, const unsigned timerIndex
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5806 const char *format
= "timer %c%02d AF%d";
5807 const char *emptyFormat
= "timer %c%02d NONE";
5809 if (timerIndex
> 0) {
5810 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, timerIndex
);
5811 const bool printDetails
= printValue(dumpMask
, equalsDefault
, format
,
5812 IO_GPIOPortIdxByTag(ioTag
) + 'A',
5813 IO_GPIOPinIdxByTag(ioTag
),
5814 timer
->alternateFunction
5817 printValue(dumpMask
, false,
5818 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5819 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5820 timerGetTIMNumber(timer
->tim
),
5821 CC_INDEX_FROM_CHANNEL(timer
->channel
) + 1,
5822 timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : "",
5823 timer
->alternateFunction
5827 printValue(dumpMask
, equalsDefault
, emptyFormat
,
5828 IO_GPIOPortIdxByTag(ioTag
) + 'A',
5829 IO_GPIOPinIdxByTag(ioTag
)
5834 static void printTimer(dumpFlags_t dumpMask
, const char *headingStr
)
5836 const pgRegistry_t
* pg
= pgFind(PG_TIMER_IO_CONFIG
);
5837 const timerIOConfig_t
*currentConfig
;
5838 const timerIOConfig_t
*defaultConfig
;
5840 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5841 if (isReadingConfigFromCopy()) {
5842 currentConfig
= (timerIOConfig_t
*)pg
->copy
;
5843 defaultConfig
= (timerIOConfig_t
*)pg
->address
;
5845 currentConfig
= (timerIOConfig_t
*)pg
->address
;
5846 defaultConfig
= NULL
;
5849 bool tagsInUse
[MAX_TIMER_PINMAP_COUNT
] = { false };
5850 for (unsigned int i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5851 const ioTag_t ioTag
= currentConfig
[i
].ioTag
;
5857 const uint8_t timerIndex
= currentConfig
[i
].index
;
5859 uint8_t defaultTimerIndex
= 0;
5860 if (defaultConfig
) {
5861 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5862 if (defaultConfig
[i
].ioTag
== ioTag
) {
5863 defaultTimerIndex
= defaultConfig
[i
].index
;
5864 tagsInUse
[i
] = true;
5871 const bool equalsDefault
= defaultTimerIndex
== timerIndex
;
5872 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5873 if (defaultConfig
&& defaultTimerIndex
) {
5874 printTimerDetails(ioTag
, defaultTimerIndex
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5877 printTimerDetails(ioTag
, timerIndex
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5880 if (defaultConfig
) {
5881 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5882 if (!tagsInUse
[i
] && defaultConfig
[i
].ioTag
) {
5883 headingStr
= cliPrintSectionHeading(DO_DIFF
, true, headingStr
);
5884 printTimerDetails(defaultConfig
[i
].ioTag
, defaultConfig
[i
].index
, false, dumpMask
, cliDefaultPrintLinef
);
5886 printTimerDetails(defaultConfig
[i
].ioTag
, 0, false, dumpMask
, cliDumpPrintLinef
);
5892 #define TIMER_INDEX_UNDEFINED -1
5893 #define TIMER_AF_STRING_BUFSIZE 5
5895 static void alternateFunctionToString(const ioTag_t ioTag
, const int index
, char *buf
)
5897 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, index
+ 1);
5899 memcpy(buf
, "NONE", TIMER_AF_STRING_BUFSIZE
);
5901 tfp_sprintf(buf
, "AF%d", timer
->alternateFunction
);
5905 #ifdef USE_TIMER_MAP_PRINT
5906 static void showTimerMap(void)
5909 cliPrintLine("Timer Mapping:");
5910 for (unsigned int i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5911 const ioTag_t ioTag
= timerIOConfig(i
)->ioTag
;
5917 cliPrintLinef(" TIMER_PIN_MAP(%d, P%c%d, %d, %d)",
5919 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5920 timerIOConfig(i
)->index
,
5921 timerIOConfig(i
)->dmaopt
5927 static void showTimers(void)
5932 cliPrintLine("Timers:");
5934 cliPrintLine("Currently active Timers:");
5939 for (int i
= 0; (timerNumber
= timerGetNumberByIndex(i
)); i
++) {
5940 cliPrintf("TIM%d:", timerNumber
);
5941 bool timerUsed
= false;
5942 for (unsigned timerIndex
= 0; timerIndex
< CC_CHANNELS_PER_TIMER
; timerIndex
++) {
5943 const timerHardware_t
*timer
= timerGetAllocatedByNumberAndChannel(timerNumber
, CC_CHANNEL_FROM_INDEX(timerIndex
));
5944 const resourceOwner_t
*timerOwner
= timerGetOwner(timer
);
5945 if (timerOwner
->owner
) {
5952 if (timerOwner
->resourceIndex
> 0) {
5953 cliPrintLinef(" CH%d%s: %s %d", timerIndex
+ 1, timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : " ", ownerNames
[timerOwner
->owner
], timerOwner
->resourceIndex
);
5955 cliPrintLinef(" CH%d%s: %s", timerIndex
+ 1, timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : " ", ownerNames
[timerOwner
->owner
]);
5961 cliPrintLine(" FREE");
5966 static void cliTimer(const char *cmdName
, char *cmdline
)
5968 int len
= strlen(cmdline
);
5971 printTimer(DUMP_MASTER
, NULL
);
5974 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
5975 cliPrintErrorLinef(cmdName
, "NOT IMPLEMENTED YET");
5978 #ifdef USE_TIMER_MAP_PRINT
5979 } else if (strncasecmp(cmdline
, "map", len
) == 0) {
5984 } else if (strncasecmp(cmdline
, "show", len
) == 0) {
5993 ioTag_t ioTag
= IO_TAG_NONE
;
5994 pch
= strtok_r(cmdline
, " ", &saveptr
);
5995 if (!pch
|| !strToPin(pch
, &ioTag
)) {
5996 cliShowParseError(cmdName
);
5999 } else if (!IOGetByTag(ioTag
)) {
6000 cliPrintErrorLinef(cmdName
, "PIN NOT USED ON BOARD.");
6005 int timerIOIndex
= TIMER_INDEX_UNDEFINED
;
6006 bool isExistingTimerOpt
= false;
6007 /* find existing entry, or go for next available */
6008 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
6009 if (timerIOConfig(i
)->ioTag
== ioTag
) {
6011 isExistingTimerOpt
= true;
6016 /* first available empty slot */
6017 if (timerIOIndex
< 0 && timerIOConfig(i
)->ioTag
== IO_TAG_NONE
) {
6022 if (timerIOIndex
< 0) {
6023 cliPrintErrorLinef(cmdName
, "PIN TIMER MAP FULL.");
6028 pch
= strtok_r(NULL
, " ", &saveptr
);
6030 int timerIndex
= TIMER_INDEX_UNDEFINED
;
6031 if (strcasecmp(pch
, "list") == 0) {
6032 /* output the list of available options */
6033 const timerHardware_t
*timer
;
6034 for (unsigned index
= 0; (timer
= timerGetByTagAndIndex(ioTag
, index
+ 1)); index
++) {
6035 cliPrintLinef("# AF%d: TIM%d CH%d%s",
6036 timer
->alternateFunction
,
6037 timerGetTIMNumber(timer
->tim
),
6038 CC_INDEX_FROM_CHANNEL(timer
->channel
) + 1,
6039 timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : ""
6044 } else if (strncasecmp(pch
, "af", 2) == 0) {
6045 unsigned alternateFunction
= atoi(&pch
[2]);
6047 const timerHardware_t
*timer
;
6048 for (unsigned index
= 0; (timer
= timerGetByTagAndIndex(ioTag
, index
+ 1)); index
++) {
6049 if (timer
->alternateFunction
== alternateFunction
) {
6057 cliPrintErrorLinef(cmdName
, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
6061 } else if (strcasecmp(pch
, "none") != 0) {
6062 cliPrintErrorLinef(cmdName
, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
6067 int oldTimerIndex
= isExistingTimerOpt
? timerIOConfig(timerIOIndex
)->index
- 1 : -1;
6068 timerIOConfigMutable(timerIOIndex
)->ioTag
= timerIndex
== TIMER_INDEX_UNDEFINED
? IO_TAG_NONE
: ioTag
;
6069 timerIOConfigMutable(timerIOIndex
)->index
= timerIndex
+ 1;
6070 timerIOConfigMutable(timerIOIndex
)->dmaopt
= DMA_OPT_UNUSED
;
6072 char optvalString
[DMA_OPT_STRING_BUFSIZE
];
6073 alternateFunctionToString(ioTag
, timerIndex
, optvalString
);
6075 char orgvalString
[DMA_OPT_STRING_BUFSIZE
];
6076 alternateFunctionToString(ioTag
, oldTimerIndex
, orgvalString
);
6078 if (timerIndex
== oldTimerIndex
) {
6079 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
);
6081 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
, optvalString
);
6086 printTimerDetails(ioTag
, timerIOConfig(timerIOIndex
)->index
, false, DUMP_MASTER
, cliDumpPrintLinef
);
6093 #if defined(USE_RESOURCE_MGMT)
6094 static void cliResource(const char *cmdName
, char *cmdline
)
6099 pch
= strtok_r(cmdline
, " ", &saveptr
);
6101 printResource(DUMP_MASTER
| HIDE_UNUSED
, NULL
);
6104 } else if (strcasecmp(pch
, "show") == 0) {
6108 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
6111 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
6113 owner
= ownerNames
[ioRecs
[i
].owner
];
6115 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
);
6116 if (ioRecs
[i
].index
> 0) {
6117 cliPrintf(" %d", ioRecs
[i
].index
);
6122 pch
= strtok_r(NULL
, " ", &saveptr
);
6123 if (strcasecmp(pch
, "all") == 0) {
6124 #if defined(USE_TIMER_MGMT)
6125 cliTimer(cmdName
, "show");
6127 #if defined(USE_DMA)
6128 cliDma(cmdName
, "show");
6135 unsigned resourceIndex
= 0;
6136 for (; ; resourceIndex
++) {
6137 if (resourceIndex
>= ARRAYLEN(resourceTable
)) {
6138 cliPrintErrorLinef(cmdName
, "INVALID RESOURCE NAME: '%s'", pch
);
6142 const char *resourceName
= ownerNames
[resourceTable
[resourceIndex
].owner
];
6143 if (strcasecmp(pch
, resourceName
) == 0) {
6148 pch
= strtok_r(NULL
, " ", &saveptr
);
6149 int index
= pch
? atoi(pch
) : 0;
6151 if (resourceTable
[resourceIndex
].maxIndex
> 0 || index
> 0) {
6152 if (index
<= 0 || index
> RESOURCE_VALUE_MAX_INDEX(resourceTable
[resourceIndex
].maxIndex
)) {
6153 cliShowArgumentRangeError(cmdName
, "INDEX", 1, RESOURCE_VALUE_MAX_INDEX(resourceTable
[resourceIndex
].maxIndex
));
6158 pch
= strtok_r(NULL
, " ", &saveptr
);
6161 ioTag_t
*resourceTag
= getIoTag(resourceTable
[resourceIndex
], index
);
6163 if (pch
&& strlen(pch
) > 0) {
6165 if (strToPin(pch
, &tag
)) {
6169 cliPrintLine("Freed");
6171 cliPrintLine("Resource is freed");
6175 ioRec_t
*rec
= IO_Rec(IOGetByTag(tag
));
6178 resourceCheck(resourceIndex
, index
, tag
);
6180 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
6182 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
6185 cliShowParseError(cmdName
);
6189 cliPrintErrorLinef(cmdName
, "Failed to parse '%s' as pin", pch
);
6192 ioTag_t tag
= *resourceTag
;
6195 tfp_sprintf(ioName
, "%c%02d", IO_GPIOPortIdxByTag(tag
) + 'A', IO_GPIOPinIdxByTag(tag
));
6197 cliPrintLinef("# resource %s %d %s", ownerNames
[resourceTable
[resourceIndex
].owner
], RESOURCE_INDEX(index
), tag
? ioName
: "NONE");
6202 #ifdef USE_DSHOT_TELEMETRY
6204 static void cliDshotTelemetryInfo(const char *cmdName
, char *cmdline
)
6209 if (useDshotTelemetry
) {
6210 cliPrintLinef("Dshot reads: %u", dshotTelemetryState
.readCount
);
6211 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState
.invalidPacketCount
);
6212 int32_t directionChangeCycles
= cmp32(dshotDMAHandlerCycleCounters
.changeDirectionCompletedAt
, dshotDMAHandlerCycleCounters
.irqAt
);
6213 int32_t directionChangeDurationUs
= clockCyclesToMicros(directionChangeCycles
);
6214 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles
, directionChangeDurationUs
);
6217 #ifdef USE_DSHOT_TELEMETRY_STATS
6218 cliPrintLine("Motor Type eRPM RPM Hz Invalid TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6219 cliPrintLine("===== ====== ====== ====== ====== ======= ====== ====== ====== ====== ====== ====== ======");
6221 cliPrintLine("Motor Type eRPM RPM Hz TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6222 cliPrintLine("===== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======");
6225 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
6226 const uint16_t erpm
= getDshotErpm(i
);
6227 const uint16_t rpm
= lrintf(getDshotRpm(i
));
6229 cliPrintf("%5d %c%c%c%c%c %6d %6d %6d",
6231 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_eRPM
)) ? 'R' : '-'),
6232 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE
)) ? 'T' : '-'),
6233 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_VOLTAGE
)) ? 'V' : '-'),
6234 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_CURRENT
)) ? 'C' : '-'),
6235 ((dshotTelemetryState
.motorState
[i
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_STATE_EVENTS
)) ? 'S' : '-'),
6236 erpm
* 100, rpm
, rpm
/ 60);
6238 #ifdef USE_DSHOT_TELEMETRY_STATS
6239 if (isDshotMotorTelemetryActive(i
)) {
6240 int32_t calcPercent
= getDshotTelemetryMotorInvalidPercent(i
);
6241 cliPrintf(" %3d.%02d%%", calcPercent
/ 100, calcPercent
% 100);
6243 cliPrint(" NO DATA");
6247 cliPrintLinef(" %6d %3d.%02d %6d %6d %6d %6d %6d",
6248 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_TEMPERATURE
],
6249 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_VOLTAGE
] / 4,
6250 25 * (dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_VOLTAGE
] % 4),
6251 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_CURRENT
],
6252 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_STATE_EVENTS
],
6253 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_DEBUG1
],
6254 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_DEBUG2
],
6255 dshotTelemetryState
.motorState
[i
].telemetryData
[DSHOT_TELEMETRY_TYPE_DEBUG3
]
6260 const int len
= MAX_GCR_EDGES
;
6261 #ifdef DEBUG_BBDECODE
6262 extern uint16_t bbBuffer
[134];
6263 for (int i
= 0; i
< 134; i
++) {
6264 cliPrintf("%u ", (int)bbBuffer
[i
]);
6268 for (int i
= 0; i
< len
; i
++) {
6269 cliPrintf("%u ", (int)dshotTelemetryState
.inputBuffer
[i
]);
6272 for (int i
= 1; i
< len
; i
++) {
6273 cliPrintf("%u ", (int)(dshotTelemetryState
.inputBuffer
[i
] - dshotTelemetryState
.inputBuffer
[i
-1]));
6277 cliPrintLine("Dshot telemetry not enabled");
6283 static void printConfig(const char *cmdName
, char *cmdline
, bool doDiff
)
6285 dumpFlags_t dumpMask
= DUMP_MASTER
;
6287 if ((options
= checkCommand(cmdline
, "master"))) {
6288 dumpMask
= DUMP_MASTER
; // only
6289 } else if ((options
= checkCommand(cmdline
, "profile"))) {
6290 dumpMask
= DUMP_PROFILE
; // only
6291 } else if ((options
= checkCommand(cmdline
, "rates"))) {
6292 dumpMask
= DUMP_RATES
; // only
6293 } else if ((options
= checkCommand(cmdline
, "hardware"))) {
6294 dumpMask
= DUMP_MASTER
| HARDWARE_ONLY
; // Show only hardware related settings (useful to generate unified target configs).
6295 } else if ((options
= checkCommand(cmdline
, "all"))) {
6296 dumpMask
= DUMP_ALL
; // all profiles and rates
6302 dumpMask
= dumpMask
| DO_DIFF
;
6305 if (checkCommand(options
, "defaults")) {
6306 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
6307 } else if (checkCommand(options
, "bare")) {
6308 dumpMask
= dumpMask
| BARE
; // show the diff / dump without extra commands and board specific data
6311 backupAndResetConfigs();
6313 #ifdef USE_CLI_BATCH
6314 bool batchModeEnabled
= false;
6316 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
6317 cliPrintHashLine("version");
6318 printVersion(false);
6320 if (!(dumpMask
& BARE
)) {
6321 #ifdef USE_CLI_BATCH
6322 cliPrintHashLine("start the command batch");
6323 cliPrintLine("batch start");
6324 batchModeEnabled
= true;
6327 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
6328 cliPrintHashLine("reset configuration to default settings");
6329 cliPrintLine("defaults nosave");
6333 #if defined(USE_BOARD_INFO)
6335 printBoardName(dumpMask
);
6336 printManufacturerId(dumpMask
);
6339 if ((dumpMask
& DUMP_ALL
) && !(dumpMask
& BARE
)) {
6340 cliMcuId(cmdName
, "");
6341 #if defined(USE_SIGNATURE)
6342 cliSignature(cmdName
, "");
6346 if (!(dumpMask
& HARDWARE_ONLY
)) {
6347 printCraftName(dumpMask
, &pilotConfig_Copy
);
6350 #ifdef USE_RESOURCE_MGMT
6351 printResource(dumpMask
, "resources");
6352 #if defined(USE_TIMER_MGMT)
6353 printTimer(dumpMask
, "timer");
6356 printDmaopt(dumpMask
, "dma");
6360 printFeature(dumpMask
, featureConfig_Copy
.enabledFeatures
, featureConfig()->enabledFeatures
, "feature");
6362 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig(), "serial");
6364 if (!(dumpMask
& HARDWARE_ONLY
)) {
6365 #ifndef USE_QUAD_MIXER_ONLY
6366 const char *mixerHeadingStr
= "mixer";
6367 const bool equalsDefault
= mixerConfig_Copy
.mixerMode
== mixerConfig()->mixerMode
;
6368 mixerHeadingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, mixerHeadingStr
);
6369 const char *formatMixer
= "mixer %s";
6370 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig()->mixerMode
- 1]);
6371 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig_Copy
.mixerMode
- 1]);
6373 cliDumpPrintLinef(dumpMask
, customMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
6375 printMotorMix(dumpMask
, customMotorMixer_CopyArray
, customMotorMixer(0), mixerHeadingStr
);
6378 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0), "servo");
6380 const char *servoMixHeadingStr
= "servo mixer";
6381 if (!(dumpMask
& DO_DIFF
) || customServoMixers(0)->rate
!= 0) {
6382 cliPrintHashLine(servoMixHeadingStr
);
6383 cliPrintLine("smix reset\r\n");
6384 servoMixHeadingStr
= NULL
;
6386 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0), servoMixHeadingStr
);
6390 #if defined(USE_BEEPER)
6391 printBeeper(dumpMask
, beeperConfig_Copy
.beeper_off_flags
, beeperConfig()->beeper_off_flags
, "beeper", BEEPER_ALLOWED_MODES
, "beeper");
6393 #if defined(USE_DSHOT)
6394 printBeeper(dumpMask
, beeperConfig_Copy
.dshotBeaconOffFlags
, beeperConfig()->dshotBeaconOffFlags
, "beacon", DSHOT_BEACON_ALLOWED_MODES
, "beacon");
6396 #endif // USE_BEEPER
6398 printMap(dumpMask
, &rxConfig_Copy
, rxConfig(), "map");
6400 #ifdef USE_LED_STRIP_STATUS_MODE
6401 printLed(dumpMask
, ledStripStatusModeConfig_Copy
.ledConfigs
, ledStripStatusModeConfig()->ledConfigs
, "led");
6403 printColor(dumpMask
, ledStripStatusModeConfig_Copy
.colors
, ledStripStatusModeConfig()->colors
, "color");
6405 printModeColor(dumpMask
, &ledStripStatusModeConfig_Copy
, ledStripStatusModeConfig(), "mode_color");
6408 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0), "aux");
6410 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0), "adjrange");
6412 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0), "rxrange");
6414 #ifdef USE_VTX_TABLE
6415 printVtxTable(dumpMask
, &vtxTableConfig_Copy
, vtxTableConfig(), "vtxtable");
6418 #ifdef USE_VTX_CONTROL
6419 printVtx(dumpMask
, &vtxConfig_Copy
, vtxConfig(), "vtx");
6422 printRxFailsafe(dumpMask
, rxFailsafeChannelConfigs_CopyArray
, rxFailsafeChannelConfigs(0), "rxfail");
6425 if (dumpMask
& HARDWARE_ONLY
) {
6426 dumpAllValues(cmdName
, HARDWARE_VALUE
, dumpMask
, "master");
6428 dumpAllValues(cmdName
, MASTER_VALUE
, dumpMask
, "master");
6430 if (dumpMask
& DUMP_ALL
) {
6431 for (uint32_t pidProfileIndex
= 0; pidProfileIndex
< PID_PROFILE_COUNT
; pidProfileIndex
++) {
6432 cliDumpPidProfile(cmdName
, pidProfileIndex
, dumpMask
);
6435 pidProfileIndexToUse
= systemConfig_Copy
.pidProfileIndex
;
6437 if (!(dumpMask
& BARE
)) {
6438 cliPrintHashLine("restore original profile selection");
6440 cliProfile(cmdName
, "");
6443 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
6445 for (uint32_t rateIndex
= 0; rateIndex
< CONTROL_RATE_PROFILE_COUNT
; rateIndex
++) {
6446 cliDumpRateProfile(cmdName
, rateIndex
, dumpMask
);
6449 rateProfileIndexToUse
= systemConfig_Copy
.activeRateProfile
;
6451 if (!(dumpMask
& BARE
)) {
6452 cliPrintHashLine("restore original rateprofile selection");
6454 cliRateProfile(cmdName
, "");
6456 cliPrintHashLine("save configuration");
6458 #ifdef USE_CLI_BATCH
6459 batchModeEnabled
= false;
6463 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
6465 cliDumpPidProfile(cmdName
, systemConfig_Copy
.pidProfileIndex
, dumpMask
);
6467 cliDumpRateProfile(cmdName
, systemConfig_Copy
.activeRateProfile
, dumpMask
);
6470 } else if (dumpMask
& DUMP_PROFILE
) {
6471 cliDumpPidProfile(cmdName
, systemConfig_Copy
.pidProfileIndex
, dumpMask
);
6472 } else if (dumpMask
& DUMP_RATES
) {
6473 cliDumpRateProfile(cmdName
, systemConfig_Copy
.activeRateProfile
, dumpMask
);
6476 #ifdef USE_CLI_BATCH
6477 if (batchModeEnabled
) {
6478 cliPrintHashLine("end the command batch");
6479 cliPrintLine("batch end");
6483 // restore configs from copies
6487 static void cliDump(const char *cmdName
, char *cmdline
)
6489 printConfig(cmdName
, cmdline
, false);
6492 static void cliDiff(const char *cmdName
, char *cmdline
)
6494 printConfig(cmdName
, cmdline
, true);
6497 #if defined(USE_USB_MSC)
6498 static void cliMsc(const char *cmdName
, char *cmdline
)
6500 if (mscCheckFilesystemReady()) {
6502 int timezoneOffsetMinutes
= timeConfig()->tz_offsetMinutes
;
6503 if (!isEmpty(cmdline
)) {
6504 timezoneOffsetMinutes
= atoi(cmdline
);
6505 if ((timezoneOffsetMinutes
< TIMEZONE_OFFSET_MINUTES_MIN
) || (timezoneOffsetMinutes
> TIMEZONE_OFFSET_MINUTES_MAX
)) {
6506 cliPrintErrorLinef(cmdName
, "INVALID TIMEZONE OFFSET");
6511 int timezoneOffsetMinutes
= 0;
6514 cliPrintHashLine("Restarting in mass storage mode");
6515 cliPrint("\r\nRebooting");
6517 waitForSerialPortToFinishTransmitting(cliPort
);
6520 systemResetToMsc(timezoneOffsetMinutes
);
6522 cliPrintHashLine("Storage not present or failed to initialize!");
6527 typedef void cliCommandFn(const char* name
, char *cmdline
);
6532 const char *description
;
6535 cliCommandFn
*cliCommand
;
6539 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6547 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6554 static void cliHelp(const char *cmdName
, char *cmdline
);
6556 // should be sorted a..z for bsearch()
6557 const clicmd_t cmdTable
[] = {
6558 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange
),
6559 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux
),
6560 #ifdef USE_CLI_BATCH
6561 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch
),
6563 #if defined(USE_BEEPER)
6564 #if defined(USE_DSHOT)
6565 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6566 "\t<->[name]", cliBeacon
),
6568 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6569 "\t<->[name]", cliBeeper
),
6570 #endif // USE_BEEPER
6571 #if defined(USE_RX_BIND)
6572 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI, SRXL2 or CRSF", NULL
, cliRxBind
),
6574 #if defined(USE_FLASH_BOOT_LOADER)
6575 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader
),
6577 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader
),
6579 #if defined(USE_BOARD_INFO)
6580 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName
),
6582 #ifdef USE_LED_STRIP_STATUS_MODE
6583 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
6585 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{nosave}", cliDefaults
),
6586 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff
),
6587 #ifdef USE_RESOURCE_MGMT
6591 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma
),
6593 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma
),
6598 #ifdef USE_DSHOT_TELEMETRY
6599 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL
, cliDshotTelemetryInfo
),
6602 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg
),
6604 CLI_COMMAND_DEF("dump", "dump configuration",
6605 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump
),
6606 #ifdef USE_ESCSERIAL
6607 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough
),
6609 CLI_COMMAND_DEF("exit", "exit command line interface and reboot (default)", "[noreboot]", cliExitCmd
),
6610 CLI_COMMAND_DEF("feature", "configure features",
6612 "\t<->[name]", cliFeature
),
6613 #ifdef USE_FLASH_CHIP
6615 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
6617 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
6618 #if defined(USE_FLASH_TOOLS) && defined(USE_FLASHFS)
6619 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
6620 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL
, cliFlashVerify
),
6621 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
6624 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
6626 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
6628 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6629 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL
, cliDumpGyroRegisters
),
6631 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp
),
6632 #ifdef USE_LED_STRIP_STATUS_MODE
6633 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
6635 #if defined(USE_BOARD_INFO)
6636 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId
),
6638 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
6639 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL
, cliMcuId
),
6640 #ifndef USE_QUAD_MIXER_ONLY
6641 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer
),
6643 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
6644 #ifdef USE_LED_STRIP_STATUS_MODE
6645 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
6647 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
6650 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc
),
6652 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
6656 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]", cliPlaySound
),
6658 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile
),
6659 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile
),
6660 #ifdef USE_RC_SMOOTHING_FILTER
6661 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL
, cliRcSmoothing
),
6662 #endif // USE_RC_SMOOTHING_FILTER
6663 #ifdef USE_RESOURCE_MGMT
6664 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource
),
6666 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL
, cliRxFailsafe
),
6667 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
6668 CLI_COMMAND_DEF("save", "save and reboot (default)", "[noreboot]", cliSave
),
6670 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
6672 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
6673 #if defined(USE_SERIAL_PASSTHROUGH)
6674 #if defined(USE_PINIO)
6675 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
),
6677 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough
),
6681 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
6683 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
6684 #if defined(USE_SIGNATURE)
6685 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature
),
6687 #if defined(USE_SIMPLIFIED_TUNING)
6688 CLI_COMMAND_DEF("simplified_tuning", "applies or disables simplified tuning", "apply | disable", cliSimplifiedTuning
),
6691 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6693 "\tload <mixer>\r\n"
6694 "\treverse <servo> <source> r|n", cliServoMix
),
6696 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
6697 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
6698 #ifdef USE_TIMER_MGMT
6699 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer
),
6701 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
6702 #ifdef USE_VTX_CONTROL
6704 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL
, cliVtx
),
6706 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx
),
6709 #ifdef USE_VTX_TABLE
6710 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL
, cliVtxInfo
),
6711 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable
),
6715 static void cliHelp(const char *cmdName
, char *cmdline
)
6717 bool anyMatches
= false;
6719 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
6720 bool printEntry
= false;
6721 if (isEmpty(cmdline
)) {
6724 if (strcasestr(cmdTable
[i
].name
, cmdline
)
6726 || strcasestr(cmdTable
[i
].description
, cmdline
)
6735 cliPrint(cmdTable
[i
].name
);
6737 if (cmdTable
[i
].description
) {
6738 cliPrintf(" - %s", cmdTable
[i
].description
);
6740 if (cmdTable
[i
].args
) {
6741 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
6747 if (!isEmpty(cmdline
) && !anyMatches
) {
6748 cliPrintErrorLinef(cmdName
, "NO MATCHES FOR '%s'", cmdline
);
6752 static void processCharacter(const char c
)
6754 if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
6755 if (cliInteractive
) {
6756 // echo new line back to terminal
6760 // Strip comment starting with # from line
6761 char *p
= cliBuffer
;
6764 bufferIndex
= (uint32_t)(p
- cliBuffer
);
6767 // Strip trailing whitespace
6768 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
6772 // Process non-empty lines
6773 if (bufferIndex
> 0) {
6774 cliBuffer
[bufferIndex
] = 0; // null terminate
6776 const clicmd_t
*cmd
;
6778 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
6779 if ((options
= checkCommand(cliBuffer
, cmd
->name
))) {
6783 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
)) {
6784 cmd
->cliCommand(cmd
->name
, options
);
6786 // cli session ended
6790 if (cliInteractive
) {
6791 cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
6793 cliPrint("ERR_CMD_NA: ");
6794 cliPrintLine(cliBuffer
);
6799 cliClearInputBuffer();
6801 // prompt if in interactive mode
6802 if (cliInteractive
) {
6806 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
6807 if (!bufferIndex
&& c
== ' ') {
6808 return; // Ignore leading spaces
6810 cliBuffer
[bufferIndex
++] = c
;
6812 // echo the character if interactive
6813 if (cliInteractive
) {
6819 static void processCharacterInteractive(const char c
)
6821 if (c
== '\t' || c
== '?') {
6822 // do tab completion
6823 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
6824 uint32_t i
= bufferIndex
;
6825 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
6826 if (bufferIndex
&& (strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0)) {
6834 if (pstart
) { /* Buffer matches one or more commands */
6835 for (; ; bufferIndex
++) {
6836 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
6838 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
6839 /* Unambiguous -- append a space */
6840 cliBuffer
[bufferIndex
++] = ' ';
6841 cliBuffer
[bufferIndex
] = '\0';
6844 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
6847 if (!bufferIndex
|| pstart
!= pend
) {
6848 /* Print list of ambiguous matches */
6849 cliPrint("\r\n\033[K");
6850 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
6851 cliPrint(cmd
->name
);
6855 i
= 0; /* Redraw prompt */
6857 for (; i
< bufferIndex
; i
++)
6858 cliWrite(cliBuffer
[i
]);
6859 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
6862 } else if (c
== 12) { // NewPage / CTRL-L
6864 cliPrint("\033[2J\033[1;1H");
6866 } else if (c
== 127) {
6869 cliBuffer
[--bufferIndex
] = 0;
6870 cliPrint("\010 \010");
6873 processCharacter(c
);
6877 bool cliProcess(void)
6879 if (!cliWriter
|| !cliMode
) {
6883 while (serialRxBytesWaiting(cliPort
)) {
6884 uint8_t c
= serialRead(cliPort
);
6885 if (cliInteractive
) {
6886 processCharacterInteractive(c
);
6888 // handle terminating flow control character
6889 if (c
== 0x3 || (cmp32(millis(), cliEntryTime
) > 2000)) { // CTRL-C (ETX) or 2 seconds timeout
6890 cliWrite(0x3); // send end of text, terminating flow control
6894 processCharacter(c
);
6901 static void cliExit(const bool reboot
)
6904 waitForSerialPortToFinishTransmitting(cliPort
);
6905 cliClearInputBuffer();
6907 cliInteractive
= false;
6908 // incase a motor was left running during motortest, clear it here
6909 mixerResetDisarmedMotors();
6916 void cliEnter(serialPort_t
*serialPort
, bool interactive
)
6919 cliInteractive
= interactive
;
6920 cliPort
= serialPort
;
6921 cliEntryTime
= millis();
6922 cliClearInputBuffer();
6925 setPrintfSerialPort(cliPort
);
6928 bufWriterInit(&cliWriterDesc
, cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
6929 cliErrorWriter
= cliWriter
= &cliWriterDesc
;
6933 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to reboot, or 'help'");
6935 cliPrintLine("\r\nCLI");
6937 // arming flag not released if exiting cli with no reboot for safety
6938 setArmingDisabled(ARMING_DISABLED_CLI
);
6941 #ifdef USE_CLI_BATCH
6942 resetCommandBatch();
6945 cliWrite(0x2); // send start of text, initiating flow control