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
35 extern uint8_t __config_start
; // configured via linker script when building binaries.
36 extern uint8_t __config_end
;
41 #include "blackbox/blackbox.h"
43 #include "build/build_config.h"
44 #include "build/debug.h"
45 #include "build/version.h"
47 #include "cli/settings.h"
51 #include "common/axis.h"
52 #include "common/color.h"
53 #include "common/maths.h"
54 #include "common/printf.h"
55 #include "common/printf_serial.h"
56 #include "common/strtol.h"
57 #include "common/time.h"
58 #include "common/typeconversion.h"
59 #include "common/utils.h"
61 #include "config/config_eeprom.h"
62 #include "config/feature.h"
64 #include "drivers/accgyro/accgyro.h"
65 #include "drivers/adc.h"
66 #include "drivers/buf_writer.h"
67 #include "drivers/bus_spi.h"
68 #include "drivers/dma_reqmap.h"
69 #include "drivers/camera_control.h"
70 #include "drivers/compass/compass.h"
71 #include "drivers/display.h"
72 #include "drivers/dma.h"
73 #include "drivers/flash.h"
74 #include "drivers/inverter.h"
75 #include "drivers/io.h"
76 #include "drivers/io_impl.h"
77 #include "drivers/light_led.h"
78 #include "drivers/pwm_output.h"
79 #include "drivers/rangefinder/rangefinder_hcsr04.h"
80 #include "drivers/sdcard.h"
81 #include "drivers/sensor.h"
82 #include "drivers/serial.h"
83 #include "drivers/serial_escserial.h"
84 #include "drivers/sound_beeper.h"
85 #include "drivers/stack_check.h"
86 #include "drivers/system.h"
87 #include "drivers/time.h"
88 #include "drivers/timer.h"
89 #include "drivers/transponder_ir.h"
90 #include "drivers/usb_msc.h"
91 #include "drivers/vtx_common.h"
92 #include "drivers/vtx_table.h"
94 #include "fc/board_info.h"
95 #include "fc/config.h"
96 #include "fc/controlrate_profile.h"
99 #include "fc/rc_adjustments.h"
100 #include "fc/rc_controls.h"
101 #include "fc/runtime_config.h"
103 #include "flight/failsafe.h"
104 #include "flight/imu.h"
105 #include "flight/mixer.h"
106 #include "flight/pid.h"
107 #include "flight/position.h"
108 #include "flight/servos.h"
110 #include "io/asyncfatfs/asyncfatfs.h"
111 #include "io/beeper.h"
112 #include "io/flashfs.h"
113 #include "io/gimbal.h"
115 #include "io/ledstrip.h"
116 #include "io/serial.h"
117 #include "io/transponder_ir.h"
118 #include "io/usb_msc.h"
119 #include "io/vtx_control.h"
123 #include "msp/msp_box.h"
124 #include "msp/msp_protocol.h"
129 #include "pg/beeper.h"
130 #include "pg/beeper_dev.h"
131 #include "pg/board.h"
132 #include "pg/bus_i2c.h"
133 #include "pg/bus_spi.h"
134 #include "pg/gyrodev.h"
135 #include "pg/max7456.h"
137 #include "pg/pinio.h"
139 #include "pg/pg_ids.h"
141 #include "pg/rx_spi_cc2500.h"
142 #include "pg/rx_spi.h"
143 #include "pg/rx_pwm.h"
144 #include "pg/serial_uart.h"
146 #include "pg/timerio.h"
148 #include "pg/vtx_table.h"
151 #include "rx/spektrum.h"
152 #include "rx/rx_spi_common.h"
154 #include "scheduler/scheduler.h"
156 #include "sensors/acceleration.h"
157 #include "sensors/adcinternal.h"
158 #include "sensors/barometer.h"
159 #include "sensors/battery.h"
160 #include "sensors/boardalignment.h"
161 #include "sensors/compass.h"
162 #include "sensors/esc_sensor.h"
163 #include "sensors/gyro.h"
164 #include "sensors/sensors.h"
166 #include "telemetry/frsky_hub.h"
167 #include "telemetry/telemetry.h"
171 static serialPort_t
*cliPort
;
174 #define CLI_IN_BUFFER_SIZE 128
176 // Space required to set array parameters
177 #define CLI_IN_BUFFER_SIZE 256
179 #define CLI_OUT_BUFFER_SIZE 64
181 static bufWriter_t
*cliWriter
;
182 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + CLI_OUT_BUFFER_SIZE
];
184 static char cliBuffer
[CLI_IN_BUFFER_SIZE
];
185 static uint32_t bufferIndex
= 0;
187 static bool configIsInCopy
= false;
189 #define CURRENT_PROFILE_INDEX -1
190 static int8_t pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
191 static int8_t rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
193 static bool featureMaskIsCopied
= false;
194 static uint32_t featureMaskCopy
;
197 static bool commandBatchActive
= false;
198 static bool commandBatchError
= false;
201 #if defined(USE_BOARD_INFO)
202 static bool boardInformationUpdated
= false;
203 #if defined(USE_SIGNATURE)
204 static bool signatureUpdated
= false;
206 #endif // USE_BOARD_INFO
208 static const char* const emptyName
= "-";
209 static const char* const emptyString
= "";
211 #ifndef USE_QUAD_MIXER_ONLY
212 // sync this with mixerMode_e
213 static const char * const mixerNames
[] = {
214 "TRI", "QUADP", "QUADX", "BI",
215 "GIMBAL", "Y6", "HEX6",
216 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
217 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
218 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
219 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
223 // sync this with features_e
224 static const char * const featureNames
[] = {
225 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
226 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
227 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
228 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
229 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
230 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
233 // sync this with rxFailsafeChannelMode_e
234 static const char rxFailsafeModeCharacters
[] = "ahs";
236 static const rxFailsafeChannelMode_e rxFailsafeModesTable
[RX_FAILSAFE_TYPE_COUNT
][RX_FAILSAFE_MODE_COUNT
] = {
237 { RX_FAILSAFE_MODE_AUTO
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
},
238 { RX_FAILSAFE_MODE_INVALID
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
}
241 #if defined(USE_SENSOR_NAMES)
242 // sync this with sensors_e
243 static const char * const sensorTypeNames
[] = {
244 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
247 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
249 static const char * const *sensorHardwareNames
[] = {
250 lookupTableGyroHardware
, lookupTableAccHardware
, lookupTableBaroHardware
, lookupTableMagHardware
, lookupTableRangefinderHardware
252 #endif // USE_SENSOR_NAMES
254 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
255 extern uint32_t readDoneCount
;
256 extern uint32_t inputBuffer
[DSHOT_TELEMETRY_INPUT_LEN
];
257 extern uint32_t setDirectionMicros
;
260 typedef enum dumpFlags_e
{
261 DUMP_MASTER
= (1 << 0),
262 DUMP_PROFILE
= (1 << 1),
263 DUMP_RATES
= (1 << 2),
266 SHOW_DEFAULTS
= (1 << 5),
267 HIDE_UNUSED
= (1 << 6),
268 HARDWARE_ONLY
= (1 << 7),
272 typedef bool printFn(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...);
275 static void backupPgConfig(const pgRegistry_t
*pg
)
277 memcpy(pg
->copy
, pg
->address
, pg
->size
);
280 static void restorePgConfig(const pgRegistry_t
*pg
)
282 memcpy(pg
->address
, pg
->copy
, pg
->size
);
285 static void backupConfigs(void)
287 // make copies of configs to do differencing
292 configIsInCopy
= true;
295 static void restoreConfigs(void)
301 configIsInCopy
= false;
304 static void backupAndResetConfigs(void)
307 // reset all configs to defaults to do differencing
311 static void cliPrint(const char *str
)
314 bufWriterAppend(cliWriter
, *str
++);
316 bufWriterFlush(cliWriter
);
319 static void cliPrintLinefeed(void)
324 static void cliPrintLine(const char *str
)
331 #define cliPrintHashLine(str)
333 static void cliPrintHashLine(const char *str
)
340 static void cliPutp(void *p
, char ch
)
342 bufWriterAppend(p
, ch
);
345 static void cliPrintfva(const char *format
, va_list va
)
347 tfp_format(cliWriter
, cliPutp
, format
, va
);
348 bufWriterFlush(cliWriter
);
351 static bool cliDumpPrintLinef(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...)
353 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
355 va_start(va
, format
);
356 cliPrintfva(format
, va
);
365 static void cliWrite(uint8_t ch
)
367 bufWriterAppend(cliWriter
, ch
);
370 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask
, bool equalsDefault
, const char *format
, ...)
372 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
376 va_start(va
, format
);
377 cliPrintfva(format
, va
);
386 static void cliPrintf(const char *format
, ...)
389 va_start(va
, format
);
390 cliPrintfva(format
, va
);
395 static void cliPrintLinef(const char *format
, ...)
398 va_start(va
, format
);
399 cliPrintfva(format
, va
);
404 static void cliPrintErrorVa(const char *format
, va_list va
)
406 cliPrint("###ERROR: ");
407 cliPrintfva(format
, va
);
412 if (commandBatchActive
) {
413 commandBatchError
= true;
418 static void cliPrintError(const char *format
, ...)
421 va_start(va
, format
);
422 cliPrintErrorVa(format
, va
);
425 static void cliPrintErrorLinef(const char *format
, ...)
428 va_start(va
, format
);
429 cliPrintErrorVa(format
, va
);
433 static void getMinMax(const clivalue_t
*var
, int *min
, int *max
)
435 switch (var
->type
& VALUE_TYPE_MASK
) {
438 *min
= var
->config
.minmaxUnsigned
.min
;
439 *max
= var
->config
.minmaxUnsigned
.max
;
443 *min
= var
->config
.minmax
.min
;
444 *max
= var
->config
.minmax
.max
;
450 static void printValuePointer(const clivalue_t
*var
, const void *valuePointer
, bool full
)
452 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
453 for (int i
= 0; i
< var
->config
.array
.length
; i
++) {
454 switch (var
->type
& VALUE_TYPE_MASK
) {
458 cliPrintf("%d", ((uint8_t *)valuePointer
)[i
]);
463 cliPrintf("%d", ((int8_t *)valuePointer
)[i
]);
468 cliPrintf("%d", ((uint16_t *)valuePointer
)[i
]);
473 cliPrintf("%d", ((int16_t *)valuePointer
)[i
]);
477 if (i
< var
->config
.array
.length
- 1) {
484 switch (var
->type
& VALUE_TYPE_MASK
) {
486 value
= *(uint8_t *)valuePointer
;
490 value
= *(int8_t *)valuePointer
;
494 value
= *(uint16_t *)valuePointer
;
498 value
= *(int16_t *)valuePointer
;
502 value
= *(uint32_t *)valuePointer
;
507 bool valueIsCorrupted
= false;
508 switch (var
->type
& VALUE_MODE_MASK
) {
510 if ((var
->type
& VALUE_TYPE_MASK
) == VAR_UINT32
) {
511 cliPrintf("%u", (uint32_t)value
);
512 if ((uint32_t)value
> var
->config
.u32Max
) {
513 valueIsCorrupted
= true;
515 cliPrintf(" 0 %u", var
->config
.u32Max
);
520 getMinMax(var
, &min
, &max
);
522 cliPrintf("%d", value
);
523 if ((value
< min
) || (value
> max
)) {
524 valueIsCorrupted
= true;
526 cliPrintf(" %d %d", min
, max
);
531 if (value
< lookupTables
[var
->config
.lookup
.tableIndex
].valueCount
) {
532 cliPrint(lookupTables
[var
->config
.lookup
.tableIndex
].values
[value
]);
534 valueIsCorrupted
= true;
538 if (value
& 1 << var
->config
.bitpos
) {
545 cliPrintf("%s", (strlen((char *)valuePointer
) == 0) ? "-" : (char *)valuePointer
);
549 if (valueIsCorrupted
) {
551 cliPrintError("CORRUPTED CONFIG: %s = %d", var
->name
, value
);
557 static bool valuePtrEqualsDefault(const clivalue_t
*var
, const void *ptr
, const void *ptrDefault
)
560 int elementCount
= 1;
561 uint32_t mask
= 0xffffffff;
563 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
564 elementCount
= var
->config
.array
.length
;
566 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
567 mask
= 1 << var
->config
.bitpos
;
569 for (int i
= 0; i
< elementCount
; i
++) {
570 switch (var
->type
& VALUE_TYPE_MASK
) {
572 result
= result
&& (((uint8_t *)ptr
)[i
] & mask
) == (((uint8_t *)ptrDefault
)[i
] & mask
);
576 result
= result
&& ((int8_t *)ptr
)[i
] == ((int8_t *)ptrDefault
)[i
];
580 result
= result
&& (((uint16_t *)ptr
)[i
] & mask
) == (((uint16_t *)ptrDefault
)[i
] & mask
);
583 result
= result
&& ((int16_t *)ptr
)[i
] == ((int16_t *)ptrDefault
)[i
];
586 result
= result
&& (((uint32_t *)ptr
)[i
] & mask
) == (((uint32_t *)ptrDefault
)[i
] & mask
);
594 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask
, bool outputFlag
, const char *headingStr
)
596 if (headingStr
&& (!(dumpMask
& DO_DIFF
) || outputFlag
)) {
597 cliPrintHashLine(headingStr
);
604 static uint8_t getPidProfileIndexToUse()
606 return pidProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentPidProfileIndex() : pidProfileIndexToUse
;
609 static uint8_t getRateProfileIndexToUse()
611 return rateProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentControlRateProfileIndex() : rateProfileIndexToUse
;
615 static uint16_t getValueOffset(const clivalue_t
*value
)
617 switch (value
->type
& VALUE_SECTION_MASK
) {
620 return value
->offset
;
622 return value
->offset
+ sizeof(pidProfile_t
) * getPidProfileIndexToUse();
623 case PROFILE_RATE_VALUE
:
624 return value
->offset
+ sizeof(controlRateConfig_t
) * getRateProfileIndexToUse();
629 void *cliGetValuePointer(const clivalue_t
*value
)
631 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
632 if (configIsInCopy
) {
633 return CONST_CAST(void *, rec
->copy
+ getValueOffset(value
));
635 return CONST_CAST(void *, rec
->address
+ getValueOffset(value
));
639 const void *cliGetDefaultPointer(const clivalue_t
*value
)
641 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
642 return rec
->address
+ getValueOffset(value
);
645 static const char *dumpPgValue(const clivalue_t
*value
, dumpFlags_t dumpMask
, const char *headingStr
)
647 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
650 cliPrintLinef("VALUE %s ERROR", value
->name
);
651 return; // if it's not found, the pgn shouldn't be in the value table!
655 const char *format
= "set %s = ";
656 const char *defaultFormat
= "#set %s = ";
657 const int valueOffset
= getValueOffset(value
);
658 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
660 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
661 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
662 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
663 cliPrintf(defaultFormat
, value
->name
);
664 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
667 cliPrintf(format
, value
->name
);
668 printValuePointer(value
, pg
->copy
+ valueOffset
, false);
674 static void dumpAllValues(uint16_t valueSection
, dumpFlags_t dumpMask
, const char *headingStr
)
676 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
678 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
679 const clivalue_t
*value
= &valueTable
[i
];
680 bufWriterFlush(cliWriter
);
681 if ((value
->type
& VALUE_SECTION_MASK
) == valueSection
|| ((valueSection
== MASTER_VALUE
) && (value
->type
& VALUE_SECTION_MASK
) == HARDWARE_VALUE
)) {
682 headingStr
= dumpPgValue(value
, dumpMask
, headingStr
);
687 static void cliPrintVar(const clivalue_t
*var
, bool full
)
689 const void *ptr
= cliGetValuePointer(var
);
691 printValuePointer(var
, ptr
, full
);
694 static void cliPrintVarRange(const clivalue_t
*var
)
696 switch (var
->type
& VALUE_MODE_MASK
) {
697 case (MODE_DIRECT
): {
698 switch (var
->type
& VALUE_TYPE_MASK
) {
700 cliPrintLinef("Allowed range: 0 - %u", var
->config
.u32Max
);
705 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmaxUnsigned
.min
, var
->config
.minmaxUnsigned
.max
);
709 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
715 case (MODE_LOOKUP
): {
716 const lookupTableEntry_t
*tableEntry
= &lookupTables
[var
->config
.lookup
.tableIndex
];
717 cliPrint("Allowed values: ");
718 bool firstEntry
= true;
719 for (unsigned i
= 0; i
< tableEntry
->valueCount
; i
++) {
720 if (tableEntry
->values
[i
]) {
724 cliPrintf("%s", tableEntry
->values
[i
]);
732 cliPrintLinef("Array length: %d", var
->config
.array
.length
);
735 case (MODE_BITSET
): {
736 cliPrintLinef("Allowed values: OFF, ON");
742 static void cliSetVar(const clivalue_t
*var
, const uint32_t value
)
744 void *ptr
= cliGetValuePointer(var
);
748 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
749 switch (var
->type
& VALUE_TYPE_MASK
) {
751 mask
= (1 << var
->config
.bitpos
) & 0xff;
753 workValue
= *(uint8_t *)ptr
| mask
;
755 workValue
= *(uint8_t *)ptr
& ~mask
;
757 *(uint8_t *)ptr
= workValue
;
761 mask
= (1 << var
->config
.bitpos
) & 0xffff;
763 workValue
= *(uint16_t *)ptr
| mask
;
765 workValue
= *(uint16_t *)ptr
& ~mask
;
767 *(uint16_t *)ptr
= workValue
;
771 mask
= 1 << var
->config
.bitpos
;
773 workValue
= *(uint32_t *)ptr
| mask
;
775 workValue
= *(uint32_t *)ptr
& ~mask
;
777 *(uint32_t *)ptr
= workValue
;
781 switch (var
->type
& VALUE_TYPE_MASK
) {
783 *(uint8_t *)ptr
= value
;
787 *(int8_t *)ptr
= value
;
791 *(uint16_t *)ptr
= value
;
795 *(int16_t *)ptr
= value
;
799 *(uint32_t *)ptr
= value
;
805 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
806 static void cliRepeat(char ch
, uint8_t len
)
808 for (int i
= 0; i
< len
; i
++) {
809 bufWriterAppend(cliWriter
, ch
);
815 static void cliPrompt(void)
820 static void cliShowParseError(void)
822 cliPrintErrorLinef("PARSE ERROR");
825 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
827 cliPrintErrorLinef("%s NOT BETWEEN %d AND %d", name
, min
, max
);
830 static const char *nextArg(const char *currentArg
)
832 const char *ptr
= strchr(currentArg
, ' ');
833 while (ptr
&& *ptr
== ' ') {
840 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
842 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
846 val
= CHANNEL_VALUE_TO_STEP(val
);
847 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
849 range
->startStep
= val
;
851 range
->endStep
= val
;
853 (*validArgumentCount
)++;
861 // Check if a string's length is zero
862 static bool isEmpty(const char *string
)
864 return (string
== NULL
|| *string
== '\0') ? true : false;
867 static void printRxFailsafe(dumpFlags_t dumpMask
, const rxFailsafeChannelConfig_t
*rxFailsafeChannelConfigs
, const rxFailsafeChannelConfig_t
*defaultRxFailsafeChannelConfigs
, const char *headingStr
)
869 // print out rxConfig failsafe settings
870 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
871 for (uint32_t channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
872 const rxFailsafeChannelConfig_t
*channelFailsafeConfig
= &rxFailsafeChannelConfigs
[channel
];
873 const rxFailsafeChannelConfig_t
*defaultChannelFailsafeConfig
= &defaultRxFailsafeChannelConfigs
[channel
];
874 const bool equalsDefault
= !memcmp(channelFailsafeConfig
, defaultChannelFailsafeConfig
, sizeof(*channelFailsafeConfig
));
875 const bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
876 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
878 const char *format
= "rxfail %u %c %d";
879 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
881 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
],
882 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig
->step
)
884 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
886 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
],
887 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
890 const char *format
= "rxfail %u %c";
891 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
893 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
]
895 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
897 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
]
903 static void cliRxFailsafe(char *cmdline
)
908 if (isEmpty(cmdline
)) {
909 // print out rxConfig failsafe settings
910 for (channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
911 cliRxFailsafe(itoa(channel
, buf
, 10));
914 const char *ptr
= cmdline
;
915 channel
= atoi(ptr
++);
916 if ((channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
)) {
918 rxFailsafeChannelConfig_t
*channelFailsafeConfig
= rxFailsafeChannelConfigsMutable(channel
);
920 const rxFailsafeChannelType_e type
= (channel
< NON_AUX_CHANNEL_COUNT
) ? RX_FAILSAFE_TYPE_FLIGHT
: RX_FAILSAFE_TYPE_AUX
;
921 rxFailsafeChannelMode_e mode
= channelFailsafeConfig
->mode
;
922 bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
926 const char *p
= strchr(rxFailsafeModeCharacters
, *(ptr
));
928 const uint8_t requestedMode
= p
- rxFailsafeModeCharacters
;
929 mode
= rxFailsafeModesTable
[type
][requestedMode
];
931 mode
= RX_FAILSAFE_MODE_INVALID
;
933 if (mode
== RX_FAILSAFE_MODE_INVALID
) {
938 requireValue
= mode
== RX_FAILSAFE_MODE_SET
;
946 uint16_t value
= atoi(ptr
);
947 value
= CHANNEL_VALUE_TO_RXFAIL_STEP(value
);
948 if (value
> MAX_RXFAIL_RANGE_STEP
) {
949 cliPrintLine("Value out of range");
953 channelFailsafeConfig
->step
= value
;
954 } else if (requireValue
) {
958 channelFailsafeConfig
->mode
= mode
;
961 char modeCharacter
= rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
];
963 // double use of cliPrintf below
964 // 1. acknowledge interpretation on command,
965 // 2. query current setting on single item,
968 cliPrintLinef("rxfail %u %c %d",
971 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
974 cliPrintLinef("rxfail %u %c",
980 cliShowArgumentRangeError("CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT
- 1);
985 static void printAux(dumpFlags_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
, const char *headingStr
)
987 const char *format
= "aux %u %u %u %u %u %u %u";
988 // print out aux channel settings
989 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
990 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
991 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
992 bool equalsDefault
= false;
993 if (defaultModeActivationConditions
) {
994 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
995 equalsDefault
= !isModeActivationConditionConfigured(mac
, macDefault
);
996 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
997 const box_t
*box
= findBoxByBoxId(macDefault
->modeId
);
998 const box_t
*linkedTo
= findBoxByBoxId(macDefault
->linkedTo
);
1000 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1003 macDefault
->auxChannelIndex
,
1004 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
1005 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
),
1006 macDefault
->modeLogic
,
1007 linkedTo
? linkedTo
->permanentId
: 0
1011 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
1012 const box_t
*linkedTo
= findBoxByBoxId(mac
->linkedTo
);
1014 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1017 mac
->auxChannelIndex
,
1018 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
1019 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
1021 linkedTo
? linkedTo
->permanentId
: 0
1027 static void cliAux(char *cmdline
)
1032 if (isEmpty(cmdline
)) {
1033 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
, NULL
);
1037 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
1038 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
1039 uint8_t validArgumentCount
= 0;
1043 const box_t
*box
= findBoxByPermanentId(val
);
1045 mac
->modeId
= box
->boxId
;
1046 validArgumentCount
++;
1052 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1053 mac
->auxChannelIndex
= val
;
1054 validArgumentCount
++;
1057 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
1061 if (val
== MODELOGIC_OR
|| val
== MODELOGIC_AND
) {
1062 mac
->modeLogic
= val
;
1063 validArgumentCount
++;
1069 const box_t
*box
= findBoxByPermanentId(val
);
1071 mac
->linkedTo
= box
->boxId
;
1072 validArgumentCount
++;
1075 if (validArgumentCount
== 4) { // for backwards compatibility
1076 mac
->modeLogic
= MODELOGIC_OR
;
1078 } else if (validArgumentCount
== 5) { // for backwards compatibility
1080 } else if (validArgumentCount
!= 6) {
1081 memset(mac
, 0, sizeof(modeActivationCondition_t
));
1083 analyzeModeActivationConditions();
1084 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1086 findBoxByBoxId(mac
->modeId
)->permanentId
,
1087 mac
->auxChannelIndex
,
1088 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
1089 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
1091 findBoxByBoxId(mac
->linkedTo
)->permanentId
1094 cliShowArgumentRangeError("INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
1099 static void printSerial(dumpFlags_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
, const char *headingStr
)
1101 const char *format
= "serial %d %d %ld %ld %ld %ld";
1102 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1103 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
1104 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
1107 bool equalsDefault
= false;
1108 if (serialConfigDefault
) {
1109 equalsDefault
= !memcmp(&serialConfig
->portConfigs
[i
], &serialConfigDefault
->portConfigs
[i
], sizeof(serialConfig
->portConfigs
[i
]));
1110 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1111 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1112 serialConfigDefault
->portConfigs
[i
].identifier
,
1113 serialConfigDefault
->portConfigs
[i
].functionMask
,
1114 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
1115 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
1116 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
1117 baudRates
[serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
]
1120 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1121 serialConfig
->portConfigs
[i
].identifier
,
1122 serialConfig
->portConfigs
[i
].functionMask
,
1123 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
1124 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
1125 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
1126 baudRates
[serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
]
1131 static void cliSerial(char *cmdline
)
1133 const char *format
= "serial %d %d %ld %ld %ld %ld";
1134 if (isEmpty(cmdline
)) {
1135 printSerial(DUMP_MASTER
, serialConfig(), NULL
, NULL
);
1138 serialPortConfig_t portConfig
;
1139 memset(&portConfig
, 0 , sizeof(portConfig
));
1141 serialPortConfig_t
*currentConfig
;
1143 uint8_t validArgumentCount
= 0;
1145 const char *ptr
= cmdline
;
1147 int val
= atoi(ptr
++);
1148 currentConfig
= serialFindPortConfiguration(val
);
1149 if (currentConfig
) {
1150 portConfig
.identifier
= val
;
1151 validArgumentCount
++;
1157 portConfig
.functionMask
= val
& 0xFFFF;
1158 validArgumentCount
++;
1161 for (int i
= 0; i
< 4; i
++) {
1169 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
1170 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
1176 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_1000000
) {
1179 portConfig
.msp_baudrateIndex
= baudRateIndex
;
1182 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
1185 portConfig
.gps_baudrateIndex
= baudRateIndex
;
1188 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
1191 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
1194 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_2470000
) {
1197 portConfig
.blackbox_baudrateIndex
= baudRateIndex
;
1201 validArgumentCount
++;
1204 if (validArgumentCount
< 6) {
1205 cliShowParseError();
1209 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
1211 cliDumpPrintLinef(0, false, format
,
1212 portConfig
.identifier
,
1213 portConfig
.functionMask
,
1214 baudRates
[portConfig
.msp_baudrateIndex
],
1215 baudRates
[portConfig
.gps_baudrateIndex
],
1216 baudRates
[portConfig
.telemetry_baudrateIndex
],
1217 baudRates
[portConfig
.blackbox_baudrateIndex
]
1222 #if defined(USE_SERIAL_PASSTHROUGH)
1223 static void cbCtrlLine(void *context
, uint16_t ctrl
)
1226 int contextValue
= (int)(long)context
;
1228 pinioSet(contextValue
- 1, !(ctrl
& CTRL_LINE_STATE_DTR
));
1230 #endif /* USE_PINIO */
1233 if (!(ctrl
& CTRL_LINE_STATE_DTR
)) {
1238 static void cliSerialPassthrough(char *cmdline
)
1240 if (isEmpty(cmdline
)) {
1241 cliShowParseError();
1247 bool enableBaudCb
= false;
1249 bool resetOnDtr
= false;
1252 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
1255 while (tok
!= NULL
) {
1264 if (strcasestr(tok
, "rx")) {
1267 if (strcasestr(tok
, "tx")) {
1272 if (strncasecmp(tok
, "reset", strlen(tok
)) == 0) {
1276 pinioDtr
= atoi(tok
);
1277 #endif /* USE_PINIO */
1283 tok
= strtok_r(NULL
, " ", &saveptr
);
1287 enableBaudCb
= true;
1290 cliPrintf("Port %d ", id
);
1291 serialPort_t
*passThroughPort
;
1292 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
1293 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
1303 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
, NULL
,
1305 SERIAL_NOT_INVERTED
);
1306 if (!passThroughPort
) {
1307 cliPrintLine("could not be opened.");
1312 cliPrintf("opened, default baud = %d.\r\n", baud
);
1314 cliPrintf("opened, baud = %d.\r\n", baud
);
1317 passThroughPort
= passThroughPortUsage
->serialPort
;
1318 // If the user supplied a mode, override the port's mode, otherwise
1319 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1320 // Set the baud rate if specified
1322 cliPrintf("already open, setting baud = %d.\n\r", baud
);
1323 serialSetBaudRate(passThroughPort
, baud
);
1325 cliPrintf("already open, baud = %d.\n\r", passThroughPort
->baudRate
);
1328 if (mode
&& passThroughPort
->mode
!= mode
) {
1329 cliPrintf("Mode changed from %d to %d.\r\n",
1330 passThroughPort
->mode
, mode
);
1331 serialSetMode(passThroughPort
, mode
);
1334 // If this port has a rx callback associated we need to remove it now.
1335 // Otherwise no data will be pushed in the serial port buffer!
1336 if (passThroughPort
->rxCallback
) {
1337 passThroughPort
->rxCallback
= 0;
1341 // If no baud rate is specified allow to be set via USB
1343 cliPrintLine("Baud rate change over USB enabled.");
1344 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1345 // baud rate over USB without setting it using the serialpassthrough command
1346 serialSetBaudRateCb(cliPort
, serialSetBaudRate
, passThroughPort
);
1349 char *resetMessage
= "";
1351 resetMessage
= "or drop DTR ";
1354 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage
);
1359 #endif /* USE_PINIO */
1361 // Register control line state callback
1362 serialSetCtrlLineStateCb(cliPort
, cbCtrlLine
, (void *)(intptr_t)(pinioDtr
));
1365 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
1369 static void printAdjustmentRange(dumpFlags_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
, const char *headingStr
)
1371 const char *format
= "adjrange %u %u %u %u %u %u %u %u %u";
1372 // print out adjustment ranges channel settings
1373 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1374 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
1375 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
1376 bool equalsDefault
= false;
1377 if (defaultAdjustmentRanges
) {
1378 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
1379 equalsDefault
= !memcmp(ar
, arDefault
, sizeof(*ar
));
1380 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1381 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1383 arDefault
->adjustmentIndex
,
1384 arDefault
->auxChannelIndex
,
1385 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1386 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1387 arDefault
->adjustmentConfig
,
1388 arDefault
->auxSwitchChannelIndex
,
1389 arDefault
->adjustmentCenter
,
1390 arDefault
->adjustmentScale
1393 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1395 ar
->adjustmentIndex
,
1396 ar
->auxChannelIndex
,
1397 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1398 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1399 ar
->adjustmentConfig
,
1400 ar
->auxSwitchChannelIndex
,
1401 ar
->adjustmentCenter
,
1407 static void cliAdjustmentRange(char *cmdline
)
1409 const char *format
= "adjrange %u %u %u %u %u %u %u %u %u";
1413 if (isEmpty(cmdline
)) {
1414 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
, NULL
);
1418 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1419 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1420 uint8_t validArgumentCount
= 0;
1425 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
1426 ar
->adjustmentIndex
= val
;
1427 validArgumentCount
++;
1433 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1434 ar
->auxChannelIndex
= val
;
1435 validArgumentCount
++;
1439 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1444 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1445 ar
->adjustmentConfig
= val
;
1446 validArgumentCount
++;
1452 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1453 ar
->auxSwitchChannelIndex
= val
;
1454 validArgumentCount
++;
1458 if (validArgumentCount
!= 6) {
1459 memset(ar
, 0, sizeof(adjustmentRange_t
));
1460 cliShowParseError();
1464 // Optional arguments
1465 ar
->adjustmentCenter
= 0;
1466 ar
->adjustmentScale
= 0;
1471 ar
->adjustmentCenter
= val
;
1472 validArgumentCount
++;
1477 ar
->adjustmentScale
= val
;
1478 validArgumentCount
++;
1481 activeAdjustmentRangeReset();
1483 cliDumpPrintLinef(0, false, format
,
1485 ar
->adjustmentIndex
,
1486 ar
->auxChannelIndex
,
1487 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1488 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1489 ar
->adjustmentConfig
,
1490 ar
->auxSwitchChannelIndex
,
1491 ar
->adjustmentCenter
,
1496 cliShowArgumentRangeError("INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1501 #ifndef USE_QUAD_MIXER_ONLY
1502 static void printMotorMix(dumpFlags_t dumpMask
, const motorMixer_t
*customMotorMixer
, const motorMixer_t
*defaultCustomMotorMixer
, const char *headingStr
)
1504 const char *format
= "mmix %d %s %s %s %s";
1505 char buf0
[FTOA_BUFFER_LENGTH
];
1506 char buf1
[FTOA_BUFFER_LENGTH
];
1507 char buf2
[FTOA_BUFFER_LENGTH
];
1508 char buf3
[FTOA_BUFFER_LENGTH
];
1509 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1510 if (customMotorMixer
[i
].throttle
== 0.0f
)
1512 const float thr
= customMotorMixer
[i
].throttle
;
1513 const float roll
= customMotorMixer
[i
].roll
;
1514 const float pitch
= customMotorMixer
[i
].pitch
;
1515 const float yaw
= customMotorMixer
[i
].yaw
;
1516 bool equalsDefault
= false;
1517 if (defaultCustomMotorMixer
) {
1518 const float thrDefault
= defaultCustomMotorMixer
[i
].throttle
;
1519 const float rollDefault
= defaultCustomMotorMixer
[i
].roll
;
1520 const float pitchDefault
= defaultCustomMotorMixer
[i
].pitch
;
1521 const float yawDefault
= defaultCustomMotorMixer
[i
].yaw
;
1522 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1524 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1525 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1527 ftoa(thrDefault
, buf0
),
1528 ftoa(rollDefault
, buf1
),
1529 ftoa(pitchDefault
, buf2
),
1530 ftoa(yawDefault
, buf3
));
1532 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1540 #endif // USE_QUAD_MIXER_ONLY
1542 static void cliMotorMix(char *cmdline
)
1544 #ifdef USE_QUAD_MIXER_ONLY
1551 if (isEmpty(cmdline
)) {
1552 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
, NULL
);
1553 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1554 // erase custom mixer
1555 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1556 customMotorMixerMutable(i
)->throttle
= 0.0f
;
1558 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1559 ptr
= nextArg(cmdline
);
1562 for (uint32_t i
= 0; ; i
++) {
1563 if (mixerNames
[i
] == NULL
) {
1564 cliPrintErrorLinef("INVALID NAME");
1567 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1568 mixerLoadMix(i
, customMotorMixerMutable(0));
1569 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1577 uint32_t i
= atoi(ptr
); // get motor number
1578 if (i
< MAX_SUPPORTED_MOTORS
) {
1581 customMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1586 customMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1591 customMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1596 customMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1600 cliShowParseError();
1602 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
, NULL
);
1605 cliShowArgumentRangeError("INDEX", 0, MAX_SUPPORTED_MOTORS
- 1);
1611 static void printRxRange(dumpFlags_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
, const char *headingStr
)
1613 const char *format
= "rxrange %u %u %u";
1614 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1615 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1616 bool equalsDefault
= false;
1617 if (defaultChannelRangeConfigs
) {
1618 equalsDefault
= !memcmp(&channelRangeConfigs
[i
], &defaultChannelRangeConfigs
[i
], sizeof(channelRangeConfigs
[i
]));
1619 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1620 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1622 defaultChannelRangeConfigs
[i
].min
,
1623 defaultChannelRangeConfigs
[i
].max
1626 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1628 channelRangeConfigs
[i
].min
,
1629 channelRangeConfigs
[i
].max
1634 static void cliRxRange(char *cmdline
)
1636 const char *format
= "rxrange %u %u %u";
1637 int i
, validArgumentCount
= 0;
1640 if (isEmpty(cmdline
)) {
1641 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
, NULL
);
1642 } else if (strcasecmp(cmdline
, "reset") == 0) {
1643 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1647 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1648 int rangeMin
= PWM_PULSE_MIN
, rangeMax
= PWM_PULSE_MAX
;
1652 rangeMin
= atoi(ptr
);
1653 validArgumentCount
++;
1658 rangeMax
= atoi(ptr
);
1659 validArgumentCount
++;
1662 if (validArgumentCount
!= 2) {
1663 cliShowParseError();
1664 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1665 cliShowParseError();
1667 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1668 channelRangeConfig
->min
= rangeMin
;
1669 channelRangeConfig
->max
= rangeMax
;
1670 cliDumpPrintLinef(0, false, format
,
1672 channelRangeConfig
->min
,
1673 channelRangeConfig
->max
1678 cliShowArgumentRangeError("CHANNEL", 0, NON_AUX_CHANNEL_COUNT
- 1);
1683 #ifdef USE_LED_STRIP_STATUS_MODE
1684 static void printLed(dumpFlags_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
, const char *headingStr
)
1686 const char *format
= "led %u %s";
1687 char ledConfigBuffer
[20];
1688 char ledConfigDefaultBuffer
[20];
1689 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1690 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1691 ledConfig_t ledConfig
= ledConfigs
[i
];
1692 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1693 bool equalsDefault
= false;
1694 if (defaultLedConfigs
) {
1695 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1696 equalsDefault
= ledConfig
== ledConfigDefault
;
1697 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1698 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1699 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1701 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1705 static void cliLed(char *cmdline
)
1707 const char *format
= "led %u %s";
1708 char ledConfigBuffer
[20];
1712 if (isEmpty(cmdline
)) {
1713 printLed(DUMP_MASTER
, ledStripStatusModeConfig()->ledConfigs
, NULL
, NULL
);
1717 if (i
< LED_MAX_STRIP_LENGTH
) {
1718 ptr
= nextArg(cmdline
);
1719 if (parseLedStripConfig(i
, ptr
)) {
1720 generateLedConfig((ledConfig_t
*)&ledStripStatusModeConfig()->ledConfigs
[i
], ledConfigBuffer
, sizeof(ledConfigBuffer
));
1721 cliDumpPrintLinef(0, false, format
, i
, ledConfigBuffer
);
1723 cliShowParseError();
1726 cliShowArgumentRangeError("INDEX", 0, LED_MAX_STRIP_LENGTH
- 1);
1731 static void printColor(dumpFlags_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
, const char *headingStr
)
1733 const char *format
= "color %u %d,%u,%u";
1734 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1735 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1736 const hsvColor_t
*color
= &colors
[i
];
1737 bool equalsDefault
= false;
1738 if (defaultColors
) {
1739 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1740 equalsDefault
= !memcmp(color
, colorDefault
, sizeof(*color
));
1741 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1742 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1744 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1748 static void cliColor(char *cmdline
)
1750 const char *format
= "color %u %d,%u,%u";
1751 if (isEmpty(cmdline
)) {
1752 printColor(DUMP_MASTER
, ledStripStatusModeConfig()->colors
, NULL
, NULL
);
1754 const char *ptr
= cmdline
;
1755 const int i
= atoi(ptr
);
1756 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1757 ptr
= nextArg(cmdline
);
1758 if (parseColor(i
, ptr
)) {
1759 const hsvColor_t
*color
= &ledStripStatusModeConfig()->colors
[i
];
1760 cliDumpPrintLinef(0, false, format
, i
, color
->h
, color
->s
, color
->v
);
1762 cliShowParseError();
1765 cliShowArgumentRangeError("INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1770 static void printModeColor(dumpFlags_t dumpMask
, const ledStripStatusModeConfig_t
*ledStripStatusModeConfig
, const ledStripStatusModeConfig_t
*defaultLedStripConfig
, const char *headingStr
)
1772 const char *format
= "mode_color %u %u %u";
1773 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1774 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1775 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1776 int colorIndex
= ledStripStatusModeConfig
->modeColors
[i
].color
[j
];
1777 bool equalsDefault
= false;
1778 if (defaultLedStripConfig
) {
1779 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1780 equalsDefault
= colorIndex
== colorIndexDefault
;
1781 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1782 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1784 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1788 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1789 const int colorIndex
= ledStripStatusModeConfig
->specialColors
.color
[j
];
1790 bool equalsDefault
= false;
1791 if (defaultLedStripConfig
) {
1792 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1793 equalsDefault
= colorIndex
== colorIndexDefault
;
1794 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1795 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1797 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1800 const int ledStripAuxChannel
= ledStripStatusModeConfig
->ledstrip_aux_channel
;
1801 bool equalsDefault
= false;
1802 if (defaultLedStripConfig
) {
1803 const int ledStripAuxChannelDefault
= defaultLedStripConfig
->ledstrip_aux_channel
;
1804 equalsDefault
= ledStripAuxChannel
== ledStripAuxChannelDefault
;
1805 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1806 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannelDefault
);
1808 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannel
);
1811 static void cliModeColor(char *cmdline
)
1813 if (isEmpty(cmdline
)) {
1814 printModeColor(DUMP_MASTER
, ledStripStatusModeConfig(), NULL
, NULL
);
1816 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1817 int args
[ARGS_COUNT
];
1820 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1821 while (ptr
&& argNo
< ARGS_COUNT
) {
1822 args
[argNo
++] = atoi(ptr
);
1823 ptr
= strtok_r(NULL
, " ", &saveptr
);
1826 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1827 cliShowParseError();
1831 int modeIdx
= args
[MODE
];
1832 int funIdx
= args
[FUNCTION
];
1833 int color
= args
[COLOR
];
1834 if (!setModeColor(modeIdx
, funIdx
, color
)) {
1835 cliShowParseError();
1838 // values are validated
1839 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1845 static void printServo(dumpFlags_t dumpMask
, const servoParam_t
*servoParams
, const servoParam_t
*defaultServoParams
, const char *headingStr
)
1847 // print out servo settings
1848 const char *format
= "servo %u %d %d %d %d %d";
1849 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1850 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1851 const servoParam_t
*servoConf
= &servoParams
[i
];
1852 bool equalsDefault
= false;
1853 if (defaultServoParams
) {
1854 const servoParam_t
*defaultServoConf
= &defaultServoParams
[i
];
1855 equalsDefault
= !memcmp(servoConf
, defaultServoConf
, sizeof(*servoConf
));
1856 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
1857 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1859 defaultServoConf
->min
,
1860 defaultServoConf
->max
,
1861 defaultServoConf
->middle
,
1862 defaultServoConf
->rate
,
1863 defaultServoConf
->forwardFromChannel
1866 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1872 servoConf
->forwardFromChannel
1875 // print servo directions
1876 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1877 const char *format
= "smix reverse %d %d r";
1878 const servoParam_t
*servoConf
= &servoParams
[i
];
1879 const servoParam_t
*servoConfDefault
= &defaultServoParams
[i
];
1880 if (defaultServoParams
) {
1881 bool equalsDefault
= servoConf
->reversedSources
== servoConfDefault
->reversedSources
;
1882 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1883 equalsDefault
= ~(servoConf
->reversedSources
^ servoConfDefault
->reversedSources
) & (1 << channel
);
1884 if (servoConfDefault
->reversedSources
& (1 << channel
)) {
1885 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1887 if (servoConf
->reversedSources
& (1 << channel
)) {
1888 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1892 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1893 if (servoConf
->reversedSources
& (1 << channel
)) {
1894 cliDumpPrintLinef(dumpMask
, true, format
, i
, channel
);
1901 static void cliServo(char *cmdline
)
1903 const char *format
= "servo %u %d %d %d %d %d";
1904 enum { SERVO_ARGUMENT_COUNT
= 6 };
1905 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1907 servoParam_t
*servo
;
1912 if (isEmpty(cmdline
)) {
1913 printServo(DUMP_MASTER
, servoParams(0), NULL
, NULL
);
1915 int validArgumentCount
= 0;
1919 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1921 // If command line doesn't fit the format, don't modify the config
1923 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1924 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1925 cliShowParseError();
1929 arguments
[validArgumentCount
++] = atoi(ptr
);
1933 } while (*ptr
>= '0' && *ptr
<= '9');
1934 } else if (*ptr
== ' ') {
1937 cliShowParseError();
1942 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
, FORWARD
};
1944 i
= arguments
[INDEX
];
1946 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1947 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1948 cliShowParseError();
1952 servo
= servoParamsMutable(i
);
1955 arguments
[MIN
] < PWM_PULSE_MIN
|| arguments
[MIN
] > PWM_PULSE_MAX
||
1956 arguments
[MAX
] < PWM_PULSE_MIN
|| arguments
[MAX
] > PWM_PULSE_MAX
||
1957 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
1958 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
1959 arguments
[RATE
] < -100 || arguments
[RATE
] > 100 ||
1960 arguments
[FORWARD
] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1962 cliShowParseError();
1966 servo
->min
= arguments
[MIN
];
1967 servo
->max
= arguments
[MAX
];
1968 servo
->middle
= arguments
[MIDDLE
];
1969 servo
->rate
= arguments
[RATE
];
1970 servo
->forwardFromChannel
= arguments
[FORWARD
];
1972 cliDumpPrintLinef(0, false, format
,
1978 servo
->forwardFromChannel
1986 static void printServoMix(dumpFlags_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
, const char *headingStr
)
1988 const char *format
= "smix %d %d %d %d %d %d %d %d";
1989 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
1990 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1991 const servoMixer_t customServoMixer
= customServoMixers
[i
];
1992 if (customServoMixer
.rate
== 0) {
1996 bool equalsDefault
= false;
1997 if (defaultCustomServoMixers
) {
1998 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
1999 equalsDefault
= !memcmp(&customServoMixer
, &customServoMixerDefault
, sizeof(customServoMixer
));
2001 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2002 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2004 customServoMixerDefault
.targetChannel
,
2005 customServoMixerDefault
.inputSource
,
2006 customServoMixerDefault
.rate
,
2007 customServoMixerDefault
.speed
,
2008 customServoMixerDefault
.min
,
2009 customServoMixerDefault
.max
,
2010 customServoMixerDefault
.box
2013 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2015 customServoMixer
.targetChannel
,
2016 customServoMixer
.inputSource
,
2017 customServoMixer
.rate
,
2018 customServoMixer
.speed
,
2019 customServoMixer
.min
,
2020 customServoMixer
.max
,
2021 customServoMixer
.box
2026 static void cliServoMix(char *cmdline
)
2028 int args
[8], check
= 0;
2029 int len
= strlen(cmdline
);
2032 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
, NULL
);
2033 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
2034 // erase custom mixer
2035 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2036 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
2037 servoParamsMutable(i
)->reversedSources
= 0;
2039 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
2040 const char *ptr
= nextArg(cmdline
);
2043 for (uint32_t i
= 0; ; i
++) {
2044 if (mixerNames
[i
] == NULL
) {
2045 cliPrintErrorLinef("INVALID NAME");
2048 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
2049 servoMixerLoadMix(i
);
2050 cliPrintLinef("Loaded %s", mixerNames
[i
]);
2056 } else if (strncasecmp(cmdline
, "reverse", 7) == 0) {
2057 enum {SERVO
= 0, INPUT
, REVERSE
, ARGS_COUNT
};
2058 char *ptr
= strchr(cmdline
, ' ');
2062 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
2063 cliPrintf("\ti%d", inputSource
);
2066 for (uint32_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
2067 cliPrintf("%d", servoIndex
);
2068 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++) {
2069 cliPrintf("\t%s ", (servoParams(servoIndex
)->reversedSources
& (1 << inputSource
)) ? "r" : "n");
2077 ptr
= strtok_r(ptr
, " ", &saveptr
);
2078 while (ptr
!= NULL
&& check
< ARGS_COUNT
- 1) {
2079 args
[check
++] = atoi(ptr
);
2080 ptr
= strtok_r(NULL
, " ", &saveptr
);
2083 if (ptr
== NULL
|| check
!= ARGS_COUNT
- 1) {
2084 cliShowParseError();
2088 if (args
[SERVO
] >= 0 && args
[SERVO
] < MAX_SUPPORTED_SERVOS
2089 && args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
2090 && (*ptr
== 'r' || *ptr
== 'n')) {
2092 servoParamsMutable(args
[SERVO
])->reversedSources
|= 1 << args
[INPUT
];
2094 servoParamsMutable(args
[SERVO
])->reversedSources
&= ~(1 << args
[INPUT
]);
2097 cliShowParseError();
2101 cliServoMix("reverse");
2103 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, MIN
, MAX
, BOX
, ARGS_COUNT
};
2105 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
2106 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
2107 args
[check
++] = atoi(ptr
);
2108 ptr
= strtok_r(NULL
, " ", &saveptr
);
2111 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
2112 cliShowParseError();
2116 int32_t i
= args
[RULE
];
2117 if (i
>= 0 && i
< MAX_SERVO_RULES
&&
2118 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
2119 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
2120 args
[RATE
] >= -100 && args
[RATE
] <= 100 &&
2121 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
2122 args
[MIN
] >= 0 && args
[MIN
] <= 100 &&
2123 args
[MAX
] >= 0 && args
[MAX
] <= 100 && args
[MIN
] < args
[MAX
] &&
2124 args
[BOX
] >= 0 && args
[BOX
] <= MAX_SERVO_BOXES
) {
2125 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
2126 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
2127 customServoMixersMutable(i
)->rate
= args
[RATE
];
2128 customServoMixersMutable(i
)->speed
= args
[SPEED
];
2129 customServoMixersMutable(i
)->min
= args
[MIN
];
2130 customServoMixersMutable(i
)->max
= args
[MAX
];
2131 customServoMixersMutable(i
)->box
= args
[BOX
];
2134 cliShowParseError();
2142 static void cliWriteBytes(const uint8_t *buffer
, int count
)
2151 static void cliSdInfo(char *cmdline
)
2155 cliPrint("SD card: ");
2157 if (!sdcard_isInserted()) {
2158 cliPrintLine("None inserted");
2162 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2163 cliPrintLine("Startup failed");
2167 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
2169 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2170 metadata
->manufacturerID
,
2171 metadata
->numBlocks
/ 2, /* One block is half a kB */
2172 metadata
->productionMonth
,
2173 metadata
->productionYear
,
2174 metadata
->productRevisionMajor
,
2175 metadata
->productRevisionMinor
2178 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
2180 cliPrint("'\r\n" "Filesystem: ");
2182 switch (afatfs_getFilesystemState()) {
2183 case AFATFS_FILESYSTEM_STATE_READY
:
2186 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
2187 cliPrint("Initializing");
2189 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
2190 case AFATFS_FILESYSTEM_STATE_FATAL
:
2193 switch (afatfs_getLastError()) {
2194 case AFATFS_ERROR_BAD_MBR
:
2195 cliPrint(" - no FAT MBR partitions");
2197 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
2198 cliPrint(" - bad FAT header");
2200 case AFATFS_ERROR_GENERIC
:
2201 case AFATFS_ERROR_NONE
:
2202 ; // Nothing more detailed to print
2212 #ifdef USE_FLASH_CHIP
2214 static void cliFlashInfo(char *cmdline
)
2216 const flashGeometry_t
*layout
= flashGetGeometry();
2220 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2221 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
);
2223 for (uint8_t index
= 0; index
< FLASH_MAX_PARTITIONS
; index
++) {
2224 const flashPartition_t
*partition
;
2226 cliPrintLine("Paritions:");
2228 partition
= flashPartitionFindByIndex(index
);
2232 cliPrintLinef(" %d: %s %u %u", index
, flashPartitionGetTypeName(partition
->type
), partition
->startSector
, partition
->endSector
);
2235 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
2237 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2238 FLASH_PARTITION_SECTOR_COUNT(flashPartition
) * layout
->sectorSize
,
2245 static void cliFlashErase(char *cmdline
)
2249 if (!flashfsIsSupported()) {
2255 cliPrintLine("Erasing, please wait ... ");
2257 cliPrintLine("Erasing,");
2260 bufWriterFlush(cliWriter
);
2261 flashfsEraseCompletely();
2263 while (!flashfsIsReady()) {
2271 bufWriterFlush(cliWriter
);
2275 beeper(BEEPER_BLACKBOX_ERASE
);
2277 cliPrintLine("Done.");
2280 #ifdef USE_FLASH_TOOLS
2282 static void cliFlashVerify(char *cmdline
)
2286 cliPrintLine("Verifying");
2287 if (flashfsVerifyEntireFlash()) {
2288 cliPrintLine("Success");
2290 cliPrintLine("Failed");
2294 static void cliFlashWrite(char *cmdline
)
2296 const uint32_t address
= atoi(cmdline
);
2297 const char *text
= strchr(cmdline
, ' ');
2300 cliShowParseError();
2302 flashfsSeekAbs(address
);
2303 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2306 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2310 static void cliFlashRead(char *cmdline
)
2312 uint32_t address
= atoi(cmdline
);
2314 const char *nextArg
= strchr(cmdline
, ' ');
2317 cliShowParseError();
2319 uint32_t length
= atoi(nextArg
);
2321 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2324 while (length
> 0) {
2325 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2327 for (int i
= 0; i
< bytesRead
; i
++) {
2328 cliWrite(buffer
[i
]);
2331 length
-= bytesRead
;
2332 address
+= bytesRead
;
2334 if (bytesRead
== 0) {
2335 //Assume we reached the end of the volume or something fatal happened
2346 #ifdef USE_VTX_CONTROL
2347 static void printVtx(dumpFlags_t dumpMask
, const vtxConfig_t
*vtxConfig
, const vtxConfig_t
*vtxConfigDefault
, const char *headingStr
)
2349 // print out vtx channel settings
2350 const char *format
= "vtx %u %u %u %u %u %u %u";
2351 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2352 bool equalsDefault
= false;
2353 for (uint32_t i
= 0; i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
; i
++) {
2354 const vtxChannelActivationCondition_t
*cac
= &vtxConfig
->vtxChannelActivationConditions
[i
];
2355 if (vtxConfigDefault
) {
2356 const vtxChannelActivationCondition_t
*cacDefault
= &vtxConfigDefault
->vtxChannelActivationConditions
[i
];
2357 equalsDefault
= !memcmp(cac
, cacDefault
, sizeof(*cac
));
2358 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2359 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2361 cacDefault
->auxChannelIndex
,
2363 cacDefault
->channel
,
2365 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.startStep
),
2366 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.endStep
)
2369 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2371 cac
->auxChannelIndex
,
2375 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2376 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2381 static void cliVtx(char *cmdline
)
2383 const char *format
= "vtx %u %u %u %u %u %u %u";
2387 if (isEmpty(cmdline
)) {
2388 printVtx(DUMP_MASTER
, vtxConfig(), NULL
, NULL
);
2392 if (i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
) {
2393 vtxChannelActivationCondition_t
*cac
= &vtxConfigMutable()->vtxChannelActivationConditions
[i
];
2394 uint8_t validArgumentCount
= 0;
2398 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
2399 cac
->auxChannelIndex
= val
;
2400 validArgumentCount
++;
2406 // FIXME Use VTX API to get max
2407 if (val
>= 0 && val
<= VTX_SETTINGS_MAX_BAND
) {
2409 validArgumentCount
++;
2415 // FIXME Use VTX API to get max
2416 if (val
>= 0 && val
<= VTX_SETTINGS_MAX_CHANNEL
) {
2418 validArgumentCount
++;
2424 // FIXME Use VTX API to get max
2425 if (val
>= 0 && val
< VTX_SETTINGS_POWER_COUNT
) {
2427 validArgumentCount
++;
2430 ptr
= processChannelRangeArgs(ptr
, &cac
->range
, &validArgumentCount
);
2432 if (validArgumentCount
!= 6) {
2433 memset(cac
, 0, sizeof(vtxChannelActivationCondition_t
));
2434 cliShowParseError();
2436 cliDumpPrintLinef(0, false, format
,
2438 cac
->auxChannelIndex
,
2442 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2443 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2447 cliShowArgumentRangeError("INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
- 1);
2452 #endif // VTX_CONTROL
2454 #ifdef USE_VTX_TABLE
2456 static char *formatVtxTableBandFrequency(const uint16_t *frequency
, int channels
)
2458 static char freqbuf
[5 * VTX_TABLE_MAX_CHANNELS
+ 1];
2459 char freqtmp
[5 + 1];
2461 for (int channel
= 0; channel
< channels
; channel
++) {
2462 tfp_sprintf(freqtmp
, " %4d", frequency
[channel
]);
2463 strcat(freqbuf
, freqtmp
);
2468 static const char *printVtxTableBand(dumpFlags_t dumpMask
, int band
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2470 char *fmt
= "vtxtable band %d %s %c%s";
2471 bool equalsDefault
= false;
2473 if (defaultConfig
) {
2474 equalsDefault
= true;
2475 if (strcasecmp(currentConfig
->bandNames
[band
], defaultConfig
->bandNames
[band
])) {
2476 equalsDefault
= false;
2478 if (currentConfig
->bandLetters
[band
] != defaultConfig
->bandLetters
[band
]) {
2479 equalsDefault
= false;
2481 for (int channel
= 0; channel
< VTX_TABLE_MAX_CHANNELS
; channel
++) {
2482 if (currentConfig
->frequency
[band
][channel
] != defaultConfig
->frequency
[band
][channel
]) {
2483 equalsDefault
= false;
2486 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2487 char *freqbuf
= formatVtxTableBandFrequency(defaultConfig
->frequency
[band
], defaultConfig
->channels
);
2488 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, band
+ 1, defaultConfig
->bandNames
[band
], defaultConfig
->bandLetters
[band
], freqbuf
);
2491 char *freqbuf
= formatVtxTableBandFrequency(currentConfig
->frequency
[band
], currentConfig
->channels
);
2492 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, band
+ 1, currentConfig
->bandNames
[band
], currentConfig
->bandLetters
[band
], freqbuf
);
2496 static char *formatVtxTablePowerValues(const uint16_t *levels
, int count
)
2498 // (max 4 digit + 1 space) per level
2499 static char pwrbuf
[5 * VTX_TABLE_MAX_POWER_LEVELS
+ 1];
2502 for (int pwrindex
= 0; pwrindex
< count
; pwrindex
++) {
2503 tfp_sprintf(pwrtmp
, " %d", levels
[pwrindex
]);
2504 strcat(pwrbuf
, pwrtmp
);
2509 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2511 char *fmt
= "vtxtable powervalues %s";
2512 bool equalsDefault
= false;
2513 if (defaultConfig
) {
2514 equalsDefault
= true;
2515 for (int pwrindex
= 0; pwrindex
< VTX_TABLE_MAX_POWER_LEVELS
; pwrindex
++) {
2516 if (defaultConfig
->powerValues
[pwrindex
] != currentConfig
->powerValues
[pwrindex
]) {
2517 equalsDefault
= false;
2520 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2521 char *pwrbuf
= formatVtxTablePowerValues(defaultConfig
->powerValues
, VTX_TABLE_MAX_POWER_LEVELS
);
2522 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2525 char *pwrbuf
= formatVtxTablePowerValues(currentConfig
->powerValues
, currentConfig
->powerLevels
);
2526 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2530 static char *formatVtxTablePowerLabels(const char labels
[VTX_TABLE_MAX_POWER_LEVELS
][VTX_TABLE_POWER_LABEL_LENGTH
+ 1], int count
)
2532 static char pwrbuf
[(VTX_TABLE_POWER_LABEL_LENGTH
+ 1) * VTX_TABLE_MAX_POWER_LEVELS
+ 1];
2533 char pwrtmp
[(VTX_TABLE_POWER_LABEL_LENGTH
+ 1) + 1];
2535 for (int pwrindex
= 0; pwrindex
< count
; pwrindex
++) {
2536 strcat(pwrbuf
, " ");
2537 strcpy(pwrtmp
, labels
[pwrindex
]);
2538 // trim trailing space
2540 while ((sp
= strchr(pwrtmp
, ' '))) {
2543 strcat(pwrbuf
, pwrtmp
);
2548 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2550 char *fmt
= "vtxtable powerlabels%s";
2551 bool equalsDefault
= false;
2552 if (defaultConfig
) {
2553 equalsDefault
= true;
2554 for (int pwrindex
= 0; pwrindex
< VTX_TABLE_MAX_POWER_LEVELS
; pwrindex
++) {
2555 if (strcasecmp(defaultConfig
->powerLabels
[pwrindex
], currentConfig
->powerLabels
[pwrindex
])) {
2556 equalsDefault
= false;
2559 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2560 char *pwrbuf
= formatVtxTablePowerLabels(defaultConfig
->powerLabels
, VTX_TABLE_MAX_POWER_LEVELS
);
2561 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2564 char *pwrbuf
= formatVtxTablePowerLabels(currentConfig
->powerLabels
, currentConfig
->powerLevels
);
2565 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, pwrbuf
);
2569 static void printVtxTable(dumpFlags_t dumpMask
, const vtxTableConfig_t
*currentConfig
, const vtxTableConfig_t
*defaultConfig
, const char *headingStr
)
2574 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2577 equalsDefault
= false;
2578 fmt
= "vtxtable bands %d";
2579 if (defaultConfig
) {
2580 equalsDefault
= (defaultConfig
->bands
== currentConfig
->bands
);
2581 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2582 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->bands
);
2584 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->bands
);
2587 equalsDefault
= false;
2588 fmt
= "vtxtable channels %d";
2589 if (defaultConfig
) {
2590 equalsDefault
= (defaultConfig
->channels
== currentConfig
->channels
);
2591 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2592 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->channels
);
2594 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->channels
);
2598 for (int band
= 0; band
< currentConfig
->bands
; band
++) {
2599 headingStr
= printVtxTableBand(dumpMask
, band
, currentConfig
, defaultConfig
, headingStr
);
2604 equalsDefault
= false;
2605 fmt
= "vtxtable powerlevels %d";
2606 if (defaultConfig
) {
2607 equalsDefault
= (defaultConfig
->powerLevels
== currentConfig
->powerLevels
);
2608 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2609 cliDefaultPrintLinef(dumpMask
, equalsDefault
, fmt
, defaultConfig
->powerLevels
);
2611 cliDumpPrintLinef(dumpMask
, equalsDefault
, fmt
, currentConfig
->powerLevels
);
2616 headingStr
= printVtxTablePowerValues(dumpMask
, currentConfig
, defaultConfig
, headingStr
);
2617 headingStr
= printVtxTablePowerLabels(dumpMask
, currentConfig
, defaultConfig
, headingStr
);
2620 static void cliVtxTable(char *cmdline
)
2625 // Band number or nothing
2626 tok
= strtok_r(cmdline
, " ", &saveptr
);
2629 printVtxTable(DUMP_MASTER
| HIDE_UNUSED
, vtxTableConfigMutable(), NULL
, NULL
);
2633 if (strcasecmp(tok
, "bands") == 0) {
2634 tok
= strtok_r(NULL
, " ", &saveptr
);
2635 int bands
= atoi(tok
);
2636 if (bands
< 0 || bands
> VTX_TABLE_MAX_BANDS
) {
2637 cliPrintErrorLinef("INVALID BAND COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_BANDS
);
2640 if (bands
< vtxTableConfigMutable()->bands
) {
2641 for (int i
= bands
; i
< vtxTableConfigMutable()->bands
; i
++) {
2642 vtxTableConfigClearBand(vtxTableConfigMutable(), i
);
2645 vtxTableConfigMutable()->bands
= bands
;
2647 } else if (strcasecmp(tok
, "channels") == 0) {
2648 tok
= strtok_r(NULL
, " ", &saveptr
);
2650 int channels
= atoi(tok
);
2651 if (channels
< 0 || channels
> VTX_TABLE_MAX_BANDS
) {
2652 cliPrintErrorLinef("INVALID CHANNEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_CHANNELS
);
2655 if (channels
< vtxTableConfigMutable()->channels
) {
2656 for (int i
= 0; i
< VTX_TABLE_MAX_BANDS
; i
++) {
2657 vtxTableConfigClearChannels(vtxTableConfigMutable(), i
, channels
);
2660 vtxTableConfigMutable()->channels
= channels
;
2662 } else if (strcasecmp(tok
, "powerlevels") == 0) {
2663 // Number of power levels
2664 tok
= strtok_r(NULL
, " ", &saveptr
);
2666 int levels
= atoi(tok
);
2667 if (levels
< 0 || levels
> VTX_TABLE_MAX_POWER_LEVELS
) {
2668 cliPrintErrorLinef("INVALID POWER LEVEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_POWER_LEVELS
);
2670 if (levels
< vtxTableConfigMutable()->powerLevels
) {
2671 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels
);
2672 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels
);
2674 vtxTableConfigMutable()->powerLevels
= levels
;
2677 // XXX Show current level count?
2681 } else if (strcasecmp(tok
, "powervalues") == 0) {
2683 uint16_t power
[VTX_TABLE_MAX_POWER_LEVELS
];
2685 int levels
= vtxTableConfigMutable()->powerLevels
;
2687 memset(power
, 0, sizeof(power
));
2689 for (count
= 0; count
< levels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); count
++) {
2690 int value
= atoi(tok
);
2691 power
[count
] = value
;
2694 // Check remaining tokens
2696 if (count
< levels
) {
2697 cliPrintErrorLinef("NOT ENOUGH VALUES (EXPECTED %d)", levels
);
2699 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
2700 cliPrintErrorLinef("TOO MANY VALUES (EXPECTED %d)", levels
);
2704 for (int i
= 0; i
< VTX_TABLE_MAX_POWER_LEVELS
; i
++) {
2705 vtxTableConfigMutable()->powerValues
[i
] = power
[i
];
2708 } else if (strcasecmp(tok
, "powerlabels") == 0) {
2710 char label
[VTX_TABLE_MAX_POWER_LEVELS
][VTX_TABLE_POWER_LABEL_LENGTH
+ 1];
2711 int levels
= vtxTableConfigMutable()->powerLevels
;
2713 for (count
= 0; count
< levels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); count
++) {
2714 strncpy(label
[count
], tok
, VTX_TABLE_POWER_LABEL_LENGTH
);
2715 for (unsigned i
= 0; i
< strlen(label
[count
]); i
++) {
2716 label
[count
][i
] = toupper(label
[count
][i
]);
2720 // Check remaining tokens
2722 if (count
< levels
) {
2723 cliPrintErrorLinef("NOT ENOUGH LABELS (EXPECTED %d)", levels
);
2725 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
2726 cliPrintErrorLinef("TOO MANY LABELS (EXPECTED %d)", levels
);
2730 for (int i
= 0; i
< count
; i
++) {
2731 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels
[i
], label
[i
], VTX_TABLE_POWER_LABEL_LENGTH
);
2733 } else if (strcasecmp(tok
, "band") == 0) {
2735 int bands
= vtxTableConfigMutable()->bands
;
2737 tok
= strtok_r(NULL
, " ", &saveptr
);
2742 int band
= atoi(tok
);
2745 if (band
< 0 || band
>= bands
) {
2746 cliPrintErrorLinef("INVALID BAND NUMBER %s (EXPECTED 1-%d)", tok
, bands
);
2751 tok
= strtok_r(NULL
, " ", &saveptr
);
2757 char bandname
[VTX_TABLE_BAND_NAME_LENGTH
+ 1];
2758 memset(bandname
, 0, VTX_TABLE_BAND_NAME_LENGTH
+ 1);
2759 strncpy(bandname
, tok
, VTX_TABLE_BAND_NAME_LENGTH
);
2760 for (unsigned i
= 0; i
< strlen(bandname
); i
++) {
2761 bandname
[i
] = toupper(bandname
[i
]);
2765 tok
= strtok_r(NULL
, " ", &saveptr
);
2771 char bandletter
= toupper(tok
[0]);
2773 uint16_t bandfreq
[VTX_TABLE_MAX_CHANNELS
];
2775 int channels
= vtxTableConfigMutable()->channels
;
2777 for (channel
= 0; channel
< channels
&& (tok
= strtok_r(NULL
, " ", &saveptr
)); channel
++) {
2778 int freq
= atoi(tok
);
2780 cliPrintErrorLinef("INVALID FREQUENCY %s", tok
);
2783 bandfreq
[channel
] = freq
;
2786 if (channel
< channels
) {
2787 cliPrintErrorLinef("NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels
);
2789 } else if ((tok
= strtok_r(NULL
, " ", &saveptr
))) {
2790 cliPrintErrorLinef("TOO MANY FREQUENCIES (EXPECTED %d)", channels
);
2794 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames
[band
], bandname
, VTX_TABLE_BAND_NAME_LENGTH
);
2795 vtxTableConfigMutable()->bandLetters
[band
] = bandletter
;
2797 for (int i
= 0; i
< channel
; i
++) {
2798 vtxTableConfigMutable()->frequency
[band
][i
] = bandfreq
[i
];
2802 cliPrintErrorLinef("INVALID SUBCOMMAND %s", tok
);
2805 #endif // USE_VTX_TABLE
2807 static void printName(dumpFlags_t dumpMask
, const pilotConfig_t
*pilotConfig
)
2809 const bool equalsDefault
= strlen(pilotConfig
->name
) == 0;
2810 cliDumpPrintLinef(dumpMask
, equalsDefault
, "\r\n# name: %s", equalsDefault
? emptyName
: pilotConfig
->name
);
2813 static void cliName(char *cmdline
)
2815 const unsigned int len
= strlen(cmdline
);
2816 bool updated
= false;
2818 memset(pilotConfigMutable()->name
, 0, ARRAYLEN(pilotConfig()->name
));
2819 if (strncmp(cmdline
, emptyName
, len
)) {
2820 strncpy(pilotConfigMutable()->name
, cmdline
, MIN(len
, MAX_NAME_LENGTH
));
2824 printName(DUMP_MASTER
, pilotConfig());
2826 cliPrintLine("###WARNING: This command will be removed. Use 'set name = ' instead.###");
2830 #if defined(USE_BOARD_INFO)
2832 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
2834 static void printBoardName(dumpFlags_t dumpMask
)
2836 if (!(dumpMask
& DO_DIFF
) || strlen(getBoardName())) {
2837 cliPrintLinef("board_name %s", getBoardName());
2841 static void cliBoardName(char *cmdline
)
2843 const unsigned int len
= strlen(cmdline
);
2844 if (len
> 0 && boardInformationIsSet() && (len
!= strlen(getBoardName()) || strncmp(getBoardName(), cmdline
, len
))) {
2845 cliPrintErrorLinef(ERROR_MESSAGE
, "BOARD_NAME", getBoardName());
2848 setBoardName(cmdline
);
2849 boardInformationUpdated
= true;
2851 printBoardName(DUMP_ALL
);
2855 static void printManufacturerId(dumpFlags_t dumpMask
)
2857 if (!(dumpMask
& DO_DIFF
) || strlen(getManufacturerId())) {
2858 cliPrintLinef("manufacturer_id %s", getManufacturerId());
2862 static void cliManufacturerId(char *cmdline
)
2864 const unsigned int len
= strlen(cmdline
);
2865 if (len
> 0 && boardInformationIsSet() && (len
!= strlen(getManufacturerId()) || strncmp(getManufacturerId(), cmdline
, len
))) {
2866 cliPrintErrorLinef(ERROR_MESSAGE
, "MANUFACTURER_ID", getManufacturerId());
2869 setManufacturerId(cmdline
);
2870 boardInformationUpdated
= true;
2872 printManufacturerId(DUMP_ALL
);
2876 #if defined(USE_SIGNATURE)
2877 static void writeSignature(char *signatureStr
, uint8_t *signature
)
2879 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
2880 tfp_sprintf(&signatureStr
[2 * i
], "%02x", signature
[i
]);
2884 static void cliSignature(char *cmdline
)
2886 const int len
= strlen(cmdline
);
2888 uint8_t signature
[SIGNATURE_LENGTH
] = {0};
2890 if (len
!= 2 * SIGNATURE_LENGTH
) {
2891 cliPrintErrorLinef("INVALID LENGTH: %d (EXPECTED: %d)", len
, 2 * SIGNATURE_LENGTH
);
2896 #define BLOCK_SIZE 2
2897 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
2898 char temp
[BLOCK_SIZE
+ 1];
2899 strncpy(temp
, &cmdline
[i
* BLOCK_SIZE
], BLOCK_SIZE
);
2900 temp
[BLOCK_SIZE
] = '\0';
2902 unsigned result
= strtoul(temp
, &end
, 16);
2903 if (end
== &temp
[BLOCK_SIZE
]) {
2904 signature
[i
] = result
;
2906 cliPrintErrorLinef("INVALID CHARACTER FOUND: %c", end
[0]);
2914 char signatureStr
[SIGNATURE_LENGTH
* 2 + 1] = {0};
2915 if (len
> 0 && signatureIsSet() && memcmp(signature
, getSignature(), SIGNATURE_LENGTH
)) {
2916 writeSignature(signatureStr
, getSignature());
2917 cliPrintErrorLinef(ERROR_MESSAGE
, "SIGNATURE", signatureStr
);
2920 setSignature(signature
);
2922 signatureUpdated
= true;
2924 writeSignature(signatureStr
, getSignature());
2925 } else if (signatureUpdated
|| signatureIsSet()) {
2926 writeSignature(signatureStr
, getSignature());
2929 cliPrintLinef("signature %s", signatureStr
);
2934 #undef ERROR_MESSAGE
2936 #endif // USE_BOARD_INFO
2938 static void cliMcuId(char *cmdline
)
2942 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0
, U_ID_1
, U_ID_2
);
2945 static uint32_t getFeatureMask(const uint32_t featureMask
)
2947 if (featureMaskIsCopied
) {
2948 return featureMaskCopy
;
2954 static void printFeature(dumpFlags_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
, const char *headingStr
)
2956 const uint32_t mask
= getFeatureMask(featureConfig
->enabledFeatures
);
2957 const uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
2958 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
2959 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // disabled features first
2960 if (strcmp(featureNames
[i
], emptyString
) != 0) { //Skip unused
2961 const char *format
= "feature -%s";
2962 const bool equalsDefault
= (~defaultMask
| mask
) & (1 << i
);
2963 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2964 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2965 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, featureNames
[i
]);
2968 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // enabled features
2969 if (strcmp(featureNames
[i
], emptyString
) != 0) { //Skip unused
2970 const char *format
= "feature %s";
2971 if (defaultMask
& (1 << i
)) {
2972 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2974 if (mask
& (1 << i
)) {
2975 const bool equalsDefault
= (defaultMask
| ~mask
) & (1 << i
);
2976 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
2977 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, featureNames
[i
]);
2983 static void cliFeature(char *cmdline
)
2985 uint32_t len
= strlen(cmdline
);
2986 const uint32_t mask
= getFeatureMask(featureMask());
2988 cliPrint("Enabled: ");
2989 for (uint32_t i
= 0; ; i
++) {
2990 if (featureNames
[i
] == NULL
) {
2993 if (mask
& (1 << i
)) {
2994 cliPrintf("%s ", featureNames
[i
]);
2998 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2999 cliPrint("Available:");
3000 for (uint32_t i
= 0; ; i
++) {
3001 if (featureNames
[i
] == NULL
)
3003 if (strcmp(featureNames
[i
], emptyString
) != 0) //Skip unused
3004 cliPrintf(" %s", featureNames
[i
]);
3009 if (!featureMaskIsCopied
) {
3010 featureMaskCopy
= featureMask();
3011 featureMaskIsCopied
= true;
3015 bool remove
= false;
3016 if (cmdline
[0] == '-') {
3019 cmdline
++; // skip over -
3023 for (uint32_t i
= 0; ; i
++) {
3024 if (featureNames
[i
] == NULL
) {
3025 cliPrintErrorLinef("INVALID NAME");
3029 if (strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
3032 if (feature
& FEATURE_GPS
) {
3033 cliPrintLine("unavailable");
3037 #ifndef USE_RANGEFINDER
3038 if (feature
& FEATURE_RANGEFINDER
) {
3039 cliPrintLine("unavailable");
3044 featureClear(feature
, &featureMaskCopy
);
3045 cliPrint("Disabled");
3047 featureSet(feature
, &featureMaskCopy
);
3048 cliPrint("Enabled");
3050 cliPrintLinef(" %s", featureNames
[i
]);
3057 #if defined(USE_BEEPER)
3058 static void printBeeper(dumpFlags_t dumpMask
, const uint32_t offFlags
, const uint32_t offFlagsDefault
, const char *name
, const uint32_t allowedFlags
, const char *headingStr
)
3060 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3061 const uint8_t beeperCount
= beeperTableEntryCount();
3062 for (int32_t i
= 0; i
< beeperCount
- 1; i
++) {
3063 if (beeperModeMaskForTableIndex(i
) & allowedFlags
) {
3064 const char *formatOff
= "%s -%s";
3065 const char *formatOn
= "%s %s";
3066 const uint32_t beeperModeMask
= beeperModeMaskForTableIndex(i
);
3067 cliDefaultPrintLinef(dumpMask
, ~(offFlags
^ offFlagsDefault
) & beeperModeMask
, offFlags
& beeperModeMask
? formatOn
: formatOff
, name
, beeperNameForTableIndex(i
));
3068 const bool equalsDefault
= ~(offFlags
^ offFlagsDefault
) & beeperModeMask
;
3069 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3070 cliDumpPrintLinef(dumpMask
, equalsDefault
, offFlags
& beeperModeMask
? formatOff
: formatOn
, name
, beeperNameForTableIndex(i
));
3075 static void processBeeperCommand(char *cmdline
, uint32_t *offFlags
, const uint32_t allowedFlags
)
3077 uint32_t len
= strlen(cmdline
);
3078 uint8_t beeperCount
= beeperTableEntryCount();
3081 cliPrintf("Disabled:");
3082 for (int32_t i
= 0; ; i
++) {
3083 if (i
== beeperCount
- 1) {
3089 if (beeperModeMaskForTableIndex(i
) & *offFlags
)
3090 cliPrintf(" %s", beeperNameForTableIndex(i
));
3093 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3094 cliPrint("Available:");
3095 for (uint32_t i
= 0; i
< beeperCount
; i
++) {
3096 if (beeperModeMaskForTableIndex(i
) & allowedFlags
) {
3097 cliPrintf(" %s", beeperNameForTableIndex(i
));
3102 bool remove
= false;
3103 if (cmdline
[0] == '-') {
3104 remove
= true; // this is for beeper OFF condition
3109 for (uint32_t i
= 0; ; i
++) {
3110 if (i
== beeperCount
) {
3111 cliPrintErrorLinef("INVALID NAME");
3114 if (strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0 && beeperModeMaskForTableIndex(i
) & (allowedFlags
| BEEPER_GET_FLAG(BEEPER_ALL
))) {
3115 if (remove
) { // beeper off
3116 if (i
== BEEPER_ALL
- 1) {
3117 *offFlags
= allowedFlags
;
3119 *offFlags
|= beeperModeMaskForTableIndex(i
);
3121 cliPrint("Disabled");
3124 if (i
== BEEPER_ALL
- 1) {
3127 *offFlags
&= ~beeperModeMaskForTableIndex(i
);
3129 cliPrint("Enabled");
3131 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
3138 #if defined(USE_DSHOT)
3139 static void cliBeacon(char *cmdline
)
3141 processBeeperCommand(cmdline
, &(beeperConfigMutable()->dshotBeaconOffFlags
), DSHOT_BEACON_ALLOWED_MODES
);
3145 static void cliBeeper(char *cmdline
)
3147 processBeeperCommand(cmdline
, &(beeperConfigMutable()->beeper_off_flags
), BEEPER_ALLOWED_MODES
);
3152 void cliRxSpiBind(char *cmdline
){
3154 switch (rxSpiConfig()->rx_spi_protocol
) {
3156 cliPrint("Not supported.");
3158 #if defined(USE_RX_FRSKY_SPI)
3159 #if defined(USE_RX_FRSKY_SPI_D)
3160 case RX_SPI_FRSKY_D
:
3162 #if defined(USE_RX_FRSKY_SPI_X)
3163 case RX_SPI_FRSKY_X
:
3164 case RX_SPI_FRSKY_X_LBT
:
3166 #endif // USE_RX_FRSKY_SPI
3167 #ifdef USE_RX_SFHSS_SPI
3170 #ifdef USE_RX_FLYSKY
3171 case RX_SPI_A7105_FLYSKY
:
3172 case RX_SPI_A7105_FLYSKY_2A
:
3174 #ifdef USE_RX_SPEKTRUM
3175 case RX_SPI_CYRF6936_DSM
:
3177 #if defined(USE_RX_FRSKY_SPI) || defined(USE_RX_SFHSS_SPI) || defined(USE_RX_FLYSKY) || defined(USE_RX_SPEKTRUM)
3179 cliPrint("Binding...");
3186 static void printMap(dumpFlags_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
, const char *headingStr
)
3188 bool equalsDefault
= true;
3190 char bufDefault
[16];
3193 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
3194 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3195 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3196 if (defaultRxConfig
) {
3197 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
3198 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
3203 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
3204 const char *formatMap
= "map %s";
3205 if (defaultRxConfig
) {
3206 bufDefault
[i
] = '\0';
3207 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
3209 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
3213 static void cliMap(char *cmdline
)
3216 char buf
[RX_MAPPABLE_CHANNEL_COUNT
+ 1];
3218 uint32_t len
= strlen(cmdline
);
3219 if (len
== RX_MAPPABLE_CHANNEL_COUNT
) {
3221 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3222 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
3226 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3227 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
3229 if (strchr(rcChannelLetters
, buf
[i
]) && !strchr(buf
+ i
+ 1, buf
[i
]))
3232 cliShowParseError();
3235 parseRcChannels(buf
, rxConfigMutable());
3236 } else if (len
> 0) {
3237 cliShowParseError();
3241 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3242 buf
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
3246 cliPrintLinef("map %s", buf
);
3249 static char *skipSpace(char *buffer
)
3251 while (*(buffer
) == ' ') {
3258 static char *checkCommand(char *cmdline
, const char *command
)
3260 if (!strncasecmp(cmdline
, command
, strlen(command
)) // command names match
3261 && (isspace((unsigned)cmdline
[strlen(command
)]) || cmdline
[strlen(command
)] == 0)) {
3262 return skipSpace(cmdline
+ strlen(command
) + 1);
3268 static void cliRebootEx(bool bootLoader
)
3270 cliPrint("\r\nRebooting");
3271 bufWriterFlush(cliWriter
);
3272 waitForSerialPortToFinishTransmitting(cliPort
);
3275 systemResetToBootloader();
3281 static void cliReboot(void)
3286 static void cliBootloader(char *cmdline
)
3290 cliPrintHashLine("restarting in bootloader mode");
3294 static void cliExit(char *cmdline
)
3298 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3299 bufWriterFlush(cliWriter
);
3304 // incase a motor was left running during motortest, clear it here
3305 mixerResetDisarmedMotors();
3312 static void cliGpsPassthrough(char *cmdline
)
3316 gpsEnablePassthrough(cliPort
);
3320 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3321 static void cliPrintGyroRegisters(uint8_t whichSensor
)
3323 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor
, MPU_RA_WHO_AM_I
));
3324 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_CONFIG
));
3325 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_GYRO_CONFIG
));
3328 static void cliDumpGyroRegisters(char *cmdline
)
3330 #ifdef USE_MULTI_GYRO
3331 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_1
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
3332 cliPrintLinef("\r\n# Gyro 1");
3333 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
3335 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_2
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
3336 cliPrintLinef("\r\n# Gyro 2");
3337 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2
);
3340 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
3347 static int parseOutputIndex(char *pch
, bool allowAllEscs
) {
3348 int outputIndex
= atoi(pch
);
3349 if ((outputIndex
>= 0) && (outputIndex
< getMotorCount())) {
3350 cliPrintLinef("Using output %d.", outputIndex
);
3351 } else if (allowAllEscs
&& outputIndex
== ALL_MOTORS
) {
3352 cliPrintLinef("Using all outputs.");
3354 cliPrintErrorLinef("INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3362 #if defined(USE_DSHOT)
3363 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3365 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3366 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3367 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3375 #define ESC_INFO_VERSION_POSITION 12
3377 void printEscInfo(const uint8_t *escInfoBuffer
, uint8_t bytesRead
)
3379 bool escInfoReceived
= false;
3380 if (bytesRead
> ESC_INFO_VERSION_POSITION
) {
3381 uint8_t escInfoVersion
;
3382 uint8_t frameLength
;
3383 if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 254) {
3384 escInfoVersion
= ESC_INFO_BLHELI32
;
3385 frameLength
= ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
;
3386 } else if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 255) {
3387 escInfoVersion
= ESC_INFO_KISS_V2
;
3388 frameLength
= ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE
;
3390 escInfoVersion
= ESC_INFO_KISS_V1
;
3391 frameLength
= ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE
;
3394 if (bytesRead
== frameLength
) {
3395 escInfoReceived
= true;
3397 if (calculateCrc8(escInfoBuffer
, frameLength
- 1) == escInfoBuffer
[frameLength
- 1]) {
3398 uint8_t firmwareVersion
= 0;
3399 uint8_t firmwareSubVersion
= 0;
3400 uint8_t escType
= 0;
3401 switch (escInfoVersion
) {
3402 case ESC_INFO_KISS_V1
:
3403 firmwareVersion
= escInfoBuffer
[12];
3404 firmwareSubVersion
= (escInfoBuffer
[13] & 0x1f) + 97;
3405 escType
= (escInfoBuffer
[13] & 0xe0) >> 5;
3408 case ESC_INFO_KISS_V2
:
3409 firmwareVersion
= escInfoBuffer
[13];
3410 firmwareSubVersion
= escInfoBuffer
[14];
3411 escType
= escInfoBuffer
[15];
3414 case ESC_INFO_BLHELI32
:
3415 firmwareVersion
= escInfoBuffer
[13];
3416 firmwareSubVersion
= escInfoBuffer
[14];
3417 escType
= escInfoBuffer
[15];
3422 cliPrint("ESC Type: ");
3423 switch (escInfoVersion
) {
3424 case ESC_INFO_KISS_V1
:
3425 case ESC_INFO_KISS_V2
:
3428 cliPrintLine("KISS8A");
3432 cliPrintLine("KISS16A");
3436 cliPrintLine("KISS24A");
3440 cliPrintLine("KISS Ultralite");
3444 cliPrintLine("unknown");
3450 case ESC_INFO_BLHELI32
:
3452 char *escType
= (char *)(escInfoBuffer
+ 31);
3454 cliPrintLine(escType
);
3460 cliPrint("MCU Serial No: 0x");
3461 for (int i
= 0; i
< 12; i
++) {
3462 if (i
&& (i
% 3 == 0)) {
3465 cliPrintf("%02x", escInfoBuffer
[i
]);
3469 switch (escInfoVersion
) {
3470 case ESC_INFO_KISS_V1
:
3471 case ESC_INFO_KISS_V2
:
3472 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion
/ 100, firmwareVersion
% 100, (char)firmwareSubVersion
);
3475 case ESC_INFO_BLHELI32
:
3476 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion
, firmwareSubVersion
);
3480 if (escInfoVersion
== ESC_INFO_KISS_V2
|| escInfoVersion
== ESC_INFO_BLHELI32
) {
3481 cliPrintLinef("Rotation Direction: %s", escInfoBuffer
[16] ? "reversed" : "normal");
3482 cliPrintLinef("3D: %s", escInfoBuffer
[17] ? "on" : "off");
3483 if (escInfoVersion
== ESC_INFO_BLHELI32
) {
3484 uint8_t setting
= escInfoBuffer
[18];
3485 cliPrint("Low voltage Limit: ");
3488 cliPrintLine("off");
3492 cliPrintLine("unsupported");
3496 cliPrintLinef("%d.%01d", setting
/ 10, setting
% 10);
3501 setting
= escInfoBuffer
[19];
3502 cliPrint("Current Limit: ");
3505 cliPrintLine("off");
3509 cliPrintLine("unsupported");
3513 cliPrintLinef("%d", setting
);
3518 for (int i
= 0; i
< 4; i
++) {
3519 setting
= escInfoBuffer
[i
+ 20];
3520 cliPrintLinef("LED %d: %s", i
, setting
? (setting
== 255) ? "unsupported" : "on" : "off");
3525 cliPrintErrorLinef("CHECKSUM ERROR.");
3530 if (!escInfoReceived
) {
3531 cliPrintLine("No Info.");
3535 static void executeEscInfoCommand(uint8_t escIndex
)
3537 cliPrintLinef("Info for ESC %d:", escIndex
);
3539 uint8_t escInfoBuffer
[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
];
3541 startEscDataRead(escInfoBuffer
, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
);
3543 pwmWriteDshotCommand(escIndex
, getMotorCount(), DSHOT_CMD_ESC_INFO
, true);
3547 printEscInfo(escInfoBuffer
, getNumberEscBytesRead());
3549 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3551 static void cliDshotProg(char *cmdline
)
3553 if (isEmpty(cmdline
) || motorConfig()->dev
.motorPwmProtocol
< PWM_TYPE_DSHOT150
) {
3554 cliShowParseError();
3560 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3563 bool firstCommand
= true;
3564 while (pch
!= NULL
) {
3567 escIndex
= parseOutputIndex(pch
, true);
3568 if (escIndex
== -1) {
3575 int command
= atoi(pch
);
3576 if (command
>= 0 && command
< DSHOT_MIN_THROTTLE
) {
3580 if (command
== DSHOT_CMD_ESC_INFO
) {
3581 delay(5); // Wait for potential ESC telemetry transmission to finish
3586 firstCommand
= false;
3589 if (command
!= DSHOT_CMD_ESC_INFO
) {
3590 pwmWriteDshotCommand(escIndex
, getMotorCount(), command
, true);
3592 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3593 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
3594 if (escIndex
!= ALL_MOTORS
) {
3595 executeEscInfoCommand(escIndex
);
3597 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
3598 executeEscInfoCommand(i
);
3604 cliPrintLine("Not supported.");
3608 cliPrintLinef("Command Sent: %d", command
);
3611 cliPrintErrorLinef("INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE
- 1);
3619 pch
= strtok_r(NULL
, " ", &saveptr
);
3626 #ifdef USE_ESCSERIAL
3627 static void cliEscPassthrough(char *cmdline
)
3629 if (isEmpty(cmdline
)) {
3630 cliShowParseError();
3636 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3640 while (pch
!= NULL
) {
3643 if (strncasecmp(pch
, "sk", strlen(pch
)) == 0) {
3644 mode
= PROTOCOL_SIMONK
;
3645 } else if (strncasecmp(pch
, "bl", strlen(pch
)) == 0) {
3646 mode
= PROTOCOL_BLHELI
;
3647 } else if (strncasecmp(pch
, "ki", strlen(pch
)) == 0) {
3648 mode
= PROTOCOL_KISS
;
3649 } else if (strncasecmp(pch
, "cc", strlen(pch
)) == 0) {
3650 mode
= PROTOCOL_KISSALL
;
3652 cliShowParseError();
3658 escIndex
= parseOutputIndex(pch
, mode
== PROTOCOL_KISS
);
3659 if (escIndex
== -1) {
3665 cliShowParseError();
3673 pch
= strtok_r(NULL
, " ", &saveptr
);
3676 if (!escEnablePassthrough(cliPort
, &motorConfig()->dev
, escIndex
, mode
)) {
3677 cliPrintErrorLinef("Error starting ESC connection");
3682 #ifndef USE_QUAD_MIXER_ONLY
3683 static void cliMixer(char *cmdline
)
3687 len
= strlen(cmdline
);
3690 cliPrintLinef("Mixer: %s", mixerNames
[mixerConfig()->mixerMode
- 1]);
3692 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3693 cliPrint("Available:");
3694 for (uint32_t i
= 0; ; i
++) {
3695 if (mixerNames
[i
] == NULL
)
3697 cliPrintf(" %s", mixerNames
[i
]);
3703 for (uint32_t i
= 0; ; i
++) {
3704 if (mixerNames
[i
] == NULL
) {
3705 cliPrintErrorLinef("INVALID NAME");
3708 if (strncasecmp(cmdline
, mixerNames
[i
], len
) == 0) {
3709 mixerConfigMutable()->mixerMode
= i
+ 1;
3718 static void cliMotor(char *cmdline
)
3720 if (isEmpty(cmdline
)) {
3721 cliShowParseError();
3730 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3732 while (pch
!= NULL
) {
3735 motorIndex
= parseOutputIndex(pch
, true);
3736 if (motorIndex
== -1) {
3742 motorValue
= atoi(pch
);
3747 pch
= strtok_r(NULL
, " ", &saveptr
);
3751 if (motorValue
< PWM_RANGE_MIN
|| motorValue
> PWM_RANGE_MAX
) {
3752 cliShowArgumentRangeError("VALUE", 1000, 2000);
3754 uint32_t motorOutputValue
= convertExternalToMotor(motorValue
);
3756 if (motorIndex
!= ALL_MOTORS
) {
3757 motor_disarmed
[motorIndex
] = motorOutputValue
;
3759 cliPrintLinef("motor %d: %d", motorIndex
, motorOutputValue
);
3761 for (int i
= 0; i
< getMotorCount(); i
++) {
3762 motor_disarmed
[i
] = motorOutputValue
;
3765 cliPrintLinef("all motors: %d", motorOutputValue
);
3769 cliShowParseError();
3774 static void cliPlaySound(char *cmdline
)
3778 static int lastSoundIdx
= -1;
3780 if (isEmpty(cmdline
)) {
3781 i
= lastSoundIdx
+ 1; //next sound index
3782 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3783 while (true) { //no name for index; try next one
3784 if (++i
>= beeperTableEntryCount())
3785 i
= 0; //if end then wrap around to first entry
3786 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
3787 break; //if name OK then play sound below
3788 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
3789 cliPrintErrorLinef("ERROR PLAYING SOUND");
3794 } else { //index value was given
3796 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3797 cliPrintLinef("No sound for index %d", i
);
3803 cliPrintLinef("Playing sound %d: %s", i
, name
);
3804 beeper(beeperModeForTableIndex(i
));
3808 static void cliProfile(char *cmdline
)
3810 if (isEmpty(cmdline
)) {
3811 cliPrintLinef("profile %d", getPidProfileIndexToUse());
3814 const int i
= atoi(cmdline
);
3815 if (i
>= 0 && i
< PID_PROFILE_COUNT
) {
3816 changePidProfile(i
);
3819 cliPrintErrorLinef("PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT
- 1);
3824 static void cliRateProfile(char *cmdline
)
3826 if (isEmpty(cmdline
)) {
3827 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
3830 const int i
= atoi(cmdline
);
3831 if (i
>= 0 && i
< CONTROL_RATE_PROFILE_COUNT
) {
3832 changeControlRateProfile(i
);
3835 cliPrintErrorLinef("RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT
- 1);
3840 static void cliDumpPidProfile(uint8_t pidProfileIndex
, dumpFlags_t dumpMask
)
3842 if (pidProfileIndex
>= PID_PROFILE_COUNT
) {
3847 pidProfileIndexToUse
= pidProfileIndex
;
3852 char profileStr
[10];
3853 tfp_sprintf(profileStr
, "profile %d", pidProfileIndex
);
3854 dumpAllValues(PROFILE_VALUE
, dumpMask
, profileStr
);
3856 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
3859 static void cliDumpRateProfile(uint8_t rateProfileIndex
, dumpFlags_t dumpMask
)
3861 if (rateProfileIndex
>= CONTROL_RATE_PROFILE_COUNT
) {
3866 rateProfileIndexToUse
= rateProfileIndex
;
3871 char rateProfileStr
[14];
3872 tfp_sprintf(rateProfileStr
, "rateprofile %d", rateProfileIndex
);
3873 dumpAllValues(PROFILE_RATE_VALUE
, dumpMask
, rateProfileStr
);
3875 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
3878 #ifdef USE_CLI_BATCH
3879 static void cliPrintCommandBatchWarning(const char *warning
)
3881 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
3883 cliPrintErrorLinef(warning
);
3887 static void resetCommandBatch(void)
3889 commandBatchActive
= false;
3890 commandBatchError
= false;
3893 static void cliBatch(char *cmdline
)
3895 if (strncasecmp(cmdline
, "start", 5) == 0) {
3896 if (!commandBatchActive
) {
3897 commandBatchActive
= true;
3898 commandBatchError
= false;
3900 cliPrintLine("Command batch started");
3901 } else if (strncasecmp(cmdline
, "end", 3) == 0) {
3902 if (commandBatchActive
&& commandBatchError
) {
3903 cliPrintCommandBatchWarning(NULL
);
3905 cliPrintLine("Command batch ended");
3907 resetCommandBatch();
3909 cliPrintErrorLinef("Invalid option");
3914 static void cliSave(char *cmdline
)
3918 #ifdef USE_CLI_BATCH
3919 if (commandBatchActive
&& commandBatchError
) {
3920 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3921 resetCommandBatch();
3926 cliPrintHashLine("saving");
3928 #if defined(USE_BOARD_INFO)
3929 if (boardInformationUpdated
) {
3930 persistBoardInformation();
3932 #if defined(USE_SIGNATURE)
3933 if (signatureUpdated
) {
3937 #endif // USE_BOARD_INFO
3939 if (featureMaskIsCopied
) {
3940 writeEEPROMWithFeatures(featureMaskCopy
);
3948 static void cliDefaults(char *cmdline
)
3952 if (isEmpty(cmdline
)) {
3954 } else if (strncasecmp(cmdline
, "nosave", 6) == 0) {
3955 saveConfigs
= false;
3960 cliPrintHashLine("resetting to defaults");
3964 #ifdef USE_CLI_BATCH
3965 // Reset only the error state and allow the batch active state to remain.
3966 // This way if a "defaults nosave" was issued after the "batch on" we'll
3967 // only reset the current error state but the batch will still be active
3968 // for subsequent commands.
3969 commandBatchError
= false;
3977 void cliPrintVarDefault(const clivalue_t
*value
)
3979 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
3981 const char *defaultFormat
= "Default value: ";
3982 const int valueOffset
= getValueOffset(value
);
3983 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
3984 if (!equalsDefault
) {
3985 cliPrintf(defaultFormat
, value
->name
);
3986 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
3992 STATIC_UNIT_TESTED
void cliGet(char *cmdline
)
3994 const clivalue_t
*val
;
3995 int matchedCommands
= 0;
3997 pidProfileIndexToUse
= getCurrentPidProfileIndex();
3998 rateProfileIndexToUse
= getCurrentControlRateProfileIndex();
4000 backupAndResetConfigs();
4002 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4003 if (strcasestr(valueTable
[i
].name
, cmdline
)) {
4004 val
= &valueTable
[i
];
4005 if (matchedCommands
> 0) {
4008 cliPrintf("%s = ", valueTable
[i
].name
);
4009 cliPrintVar(val
, 0);
4011 switch (val
->type
& VALUE_SECTION_MASK
) {
4016 case PROFILE_RATE_VALUE
:
4024 cliPrintVarRange(val
);
4025 cliPrintVarDefault(val
);
4033 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4034 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4036 if (matchedCommands
) {
4040 cliPrintErrorLinef("INVALID NAME");
4043 static uint8_t getWordLength(char *bufBegin
, char *bufEnd
)
4045 while (*(bufEnd
- 1) == ' ') {
4049 return bufEnd
- bufBegin
;
4052 uint16_t cliGetSettingIndex(char *name
, uint8_t length
)
4054 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4055 const char *settingName
= valueTable
[i
].name
;
4057 // ensure exact match when setting to prevent setting variables with shorter names
4058 if (strncasecmp(name
, settingName
, strlen(settingName
)) == 0 && length
== strlen(settingName
)) {
4062 return valueTableEntryCount
;
4065 STATIC_UNIT_TESTED
void cliSet(char *cmdline
)
4067 const uint32_t len
= strlen(cmdline
);
4070 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
4071 cliPrintLine("Current settings: ");
4073 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
4074 const clivalue_t
*val
= &valueTable
[i
];
4075 cliPrintf("%s = ", valueTable
[i
].name
);
4076 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4079 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
4082 uint8_t variableNameLength
= getWordLength(cmdline
, eqptr
);
4084 // skip the '=' and any ' ' characters
4086 eqptr
= skipSpace(eqptr
);
4088 const uint16_t index
= cliGetSettingIndex(cmdline
, variableNameLength
);
4089 if (index
>= valueTableEntryCount
) {
4090 cliPrintErrorLinef("INVALID NAME");
4093 const clivalue_t
*val
= &valueTable
[index
];
4095 bool valueChanged
= false;
4097 switch (val
->type
& VALUE_MODE_MASK
) {
4099 if ((val
->type
& VALUE_TYPE_MASK
) == VAR_UINT32
) {
4100 uint32_t value
= strtoul(eqptr
, NULL
, 10);
4102 if (value
<= val
->config
.u32Max
) {
4103 cliSetVar(val
, value
);
4104 valueChanged
= true;
4107 int value
= atoi(eqptr
);
4111 getMinMax(val
, &min
, &max
);
4113 if (value
>= min
&& value
<= max
) {
4114 cliSetVar(val
, value
);
4115 valueChanged
= true;
4124 if ((val
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
4125 tableIndex
= TABLE_OFF_ON
;
4127 tableIndex
= val
->config
.lookup
.tableIndex
;
4129 const lookupTableEntry_t
*tableEntry
= &lookupTables
[tableIndex
];
4130 bool matched
= false;
4131 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
4132 matched
= tableEntry
->values
[tableValueIndex
] && strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
4135 value
= tableValueIndex
;
4137 cliSetVar(val
, value
);
4138 valueChanged
= true;
4146 const uint8_t arrayLength
= val
->config
.array
.length
;
4147 char *valPtr
= eqptr
;
4150 while (i
< arrayLength
&& valPtr
!= NULL
) {
4152 valPtr
= skipSpace(valPtr
);
4154 // process substring starting at valPtr
4155 // note: no need to copy substrings for atoi()
4156 // it stops at the first character that cannot be converted...
4157 switch (val
->type
& VALUE_TYPE_MASK
) {
4161 // fetch data pointer
4162 uint8_t *data
= (uint8_t *)cliGetValuePointer(val
) + i
;
4164 *data
= (uint8_t)atoi((const char*) valPtr
);
4170 // fetch data pointer
4171 int8_t *data
= (int8_t *)cliGetValuePointer(val
) + i
;
4173 *data
= (int8_t)atoi((const char*) valPtr
);
4179 // fetch data pointer
4180 uint16_t *data
= (uint16_t *)cliGetValuePointer(val
) + i
;
4182 *data
= (uint16_t)atoi((const char*) valPtr
);
4188 // fetch data pointer
4189 int16_t *data
= (int16_t *)cliGetValuePointer(val
) + i
;
4191 *data
= (int16_t)atoi((const char*) valPtr
);
4197 // find next comma (or end of string)
4198 valPtr
= strchr(valPtr
, ',') + 1;
4205 valueChanged
= true;
4209 char *valPtr
= eqptr
;
4210 valPtr
= skipSpace(valPtr
);
4212 const unsigned int len
= strlen(valPtr
);
4213 const uint8_t min
= val
->config
.string
.minlength
;
4214 const uint8_t max
= val
->config
.string
.maxlength
;
4215 const bool updatable
= ((val
->config
.string
.flags
& STRING_FLAGS_WRITEONCE
) == 0 ||
4216 strlen((char *)cliGetValuePointer(val
)) == 0 ||
4217 strncmp(valPtr
, (char *)cliGetValuePointer(val
), len
) == 0);
4219 if (updatable
&& len
> 0 && len
<= max
) {
4220 memset((char *)cliGetValuePointer(val
), 0, max
);
4221 if (len
>= min
&& strncmp(valPtr
, emptyName
, len
)) {
4222 strncpy((char *)cliGetValuePointer(val
), valPtr
, len
);
4224 valueChanged
= true;
4226 cliPrintErrorLinef("STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max
);
4233 cliPrintf("%s set to ", val
->name
);
4234 cliPrintVar(val
, 0);
4236 cliPrintErrorLinef("INVALID VALUE");
4237 cliPrintVarRange(val
);
4242 // no equals, check for matching variables.
4247 static void cliStatus(char *cmdline
)
4251 // MCU type, clock, vrefint, core temperature
4253 cliPrintf("MCU %s Clock=%dMHz", MCU_TYPE_NAME
, (SystemCoreClock
/ 1000000));
4256 // Only F4 is capable of switching between HSE/HSI (for now)
4257 int sysclkSource
= SystemSYSCLKSource();
4259 const char *SYSCLKSource
[] = { "HSI", "HSE", "PLLP", "PLLR" };
4260 const char *PLLSource
[] = { "-HSI", "-HSE" };
4264 if (sysclkSource
>= 2) {
4265 pllSource
= SystemPLLSource();
4268 cliPrintf(" (%s%s)", SYSCLKSource
[sysclkSource
], (sysclkSource
< 2) ? "" : PLLSource
[pllSource
]);
4271 #ifdef USE_ADC_INTERNAL
4272 uint16_t vrefintMv
= getVrefMv();
4273 int16_t coretemp
= getCoreTemperatureCelsius();
4274 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv
/ 1000, (vrefintMv
% 1000) / 10, coretemp
);
4279 // Stack and config sizes and usages
4281 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4283 cliPrintf(", Stack used: %d", stackUsedSize());
4287 #ifdef EEPROM_IN_RAM
4288 #define CONFIG_SIZE EEPROM_SIZE
4290 #define CONFIG_SIZE (&__config_end - &__config_start)
4292 cliPrintLinef("Config size: %d, Max available config: %d", getEEPROMConfigSize(), CONFIG_SIZE
);
4296 #if defined(USE_SENSOR_NAMES)
4297 const uint32_t detectedSensorsMask
= sensorsMask();
4298 for (uint32_t i
= 0; ; i
++) {
4299 if (sensorTypeNames
[i
] == NULL
) {
4302 const uint32_t mask
= (1 << i
);
4303 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
4304 const uint8_t sensorHardwareIndex
= detectedSensors
[i
];
4305 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
4309 cliPrintf("%s=%s", sensorTypeNames
[i
], sensorHardware
);
4310 #if defined(USE_ACC)
4311 if (mask
== SENSOR_ACC
&& acc
.dev
.revisionCode
) {
4312 cliPrintf(".%c", acc
.dev
.revisionCode
);
4318 #endif /* USE_SENSOR_NAMES */
4320 // Uptime and wall clock
4322 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4325 char buf
[FORMATTED_DATE_TIME_BUFSIZE
];
4327 if (rtcGetDateTime(&dt
)) {
4328 dateTimeFormatLocal(buf
, &dt
);
4329 cliPrintf(", Current Time: %s", buf
);
4336 const int gyroRate
= getTaskDeltaTime(TASK_GYROPID
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_GYROPID
)));
4337 const int rxRate
= currentRxRefreshRate
== 0 ? 0 : (int)(1000000.0f
/ ((float)currentRxRefreshRate
));
4338 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
4339 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4340 constrain(averageSystemLoadPercent
, 0, 100), getTaskDeltaTime(TASK_GYROPID
), gyroRate
, rxRate
, systemRate
);
4344 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4346 // Other devices and status
4349 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
4351 const uint16_t i2cErrorCounter
= 0;
4353 cliPrintLinef("I2C Errors: %d", i2cErrorCounter
);
4359 cliPrint("Arming disable flags:");
4360 armingDisableFlags_e flags
= getArmingDisableFlags();
4362 const int bitpos
= ffs(flags
) - 1;
4363 flags
&= ~(1 << bitpos
);
4364 cliPrintf(" %s", armingDisableFlagNames
[bitpos
]);
4369 #if defined(USE_TASK_STATISTICS)
4370 static void cliTasks(char *cmdline
)
4374 int averageLoadSum
= 0;
4377 if (systemConfig()->task_statistics
) {
4378 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4380 cliPrintLine("Task list");
4383 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
4384 cfTaskInfo_t taskInfo
;
4385 getTaskInfo(taskId
, &taskInfo
);
4386 if (taskInfo
.isEnabled
) {
4388 int subTaskFrequency
= 0;
4389 if (taskId
== TASK_GYROPID
) {
4390 subTaskFrequency
= taskInfo
.movingAverageCycleTime
== 0.0f
? 0.0f
: (int)(1000000.0f
/ (taskInfo
.movingAverageCycleTime
));
4391 taskFrequency
= subTaskFrequency
/ pidConfig()->pid_process_denom
;
4392 if (pidConfig()->pid_process_denom
> 1) {
4393 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
4395 taskFrequency
= subTaskFrequency
;
4396 cliPrintf("%02d - (%11s/%3s) ", taskId
, taskInfo
.subTaskName
, taskInfo
.taskName
);
4399 taskFrequency
= taskInfo
.averageDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.averageDeltaTime
));
4400 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
4402 const int maxLoad
= taskInfo
.maxExecutionTime
== 0 ? 0 :(taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
4403 const int averageLoad
= taskInfo
.averageExecutionTime
== 0 ? 0 : (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
4404 if (taskId
!= TASK_SERIAL
) {
4405 maxLoadSum
+= maxLoad
;
4406 averageLoadSum
+= averageLoad
;
4408 if (systemConfig()->task_statistics
) {
4409 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4410 taskFrequency
, taskInfo
.maxExecutionTime
, taskInfo
.averageExecutionTime
,
4411 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, taskInfo
.totalExecutionTime
/ 1000);
4413 cliPrintLinef("%6d", taskFrequency
);
4415 if (taskId
== TASK_GYROPID
&& pidConfig()->pid_process_denom
> 1) {
4416 cliPrintLinef(" - (%15s) %6d", taskInfo
.subTaskName
, subTaskFrequency
);
4419 schedulerResetTaskMaxExecutionTime(taskId
);
4422 if (systemConfig()->task_statistics
) {
4423 cfCheckFuncInfo_t checkFuncInfo
;
4424 getCheckFuncInfo(&checkFuncInfo
);
4425 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo
.maxExecutionTime
, checkFuncInfo
.averageExecutionTime
, checkFuncInfo
.totalExecutionTime
/ 1000);
4426 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
4431 static void cliVersion(char *cmdline
)
4435 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4438 systemConfig()->boardIdentifier
,
4443 MSP_API_VERSION_STRING
4445 #ifdef FEATURE_CUT_LEVEL
4446 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL
);
4452 #ifdef USE_RC_SMOOTHING_FILTER
4453 static void cliRcSmoothing(char *cmdline
)
4456 rcSmoothingFilter_t
*rcSmoothingData
= getRcSmoothingData();
4457 cliPrint("# RC Smoothing Type: ");
4458 if (rxConfig()->rc_smoothing_type
== RC_SMOOTHING_TYPE_FILTER
) {
4459 cliPrintLine("FILTER");
4460 uint16_t avgRxFrameMs
= rcSmoothingData
->averageFrameTimeUs
;
4461 if (rcSmoothingAutoCalculate()) {
4462 cliPrint("# Detected RX frame rate: ");
4463 if (avgRxFrameMs
== 0) {
4464 cliPrintLine("NO SIGNAL");
4466 cliPrintLinef("%d.%dms", avgRxFrameMs
/ 1000, avgRxFrameMs
% 1000);
4469 cliPrint("# Input filter type: ");
4470 cliPrintLinef(lookupTables
[TABLE_RC_SMOOTHING_INPUT_TYPE
].values
[rcSmoothingData
->inputFilterType
]);
4471 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingData
->inputCutoffFrequency
);
4472 if (rcSmoothingData
->inputCutoffSetting
== 0) {
4473 cliPrintLine("(auto)");
4475 cliPrintLine("(manual)");
4477 cliPrint("# Derivative filter type: ");
4478 cliPrintLinef(lookupTables
[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE
].values
[rcSmoothingData
->derivativeFilterType
]);
4479 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingData
->derivativeCutoffFrequency
);
4480 if (rcSmoothingData
->derivativeFilterType
== RC_SMOOTHING_DERIVATIVE_OFF
) {
4481 cliPrintLine("off)");
4483 if (rcSmoothingData
->derivativeCutoffSetting
== 0) {
4484 cliPrintLine("auto)");
4486 cliPrintLine("manual)");
4490 cliPrintLine("INTERPOLATION");
4493 #endif // USE_RC_SMOOTHING_FILTER
4495 #if defined(USE_RESOURCE_MGMT)
4497 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
4500 const uint8_t owner
;
4504 const uint8_t maxIndex
;
4505 } cliResourceValue_t
;
4507 // Handy macros for keeping the table tidy.
4508 // DEFS : Single entry
4509 // DEFA : Array of uint8_t (stride = 1)
4510 // DEFW : Wider stride case; array of structs.
4512 #define DEFS(owner, pgn, type, member) \
4513 { owner, pgn, 0, offsetof(type, member), 0 }
4515 #define DEFA(owner, pgn, type, member, max) \
4516 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
4518 #define DEFW(owner, pgn, type, member, max) \
4519 { owner, pgn, sizeof(type), offsetof(type, member), max }
4521 const cliResourceValue_t resourceTable
[] = {
4523 DEFS( OWNER_BEEPER
, PG_BEEPER_DEV_CONFIG
, beeperDevConfig_t
, ioTag
) ,
4525 DEFA( OWNER_MOTOR
, PG_MOTOR_CONFIG
, motorConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_MOTORS
),
4527 DEFA( OWNER_SERVO
, PG_SERVO_CONFIG
, servoConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_SERVOS
),
4529 #if defined(USE_PPM)
4530 DEFS( OWNER_PPMINPUT
, PG_PPM_CONFIG
, ppmConfig_t
, ioTag
),
4532 #if defined(USE_PWM)
4533 DEFA( OWNER_PWMINPUT
, PG_PWM_CONFIG
, pwmConfig_t
, ioTags
[0], PWM_INPUT_PORT_COUNT
),
4535 #ifdef USE_RANGEFINDER_HCSR04
4536 DEFS( OWNER_SONAR_TRIGGER
, PG_SONAR_CONFIG
, sonarConfig_t
, triggerTag
),
4537 DEFS( OWNER_SONAR_ECHO
, PG_SONAR_CONFIG
, sonarConfig_t
, echoTag
),
4539 #ifdef USE_LED_STRIP
4540 DEFS( OWNER_LED_STRIP
, PG_LED_STRIP_CONFIG
, ledStripConfig_t
, ioTag
),
4543 DEFA( OWNER_SERIAL_TX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagTx
[0], SERIAL_PORT_MAX_INDEX
),
4544 DEFA( OWNER_SERIAL_RX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagRx
[0], SERIAL_PORT_MAX_INDEX
),
4547 DEFA( OWNER_INVERTER
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagInverter
[0], SERIAL_PORT_MAX_INDEX
),
4550 DEFW( OWNER_I2C_SCL
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagScl
, I2CDEV_COUNT
),
4551 DEFW( OWNER_I2C_SDA
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagSda
, I2CDEV_COUNT
),
4553 DEFA( OWNER_LED
, PG_STATUS_LED_CONFIG
, statusLedConfig_t
, ioTags
[0], STATUS_LED_NUMBER
),
4554 #ifdef USE_SPEKTRUM_BIND
4555 DEFS( OWNER_RX_BIND
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_pin_override_ioTag
),
4556 DEFS( OWNER_RX_BIND_PLUG
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_plug_ioTag
),
4558 #ifdef USE_TRANSPONDER
4559 DEFS( OWNER_TRANSPONDER
, PG_TRANSPONDER_CONFIG
, transponderConfig_t
, ioTag
),
4562 DEFW( OWNER_SPI_SCK
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagSck
, SPIDEV_COUNT
),
4563 DEFW( OWNER_SPI_MISO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMiso
, SPIDEV_COUNT
),
4564 DEFW( OWNER_SPI_MOSI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMosi
, SPIDEV_COUNT
),
4566 #ifdef USE_ESCSERIAL
4567 DEFS( OWNER_ESCSERIAL
, PG_ESCSERIAL_CONFIG
, escSerialConfig_t
, ioTag
),
4569 #ifdef USE_CAMERA_CONTROL
4570 DEFS( OWNER_CAMERA_CONTROL
, PG_CAMERA_CONTROL_CONFIG
, cameraControlConfig_t
, ioTag
),
4573 DEFS( OWNER_ADC_BATT
, PG_ADC_CONFIG
, adcConfig_t
, vbat
.ioTag
),
4574 DEFS( OWNER_ADC_RSSI
, PG_ADC_CONFIG
, adcConfig_t
, rssi
.ioTag
),
4575 DEFS( OWNER_ADC_CURR
, PG_ADC_CONFIG
, adcConfig_t
, current
.ioTag
),
4576 DEFS( OWNER_ADC_EXT
, PG_ADC_CONFIG
, adcConfig_t
, external1
.ioTag
),
4579 DEFS( OWNER_BARO_CS
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_spi_csn
),
4582 DEFS( OWNER_COMPASS_CS
, PG_COMPASS_CONFIG
, compassConfig_t
, mag_spi_csn
),
4583 #ifdef USE_MAG_DATA_READY_SIGNAL
4584 DEFS( OWNER_COMPASS_EXTI
, PG_COMPASS_CONFIG
, compassConfig_t
, interruptTag
),
4587 #ifdef USE_SDCARD_SPI
4588 DEFS( OWNER_SDCARD_CS
, PG_SDCARD_CONFIG
, sdcardConfig_t
, chipSelectTag
),
4591 DEFS( OWNER_SDCARD_DETECT
, PG_SDCARD_CONFIG
, sdcardConfig_t
, cardDetectTag
),
4594 DEFA( OWNER_PINIO
, PG_PINIO_CONFIG
, pinioConfig_t
, ioTag
, PINIO_COUNT
),
4596 #if defined(USE_USB_MSC)
4597 DEFS( OWNER_USB_MSC_PIN
, PG_USB_CONFIG
, usbDev_t
, mscButtonPin
),
4599 #ifdef USE_FLASH_CHIP
4600 DEFS( OWNER_FLASH_CS
, PG_FLASH_CONFIG
, flashConfig_t
, csTag
),
4603 DEFS( OWNER_OSD_CS
, PG_MAX7456_CONFIG
, max7456Config_t
, csTag
),
4606 DEFS( OWNER_RX_SPI_CS
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, csnTag
),
4607 DEFS( OWNER_RX_SPI_EXTI
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, extiIoTag
),
4608 DEFS( OWNER_RX_SPI_BIND
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, bindIoTag
),
4609 DEFS( OWNER_RX_SPI_LED
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, ledIoTag
),
4610 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
4611 DEFS( OWNER_RX_SPI_CC2500_TX_EN
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, txEnIoTag
),
4612 DEFS( OWNER_RX_SPI_CC2500_LNA_EN
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, lnaEnIoTag
),
4613 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
4614 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL
, PG_RX_CC2500_SPI_CONFIG
, rxCc2500SpiConfig_t
, antSelIoTag
),
4618 #ifdef USE_GYRO_EXTI
4619 DEFW( OWNER_GYRO_EXTI
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, extiTag
, MAX_GYRODEV_COUNT
),
4621 DEFW( OWNER_GYRO_CS
, PG_GYRO_DEVICE_CONFIG
, gyroDeviceConfig_t
, csnTag
, MAX_GYRODEV_COUNT
),
4622 #ifdef USE_USB_DETECT
4623 DEFS( OWNER_USB_DETECT
, PG_USB_CONFIG
, usbDev_t
, detectPin
),
4625 #ifdef USE_VTX_RTC6705
4626 DEFS( OWNER_VTX_POWER
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, powerTag
),
4627 DEFS( OWNER_VTX_CS
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, csTag
),
4628 DEFS( OWNER_VTX_DATA
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, dataTag
),
4629 DEFS( OWNER_VTX_CLK
, PG_VTX_IO_CONFIG
, vtxIOConfig_t
, clockTag
),
4637 static ioTag_t
*getIoTag(const cliResourceValue_t value
, uint8_t index
)
4639 const pgRegistry_t
* rec
= pgFind(value
.pgn
);
4640 return CONST_CAST(ioTag_t
*, rec
->address
+ value
.stride
* index
+ value
.offset
);
4643 static void printResource(dumpFlags_t dumpMask
, const char *headingStr
)
4645 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
4646 for (unsigned int i
= 0; i
< ARRAYLEN(resourceTable
); i
++) {
4647 const char* owner
= ownerNames
[resourceTable
[i
].owner
];
4648 const pgRegistry_t
* pg
= pgFind(resourceTable
[i
].pgn
);
4649 const void *currentConfig
;
4650 const void *defaultConfig
;
4651 if (configIsInCopy
) {
4652 currentConfig
= pg
->copy
;
4653 defaultConfig
= pg
->address
;
4655 currentConfig
= pg
->address
;
4656 defaultConfig
= NULL
;
4659 for (int index
= 0; index
< MAX_RESOURCE_INDEX(resourceTable
[i
].maxIndex
); index
++) {
4660 const ioTag_t ioTag
= *(ioTag_t
*)((const uint8_t *)currentConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
4661 ioTag_t ioTagDefault
= NULL
;
4662 if (defaultConfig
) {
4663 ioTagDefault
= *(ioTag_t
*)((const uint8_t *)defaultConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
4666 const bool equalsDefault
= ioTag
== ioTagDefault
;
4667 const char *format
= "resource %s %d %c%02d";
4668 const char *formatUnassigned
= "resource %s %d NONE";
4669 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
4671 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTagDefault
) + 'A', IO_GPIOPinIdxByTag(ioTagDefault
));
4672 } else if (defaultConfig
) {
4673 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
4676 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
));
4677 } else if (!(dumpMask
& HIDE_UNUSED
)) {
4678 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
4684 static void printResourceOwner(uint8_t owner
, uint8_t index
)
4686 cliPrintf("%s", ownerNames
[resourceTable
[owner
].owner
]);
4688 if (resourceTable
[owner
].maxIndex
> 0) {
4689 cliPrintf(" %d", RESOURCE_INDEX(index
));
4693 static void resourceCheck(uint8_t resourceIndex
, uint8_t index
, ioTag_t newTag
)
4699 const char * format
= "\r\nNOTE: %c%02d already assigned to ";
4700 for (int r
= 0; r
< (int)ARRAYLEN(resourceTable
); r
++) {
4701 for (int i
= 0; i
< MAX_RESOURCE_INDEX(resourceTable
[r
].maxIndex
); i
++) {
4702 ioTag_t
*tag
= getIoTag(resourceTable
[r
], i
);
4703 if (*tag
== newTag
) {
4704 bool cleared
= false;
4705 if (r
== resourceIndex
) {
4713 cliPrintf(format
, DEFIO_TAG_GPIOID(newTag
) + 'A', DEFIO_TAG_PIN(newTag
));
4715 printResourceOwner(r
, i
);
4719 printResourceOwner(r
, i
);
4720 cliPrintf(" disabled");
4729 static bool strToPin(char *pch
, ioTag_t
*tag
)
4731 if (strcasecmp(pch
, "NONE") == 0) {
4736 unsigned port
= (*pch
>= 'a') ? *pch
- 'a' : *pch
- 'A';
4742 *tag
= DEFIO_TAG_MAKE(port
, pin
);
4750 static void printDma(void)
4755 cliPrintLine("DMA:");
4757 cliPrintLine("Currently active DMA:");
4760 for (int i
= 1; i
<= DMA_LAST_HANDLER
; i
++) {
4762 owner
= ownerNames
[dmaGetOwner(i
)];
4764 cliPrintf(DMA_OUTPUT_STRING
, DMA_DEVICE_NO(i
), DMA_DEVICE_INDEX(i
));
4765 uint8_t resourceIndex
= dmaGetResourceIndex(i
);
4766 if (resourceIndex
> 0) {
4767 cliPrintLinef(" %s %d", owner
, resourceIndex
);
4769 cliPrintLinef(" %s", owner
);
4776 typedef struct dmaoptEntry_s
{
4778 dmaPeripheral_e peripheral
;
4785 // Handy macros for keeping the table tidy.
4786 // DEFS : Single entry
4787 // DEFA : Array of uint8_t (stride = 1)
4788 // DEFW : Wider stride case; array of structs.
4790 #define DEFS(device, peripheral, pgn, type, member) \
4791 { device, peripheral, pgn, 0, offsetof(type, member), 0 }
4793 #define DEFA(device, peripheral, pgn, type, member, max) \
4794 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max }
4796 #define DEFW(device, peripheral, pgn, type, member, max) \
4797 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max }
4799 dmaoptEntry_t dmaoptEntryTable
[] = {
4800 DEFW("SPI_TX", DMA_PERIPH_SPI_TX
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, txDmaopt
, SPIDEV_COUNT
),
4801 DEFW("SPI_RX", DMA_PERIPH_SPI_RX
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, rxDmaopt
, SPIDEV_COUNT
),
4802 DEFA("ADC", DMA_PERIPH_ADC
, PG_ADC_CONFIG
, adcConfig_t
, dmaopt
, ADCDEV_COUNT
),
4803 DEFS("SDIO", DMA_PERIPH_SDIO
, PG_SDIO_CONFIG
, sdioConfig_t
, dmaopt
),
4804 DEFA("UART_TX", DMA_PERIPH_UART_TX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, txDmaopt
, UARTDEV_CONFIG_MAX
),
4805 DEFA("UART_RX", DMA_PERIPH_UART_RX
, PG_SERIAL_UART_CONFIG
, serialUartConfig_t
, rxDmaopt
, UARTDEV_CONFIG_MAX
),
4812 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
4813 #define DMA_OPT_STRING_BUFSIZE 5
4815 static void optToString(int optval
, char *buf
)
4817 if (optval
== DMA_OPT_UNUSED
) {
4818 memcpy(buf
, "NONE", DMA_OPT_STRING_BUFSIZE
);
4820 tfp_sprintf(buf
, "%d", optval
);
4824 static void printPeripheralDmaoptDetails(dmaoptEntry_t
*entry
, int index
, const dmaoptValue_t dmaopt
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
4826 if (dmaopt
!= DMA_OPT_UNUSED
) {
4827 printValue(dumpMask
, equalsDefault
,
4829 entry
->device
, DMA_OPT_UI_INDEX(index
), dmaopt
);
4831 const dmaChannelSpec_t
*dmaChannelSpec
= dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, dmaopt
);
4832 dmaCode_t dmaCode
= 0;
4833 if (dmaChannelSpec
) {
4834 dmaCode
= dmaChannelSpec
->code
;
4836 printValue(dumpMask
, equalsDefault
,
4837 "# %s %d: DMA%d Stream %d Channel %d",
4838 entry
->device
, DMA_OPT_UI_INDEX(index
), DMA_CODE_CONTROLLER(dmaCode
), DMA_CODE_STREAM(dmaCode
), DMA_CODE_CHANNEL(dmaCode
));
4839 } else if (!(dumpMask
& HIDE_UNUSED
)) {
4840 printValue(dumpMask
, equalsDefault
,
4842 entry
->device
, DMA_OPT_UI_INDEX(index
));
4846 static const char *printPeripheralDmaopt(dmaoptEntry_t
*entry
, int index
, dumpFlags_t dumpMask
, const char *headingStr
)
4848 const pgRegistry_t
* pg
= pgFind(entry
->pgn
);
4849 const void *currentConfig
;
4850 const void *defaultConfig
;
4852 if (configIsInCopy
) {
4853 currentConfig
= pg
->copy
;
4854 defaultConfig
= pg
->address
;
4856 currentConfig
= pg
->address
;
4857 defaultConfig
= NULL
;
4860 dmaoptValue_t currentOpt
= *(dmaoptValue_t
*)((uint8_t *)currentConfig
+ entry
->stride
* index
+ entry
->offset
);
4861 dmaoptValue_t defaultOpt
;
4863 if (defaultConfig
) {
4864 defaultOpt
= *(dmaoptValue_t
*)((uint8_t *)defaultConfig
+ entry
->stride
* index
+ entry
->offset
);
4866 defaultOpt
= DMA_OPT_UNUSED
;
4869 bool equalsDefault
= currentOpt
== defaultOpt
;
4870 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
4872 if (defaultConfig
) {
4873 printPeripheralDmaoptDetails(entry
, index
, defaultOpt
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
4876 printPeripheralDmaoptDetails(entry
, index
, currentOpt
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
4880 #if defined(USE_TIMER_MGMT)
4881 static void printTimerDmaoptDetails(const ioTag_t ioTag
, const timerHardware_t
*timer
, const dmaoptValue_t dmaopt
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
4883 const char *format
= "dma pin %c%02d %d";
4885 if (dmaopt
!= DMA_OPT_UNUSED
) {
4886 const bool printDetails
= printValue(dumpMask
, equalsDefault
, format
,
4887 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
4892 const dmaChannelSpec_t
*dmaChannelSpec
= dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, dmaopt
);
4893 dmaCode_t dmaCode
= 0;
4894 if (dmaChannelSpec
) {
4895 dmaCode
= dmaChannelSpec
->code
;
4896 printValue(dumpMask
, false,
4897 "# pin %c%02d: DMA%d Stream %d Channel %d",
4898 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
4899 DMA_CODE_CONTROLLER(dmaCode
), DMA_CODE_STREAM(dmaCode
), DMA_CODE_CHANNEL(dmaCode
)
4903 } else if (!(dumpMask
& HIDE_UNUSED
)) {
4904 printValue(dumpMask
, equalsDefault
,
4905 "dma pin %c%02d NONE",
4906 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
)
4911 static const char *printTimerDmaopt(const timerIOConfig_t
*currentConfig
, const timerIOConfig_t
*defaultConfig
, unsigned index
, dumpFlags_t dumpMask
, bool tagsInUse
[], const char *headingStr
)
4913 const ioTag_t ioTag
= currentConfig
[index
].ioTag
;
4919 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, currentConfig
[index
].index
);
4920 const dmaoptValue_t dmaopt
= currentConfig
[index
].dmaopt
;
4922 dmaoptValue_t defaultDmaopt
= DMA_OPT_UNUSED
;
4923 bool equalsDefault
= defaultDmaopt
== dmaopt
;
4924 if (defaultConfig
) {
4925 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
4926 if (defaultConfig
[i
].ioTag
== ioTag
) {
4927 defaultDmaopt
= defaultConfig
[i
].dmaopt
;
4929 // 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.
4930 equalsDefault
= (defaultDmaopt
== dmaopt
) && (defaultConfig
[i
].index
== currentConfig
[index
].index
|| dmaopt
== DMA_OPT_UNUSED
);
4932 tagsInUse
[index
] = true;
4939 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
4941 if (defaultConfig
) {
4942 printTimerDmaoptDetails(ioTag
, timer
, defaultDmaopt
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
4945 printTimerDmaoptDetails(ioTag
, timer
, dmaopt
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
4950 static void printDmaopt(dumpFlags_t dumpMask
, const char *headingStr
)
4952 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
4953 for (size_t i
= 0; i
< ARRAYLEN(dmaoptEntryTable
); i
++) {
4954 dmaoptEntry_t
*entry
= &dmaoptEntryTable
[i
];
4955 for (int index
= 0; index
< entry
->maxIndex
; index
++) {
4956 headingStr
= printPeripheralDmaopt(entry
, index
, dumpMask
, headingStr
);
4960 #if defined(USE_TIMER_MGMT)
4961 const pgRegistry_t
* pg
= pgFind(PG_TIMER_IO_CONFIG
);
4962 const timerIOConfig_t
*currentConfig
;
4963 const timerIOConfig_t
*defaultConfig
;
4965 if (configIsInCopy
) {
4966 currentConfig
= (timerIOConfig_t
*)pg
->copy
;
4967 defaultConfig
= (timerIOConfig_t
*)pg
->address
;
4969 currentConfig
= (timerIOConfig_t
*)pg
->address
;
4970 defaultConfig
= NULL
;
4973 bool tagsInUse
[MAX_TIMER_PINMAP_COUNT
] = { false };
4974 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
4975 headingStr
= printTimerDmaopt(currentConfig
, defaultConfig
, i
, dumpMask
, tagsInUse
, headingStr
);
4978 if (defaultConfig
) {
4979 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
4980 if (!tagsInUse
[i
] && defaultConfig
[i
].ioTag
&& defaultConfig
[i
].dmaopt
!= DMA_OPT_UNUSED
) {
4981 const timerHardware_t
*timer
= timerGetByTagAndIndex(defaultConfig
[i
].ioTag
, defaultConfig
[i
].index
);
4982 headingStr
= cliPrintSectionHeading(dumpMask
, true, headingStr
);
4983 printTimerDmaoptDetails(defaultConfig
[i
].ioTag
, timer
, defaultConfig
[i
].dmaopt
, false, dumpMask
, cliDefaultPrintLinef
);
4985 printTimerDmaoptDetails(defaultConfig
[i
].ioTag
, timer
, DMA_OPT_UNUSED
, false, dumpMask
, cliDumpPrintLinef
);
4992 static void cliDmaopt(char *cmdline
)
4997 // Peripheral name or command option
4998 pch
= strtok_r(cmdline
, " ", &saveptr
);
5000 printDmaopt(DUMP_MASTER
| HIDE_UNUSED
, NULL
);
5003 } else if (strcasecmp(pch
, "list") == 0) {
5004 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5009 dmaoptEntry_t
*entry
= NULL
;
5010 for (unsigned i
= 0; i
< ARRAYLEN(dmaoptEntryTable
); i
++) {
5011 if (strcasecmp(pch
, dmaoptEntryTable
[i
].device
) == 0) {
5012 entry
= &dmaoptEntryTable
[i
];
5016 if (!entry
&& strcasecmp(pch
, "pin") != 0) {
5017 cliPrintErrorLinef("BAD DEVICE: %s", pch
);
5022 dmaoptValue_t orgval
= DMA_OPT_UNUSED
;
5025 dmaoptValue_t
*optaddr
= NULL
;
5027 ioTag_t ioTag
= IO_TAG_NONE
;
5028 #if defined(USE_TIMER_MGMT)
5029 timerIOConfig_t
*timerIoConfig
= NULL
;
5031 const timerHardware_t
*timer
= NULL
;
5032 pch
= strtok_r(NULL
, " ", &saveptr
);
5034 index
= atoi(pch
) - 1;
5035 if (index
< 0 || index
>= entry
->maxIndex
) {
5036 cliPrintErrorLinef("BAD INDEX: '%s'", pch
? pch
: "");
5040 const pgRegistry_t
* pg
= pgFind(entry
->pgn
);
5041 const void *currentConfig
;
5042 if (configIsInCopy
) {
5043 currentConfig
= pg
->copy
;
5045 currentConfig
= pg
->address
;
5047 optaddr
= (dmaoptValue_t
*)((uint8_t *)currentConfig
+ entry
->stride
* index
+ entry
->offset
);
5051 if (!pch
|| !(strToPin(pch
, &ioTag
) && IOGetByTag(ioTag
))) {
5052 cliPrintErrorLinef("INVALID PIN: '%s'", pch
? pch
: "");
5057 orgval
= dmaoptByTag(ioTag
);
5058 #if defined(USE_TIMER_MGMT)
5059 timerIoConfig
= timerIoConfigByTag(ioTag
);
5061 timer
= timerGetByTag(ioTag
);
5065 pch
= strtok_r(NULL
, " ", &saveptr
);
5068 printPeripheralDmaoptDetails(entry
, index
, *optaddr
, true, DUMP_MASTER
, cliDumpPrintLinef
);
5070 printTimerDmaoptDetails(ioTag
, timer
, orgval
, true, DUMP_MASTER
, cliDumpPrintLinef
);
5074 } else if (strcasecmp(pch
, "list") == 0) {
5075 // Show possible opts
5076 const dmaChannelSpec_t
*dmaChannelSpec
;
5078 for (int opt
= 0; (dmaChannelSpec
= dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, opt
)); opt
++) {
5079 cliPrintLinef("# %d: DMA%d Stream %d channel %d", opt
, DMA_CODE_CONTROLLER(dmaChannelSpec
->code
), DMA_CODE_STREAM(dmaChannelSpec
->code
), DMA_CODE_CHANNEL(dmaChannelSpec
->code
));
5082 for (int opt
= 0; (dmaChannelSpec
= dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, opt
)); opt
++) {
5083 cliPrintLinef("# %d: DMA%d Stream %d channel %d", opt
, DMA_CODE_CONTROLLER(dmaChannelSpec
->code
), DMA_CODE_STREAM(dmaChannelSpec
->code
), DMA_CODE_CHANNEL(dmaChannelSpec
->code
));
5090 if (strcasecmp(pch
, "none") == 0) {
5091 optval
= DMA_OPT_UNUSED
;
5096 if (!dmaGetChannelSpecByPeripheral(entry
->peripheral
, index
, optval
)) {
5097 cliPrintErrorLinef("INVALID DMA OPTION FOR %s %d: '%s'", entry
->device
, DMA_OPT_UI_INDEX(index
), pch
);
5102 if (!dmaGetChannelSpecByTimerValue(timer
->tim
, timer
->channel
, optval
)) {
5103 cliPrintErrorLinef("INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
5110 char optvalString
[DMA_OPT_STRING_BUFSIZE
];
5111 optToString(optval
, optvalString
);
5113 char orgvalString
[DMA_OPT_STRING_BUFSIZE
];
5114 optToString(orgval
, orgvalString
);
5116 if (optval
!= orgval
) {
5120 cliPrintLinef("# dma %s %d: changed from %s to %s", entry
->device
, DMA_OPT_UI_INDEX(index
), orgvalString
, optvalString
);
5122 #if defined(USE_TIMER_MGMT)
5123 timerIoConfig
->dmaopt
= optval
;
5126 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
, optvalString
);
5130 cliPrintLinef("# dma %s %d: no change: %s", entry
->device
, DMA_OPT_UI_INDEX(index
), orgvalString
);
5132 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),orgvalString
);
5137 #endif // USE_DMA_SPEC
5139 static void cliDma(char* cmdline
)
5141 int len
= strlen(cmdline
);
5142 if (len
&& strncasecmp(cmdline
, "show", len
) == 0) {
5148 #if defined(USE_DMA_SPEC)
5151 cliShowParseError();
5155 static void cliResource(char *cmdline
)
5160 pch
= strtok_r(cmdline
, " ", &saveptr
);
5162 printResource(DUMP_MASTER
| HIDE_UNUSED
, NULL
);
5165 } else if (strcasecmp(pch
, "show") == 0) {
5169 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
5172 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
5174 owner
= ownerNames
[ioRecs
[i
].owner
];
5176 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
);
5177 if (ioRecs
[i
].index
> 0) {
5178 cliPrintf(" %d", ioRecs
[i
].index
);
5183 pch
= strtok_r(NULL
, " ", &saveptr
);
5184 if (strcasecmp(pch
, "all") == 0) {
5191 uint8_t resourceIndex
= 0;
5193 for (resourceIndex
= 0; ; resourceIndex
++) {
5194 if (resourceIndex
>= ARRAYLEN(resourceTable
)) {
5195 cliPrintErrorLinef("INVALID RESOURCE NAME: '%s'", pch
);
5199 const char * resourceName
= ownerNames
[resourceTable
[resourceIndex
].owner
];
5200 if (strncasecmp(pch
, resourceName
, strlen(resourceName
)) == 0) {
5205 pch
= strtok_r(NULL
, " ", &saveptr
);
5208 if (resourceTable
[resourceIndex
].maxIndex
> 0 || index
> 0) {
5209 if (index
<= 0 || index
> MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
)) {
5210 cliShowArgumentRangeError("INDEX", 1, MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
));
5215 pch
= strtok_r(NULL
, " ", &saveptr
);
5218 ioTag_t
*tag
= getIoTag(resourceTable
[resourceIndex
], index
);
5220 if (strlen(pch
) > 0) {
5221 if (strToPin(pch
, tag
)) {
5222 if (*tag
== IO_TAG_NONE
) {
5224 cliPrintLine("Freed");
5226 cliPrintLine("Resource is freed");
5230 ioRec_t
*rec
= IO_Rec(IOGetByTag(*tag
));
5232 resourceCheck(resourceIndex
, index
, *tag
);
5234 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
5236 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
5239 cliShowParseError();
5246 cliShowParseError();
5249 #endif // USE_RESOURCE_MGMT
5251 #ifdef USE_TIMER_MGMT
5252 static void printTimerDetails(const ioTag_t ioTag
, const unsigned timerIndex
, const bool equalsDefault
, const dumpFlags_t dumpMask
, printFn
*printValue
)
5254 const char *format
= "timer %c%02d af%d";
5255 const char *emptyFormat
= "timer %c%02d NONE";
5257 if (timerIndex
> 0) {
5258 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, timerIndex
);
5259 const bool printDetails
= printValue(dumpMask
, equalsDefault
, format
,
5260 IO_GPIOPortIdxByTag(ioTag
) + 'A',
5261 IO_GPIOPinIdxByTag(ioTag
),
5262 timer
->alternateFunction
5265 printValue(dumpMask
, false,
5266 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5267 IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
),
5268 timerGetTIMNumber(timer
->tim
),
5269 CC_INDEX_FROM_CHANNEL(timer
->channel
) + 1,
5270 timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : "",
5271 timer
->alternateFunction
5275 printValue(dumpMask
, equalsDefault
, emptyFormat
,
5276 IO_GPIOPortIdxByTag(ioTag
) + 'A',
5277 IO_GPIOPinIdxByTag(ioTag
)
5282 static void printTimer(dumpFlags_t dumpMask
, const char *headingStr
)
5284 const pgRegistry_t
* pg
= pgFind(PG_TIMER_IO_CONFIG
);
5285 const timerIOConfig_t
*currentConfig
;
5286 const timerIOConfig_t
*defaultConfig
;
5288 headingStr
= cliPrintSectionHeading(dumpMask
, false, headingStr
);
5289 if (configIsInCopy
) {
5290 currentConfig
= (timerIOConfig_t
*)pg
->copy
;
5291 defaultConfig
= (timerIOConfig_t
*)pg
->address
;
5293 currentConfig
= (timerIOConfig_t
*)pg
->address
;
5294 defaultConfig
= NULL
;
5297 bool tagsInUse
[MAX_TIMER_PINMAP_COUNT
] = { false };
5298 for (unsigned int i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5299 const ioTag_t ioTag
= currentConfig
[i
].ioTag
;
5305 const uint8_t timerIndex
= currentConfig
[i
].index
;
5307 uint8_t defaultTimerIndex
= 0;
5308 if (defaultConfig
) {
5309 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5310 if (defaultConfig
[i
].ioTag
== ioTag
) {
5311 defaultTimerIndex
= defaultConfig
[i
].index
;
5312 tagsInUse
[i
] = true;
5319 const bool equalsDefault
= defaultTimerIndex
== timerIndex
;
5320 headingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, headingStr
);
5321 if (defaultConfig
&& defaultTimerIndex
) {
5322 printTimerDetails(ioTag
, defaultTimerIndex
, equalsDefault
, dumpMask
, cliDefaultPrintLinef
);
5325 printTimerDetails(ioTag
, timerIndex
, equalsDefault
, dumpMask
, cliDumpPrintLinef
);
5328 if (defaultConfig
) {
5329 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5330 if (!tagsInUse
[i
] && defaultConfig
[i
].ioTag
) {
5331 headingStr
= cliPrintSectionHeading(DO_DIFF
, true, headingStr
);
5332 printTimerDetails(defaultConfig
[i
].ioTag
, defaultConfig
[i
].index
, false, dumpMask
, cliDefaultPrintLinef
);
5334 printTimerDetails(defaultConfig
[i
].ioTag
, 0, false, dumpMask
, cliDumpPrintLinef
);
5340 #define TIMER_INDEX_UNDEFINED -1
5341 #define TIMER_AF_STRING_BUFSIZE 5
5343 static void alternateFunctionToString(const ioTag_t ioTag
, const int index
, char *buf
)
5345 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, index
+ 1);
5347 memcpy(buf
, "NONE", TIMER_AF_STRING_BUFSIZE
);
5349 tfp_sprintf(buf
, "af%d", timer
->alternateFunction
);
5353 static void cliTimer(char *cmdline
)
5355 int len
= strlen(cmdline
);
5358 printTimer(DUMP_MASTER
, NULL
);
5361 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
5362 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5365 } else if (strncasecmp(cmdline
, "show", len
) == 0) {
5366 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5374 ioTag_t ioTag
= IO_TAG_NONE
;
5375 pch
= strtok_r(cmdline
, " ", &saveptr
);
5376 if (!pch
|| !strToPin(pch
, &ioTag
)) {
5377 cliShowParseError();
5380 } else if (!IOGetByTag(ioTag
)) {
5381 cliPrintErrorLinef("PIN NOT USED ON BOARD.");
5386 int timerIOIndex
= TIMER_INDEX_UNDEFINED
;
5387 bool isExistingTimerOpt
= false;
5388 /* find existing entry, or go for next available */
5389 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
5390 if (timerIOConfig(i
)->ioTag
== ioTag
) {
5392 isExistingTimerOpt
= true;
5397 /* first available empty slot */
5398 if (timerIOIndex
< 0 && timerIOConfig(i
)->ioTag
== IO_TAG_NONE
) {
5403 if (timerIOIndex
< 0) {
5404 cliPrintErrorLinef("PIN TIMER MAP FULL.");
5409 pch
= strtok_r(NULL
, " ", &saveptr
);
5412 if (strcasecmp(pch
, "list") == 0) {
5413 /* output the list of available options */
5414 const timerHardware_t
*timer
;
5415 for (unsigned index
= 0; (timer
= timerGetByTagAndIndex(ioTag
, index
+ 1)); index
++) {
5416 cliPrintLinef("# af%d: TIM%d CH%d%s",
5417 timer
->alternateFunction
,
5418 timerGetTIMNumber(timer
->tim
),
5419 CC_INDEX_FROM_CHANNEL(timer
->channel
) + 1,
5420 timer
->output
& TIMER_OUTPUT_N_CHANNEL
? "N" : ""
5425 } else if (strcasecmp(pch
, "none") == 0) {
5426 timerIndex
= TIMER_INDEX_UNDEFINED
;
5427 } else if (strncasecmp(pch
, "af", 2) == 0) {
5428 unsigned alternateFunction
= atoi(&pch
[2]);
5430 const timerHardware_t
*timer
;
5431 for (unsigned index
= 0; (timer
= timerGetByTagAndIndex(ioTag
, index
+ 1)); index
++) {
5432 if (timer
->alternateFunction
== alternateFunction
) {
5440 cliPrintErrorLinef("INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
5445 timerIndex
= atoi(pch
);
5447 const timerHardware_t
*timer
= timerGetByTagAndIndex(ioTag
, timerIndex
+ 1);
5450 cliPrintErrorLinef("INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), pch
);
5456 int oldTimerIndex
= isExistingTimerOpt
? timerIOConfig(timerIOIndex
)->index
- 1 : -1;
5457 timerIOConfigMutable(timerIOIndex
)->ioTag
= timerIndex
== TIMER_INDEX_UNDEFINED
? IO_TAG_NONE
: ioTag
;
5458 timerIOConfigMutable(timerIOIndex
)->index
= timerIndex
+ 1;
5459 timerIOConfigMutable(timerIOIndex
)->dmaopt
= DMA_OPT_UNUSED
;
5461 char optvalString
[DMA_OPT_STRING_BUFSIZE
];
5462 alternateFunctionToString(ioTag
, timerIndex
, optvalString
);
5464 char orgvalString
[DMA_OPT_STRING_BUFSIZE
];
5465 alternateFunctionToString(ioTag
, oldTimerIndex
, orgvalString
);
5467 if (timerIndex
== oldTimerIndex
) {
5468 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
);
5470 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
), orgvalString
, optvalString
);
5475 printTimerDetails(ioTag
, timerIOConfig(timerIOIndex
)->index
, false, DUMP_MASTER
, cliDumpPrintLinef
);
5482 #ifdef USE_DSHOT_TELEMETRY
5483 static void cliDshotTelemetryInfo(char *cmdline
)
5487 if (useDshotTelemetry
) {
5488 cliPrintLinef("Dshot reads: %u", readDoneCount
);
5489 cliPrintLinef("Dshot invalid pkts: %u", dshotInvalidPacketCount
);
5490 extern uint32_t setDirectionMicros
;
5491 cliPrintLinef("Dshot irq micros: %u", setDirectionMicros
);
5494 #ifdef USE_DSHOT_TELEMETRY_STATS
5495 cliPrintLine("Motor RPM Invalid");
5496 cliPrintLine("===== ===== =======");
5498 cliPrintLine("Motor RPM");
5499 cliPrintLine("===== =====");
5501 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
5502 cliPrintf("%5d %5d ", i
, (int)getDshotTelemetry(i
));
5503 #ifdef USE_DSHOT_TELEMETRY_STATS
5504 if (isDshotMotorTelemetryActive(i
)) {
5505 const int calcPercent
= getDshotTelemetryMotorInvalidPercent(i
);
5506 cliPrintLinef("%3d.%02d%%", calcPercent
/ 100, calcPercent
% 100);
5508 cliPrintLine("NO DATA");
5516 const bool proshot
= (motorConfig()->dev
.motorPwmProtocol
== PWM_TYPE_PROSHOT1000
);
5517 const int modulo
= proshot
? MOTOR_NIBBLE_LENGTH_PROSHOT
: MOTOR_BITLENGTH
;
5518 const int len
= proshot
? 8 : DSHOT_TELEMETRY_INPUT_LEN
;
5519 for (int i
= 0; i
< len
; i
++) {
5520 cliPrintf("%u ", (int)inputBuffer
[i
]);
5523 for (int i
= 1; i
< len
; i
+=2) {
5524 cliPrintf("%u ", (int)(inputBuffer
[i
] + modulo
- inputBuffer
[i
-1]) % modulo
);
5528 cliPrintLine("Dshot telemetry not enabled");
5533 static void printConfig(char *cmdline
, bool doDiff
)
5535 dumpFlags_t dumpMask
= DUMP_MASTER
;
5537 if ((options
= checkCommand(cmdline
, "master"))) {
5538 dumpMask
= DUMP_MASTER
; // only
5539 } else if ((options
= checkCommand(cmdline
, "profile"))) {
5540 dumpMask
= DUMP_PROFILE
; // only
5541 } else if ((options
= checkCommand(cmdline
, "rates"))) {
5542 dumpMask
= DUMP_RATES
; // only
5543 } else if ((options
= checkCommand(cmdline
, "hardware"))) {
5544 dumpMask
= DUMP_MASTER
| HARDWARE_ONLY
; // Show only hardware related settings (useful to generate unified target configs).
5545 } else if ((options
= checkCommand(cmdline
, "all"))) {
5546 dumpMask
= DUMP_ALL
; // all profiles and rates
5552 dumpMask
= dumpMask
| DO_DIFF
;
5555 if (checkCommand(options
, "defaults")) {
5556 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
5557 } else if (checkCommand(options
, "bare")) {
5558 dumpMask
= dumpMask
| BARE
; // show the diff / dump without extra commands and board specific data
5561 backupAndResetConfigs();
5563 #ifdef USE_CLI_BATCH
5564 bool batchModeEnabled
= false;
5566 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
5567 cliPrintHashLine("version");
5570 if (!(dumpMask
& BARE
)) {
5571 #ifdef USE_CLI_BATCH
5572 cliPrintHashLine("start the command batch");
5573 cliPrintLine("batch start");
5574 batchModeEnabled
= true;
5577 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
5578 cliPrintHashLine("reset configuration to default settings");
5579 cliPrintLine("defaults nosave");
5583 #if defined(USE_BOARD_INFO)
5585 printBoardName(dumpMask
);
5586 printManufacturerId(dumpMask
);
5589 if ((dumpMask
& DUMP_ALL
) && !(dumpMask
& BARE
)) {
5591 #if defined(USE_SIGNATURE)
5596 if (!(dumpMask
& HARDWARE_ONLY
)) {
5597 printName(dumpMask
, &pilotConfig_Copy
);
5600 #ifdef USE_RESOURCE_MGMT
5601 printResource(dumpMask
, "resources");
5602 #if defined(USE_TIMER_MGMT)
5603 printTimer(dumpMask
, "timer");
5606 printDmaopt(dumpMask
, "dma");
5610 if (!(dumpMask
& HARDWARE_ONLY
)) {
5611 #ifndef USE_QUAD_MIXER_ONLY
5612 const char *mixerHeadingStr
= "mixer";
5613 const bool equalsDefault
= mixerConfig_Copy
.mixerMode
== mixerConfig()->mixerMode
;
5614 mixerHeadingStr
= cliPrintSectionHeading(dumpMask
, !equalsDefault
, mixerHeadingStr
);
5615 const char *formatMixer
= "mixer %s";
5616 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig()->mixerMode
- 1]);
5617 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig_Copy
.mixerMode
- 1]);
5619 cliDumpPrintLinef(dumpMask
, customMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
5621 printMotorMix(dumpMask
, customMotorMixer_CopyArray
, customMotorMixer(0), mixerHeadingStr
);
5624 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0), "servo");
5626 const char *servoMixHeadingStr
= "servo mixer";
5627 if (!(dumpMask
& DO_DIFF
) || customServoMixers(0)->rate
!= 0) {
5628 cliPrintHashLine(servoMixHeadingStr
);
5629 cliPrintLine("smix reset\r\n");
5630 servoMixHeadingStr
= NULL
;
5632 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0), servoMixHeadingStr
);
5636 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig(), "feature");
5638 #if defined(USE_BEEPER)
5639 printBeeper(dumpMask
, beeperConfig_Copy
.beeper_off_flags
, beeperConfig()->beeper_off_flags
, "beeper", BEEPER_ALLOWED_MODES
, "beeper");
5641 #if defined(USE_DSHOT)
5642 printBeeper(dumpMask
, beeperConfig_Copy
.dshotBeaconOffFlags
, beeperConfig()->dshotBeaconOffFlags
, "beacon", DSHOT_BEACON_ALLOWED_MODES
, "beacon");
5644 #endif // USE_BEEPER
5646 printMap(dumpMask
, &rxConfig_Copy
, rxConfig(), "map");
5648 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig(), "serial");
5650 #ifdef USE_LED_STRIP_STATUS_MODE
5651 printLed(dumpMask
, ledStripStatusModeConfig_Copy
.ledConfigs
, ledStripStatusModeConfig()->ledConfigs
, "led");
5653 printColor(dumpMask
, ledStripStatusModeConfig_Copy
.colors
, ledStripStatusModeConfig()->colors
, "color");
5655 printModeColor(dumpMask
, &ledStripStatusModeConfig_Copy
, ledStripStatusModeConfig(), "mode_color");
5658 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0), "aux");
5660 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0), "adjrange");
5662 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0), "rxrange");
5664 #ifdef USE_VTX_CONTROL
5665 printVtx(dumpMask
, &vtxConfig_Copy
, vtxConfig(), "vtx");
5668 #ifdef USE_VTX_TABLE
5669 printVtxTable(dumpMask
, &vtxTableConfig_Copy
, vtxTableConfig(), "vtxtable");
5672 printRxFailsafe(dumpMask
, rxFailsafeChannelConfigs_CopyArray
, rxFailsafeChannelConfigs(0), "rxfail");
5675 if (dumpMask
& HARDWARE_ONLY
) {
5676 dumpAllValues(HARDWARE_VALUE
, dumpMask
, "master");
5678 dumpAllValues(MASTER_VALUE
, dumpMask
, "master");
5680 if (dumpMask
& DUMP_ALL
) {
5681 for (uint32_t pidProfileIndex
= 0; pidProfileIndex
< PID_PROFILE_COUNT
; pidProfileIndex
++) {
5682 cliDumpPidProfile(pidProfileIndex
, dumpMask
);
5685 pidProfileIndexToUse
= systemConfig_Copy
.pidProfileIndex
;
5687 if (!(dumpMask
& BARE
)) {
5688 cliPrintHashLine("restore original profile selection");
5693 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
5695 for (uint32_t rateIndex
= 0; rateIndex
< CONTROL_RATE_PROFILE_COUNT
; rateIndex
++) {
5696 cliDumpRateProfile(rateIndex
, dumpMask
);
5699 rateProfileIndexToUse
= systemConfig_Copy
.activeRateProfile
;
5701 if (!(dumpMask
& BARE
)) {
5702 cliPrintHashLine("restore original rateprofile selection");
5706 cliPrintHashLine("save configuration");
5708 #ifdef USE_CLI_BATCH
5709 batchModeEnabled
= false;
5713 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
5715 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
5717 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
5720 } else if (dumpMask
& DUMP_PROFILE
) {
5721 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
5722 } else if (dumpMask
& DUMP_RATES
) {
5723 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
5726 #ifdef USE_CLI_BATCH
5727 if (batchModeEnabled
) {
5728 cliPrintHashLine("end the command batch");
5729 cliPrintLine("batch end");
5733 // restore configs from copies
5737 static void cliDump(char *cmdline
)
5739 printConfig(cmdline
, false);
5742 static void cliDiff(char *cmdline
)
5744 printConfig(cmdline
, true);
5747 #if defined(USE_USB_MSC)
5748 static void cliMsc(char *cmdline
)
5750 if (mscCheckFilesystemReady()) {
5752 int timezoneOffsetMinutes
= timeConfig()->tz_offsetMinutes
;
5753 if (!isEmpty(cmdline
)) {
5754 timezoneOffsetMinutes
= atoi(cmdline
);
5755 if ((timezoneOffsetMinutes
< TIMEZONE_OFFSET_MINUTES_MIN
) || (timezoneOffsetMinutes
> TIMEZONE_OFFSET_MINUTES_MAX
)) {
5756 cliPrintErrorLinef("INVALID TIMEZONE OFFSET");
5761 int timezoneOffsetMinutes
= 0;
5764 cliPrintHashLine("Restarting in mass storage mode");
5765 cliPrint("\r\nRebooting");
5766 bufWriterFlush(cliWriter
);
5767 waitForSerialPortToFinishTransmitting(cliPort
);
5770 systemResetToMsc(timezoneOffsetMinutes
);
5772 cliPrintHashLine("Storage not present or failed to initialize!");
5781 const char *description
;
5784 void (*func
)(char *cmdline
);
5788 #define CLI_COMMAND_DEF(name, description, args, method) \
5796 #define CLI_COMMAND_DEF(name, description, args, method) \
5803 static void cliHelp(char *cmdline
);
5805 // should be sorted a..z for bsearch()
5806 const clicmd_t cmdTable
[] = {
5807 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
5808 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux
),
5809 #ifdef USE_CLI_BATCH
5810 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch
),
5812 #if defined(USE_BEEPER)
5813 #if defined(USE_DSHOT)
5814 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
5815 "\t<->[name]", cliBeacon
),
5817 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
5818 "\t<->[name]", cliBeeper
),
5819 #endif // USE_BEEPER
5821 CLI_COMMAND_DEF("bind_rx_spi", "initiate binding for RX SPI", NULL
, cliRxSpiBind
),
5823 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL
, cliBootloader
),
5824 #if defined(USE_BOARD_INFO)
5825 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName
),
5827 #ifdef USE_LED_STRIP_STATUS_MODE
5828 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
5830 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults
),
5831 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff
),
5832 #ifdef USE_RESOURCE_MGMT
5834 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma
),
5836 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma
),
5839 #ifdef USE_DSHOT_TELEMETRY
5840 CLI_COMMAND_DEF("dshot_telemetry_info", "disply dshot telemetry info and stats", NULL
, cliDshotTelemetryInfo
),
5843 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg
),
5845 CLI_COMMAND_DEF("dump", "dump configuration",
5846 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump
),
5847 #ifdef USE_ESCSERIAL
5848 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough
),
5850 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
5851 CLI_COMMAND_DEF("feature", "configure features",
5853 "\t<->[name]", cliFeature
),
5855 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
5856 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
5857 #ifdef USE_FLASH_TOOLS
5858 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
5859 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL
, cliFlashVerify
),
5860 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
5863 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
5865 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
5867 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
5868 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL
, cliDumpGyroRegisters
),
5870 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
5871 #ifdef USE_LED_STRIP_STATUS_MODE
5872 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
5874 #if defined(USE_BOARD_INFO)
5875 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId
),
5877 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
5878 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL
, cliMcuId
),
5879 #ifndef USE_QUAD_MIXER_ONLY
5880 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer
),
5882 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
5883 #ifdef USE_LED_STRIP_STATUS_MODE
5884 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
5886 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
5889 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc
),
5891 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
5894 CLI_COMMAND_DEF("name", "name of craft", NULL
, cliName
),
5896 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]", cliPlaySound
),
5898 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile
),
5899 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile
),
5900 #ifdef USE_RC_SMOOTHING_FILTER
5901 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL
, cliRcSmoothing
),
5902 #endif // USE_RC_SMOOTHING_FILTER
5903 #ifdef USE_RESOURCE_MGMT
5904 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource
),
5906 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL
, cliRxFailsafe
),
5907 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
5908 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
5910 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
5912 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
5913 #if defined(USE_SERIAL_PASSTHROUGH)
5914 #if defined(USE_PINIO)
5915 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [dtr pinio|'reset']", cliSerialPassthrough
),
5917 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] ['reset']", cliSerialPassthrough
),
5921 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
5923 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
5924 #if defined(USE_SIGNATURE)
5925 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature
),
5928 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
5930 "\tload <mixer>\r\n"
5931 "\treverse <servo> <source> r|n", cliServoMix
),
5933 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
5934 #if defined(USE_TASK_STATISTICS)
5935 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
5937 #ifdef USE_TIMER_MGMT
5938 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<altenate function>|none|<option(deprecated)>] | list | show", cliTimer
),
5940 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
5941 #ifdef USE_VTX_CONTROL
5943 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL
, cliVtx
),
5945 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx
),
5948 #ifdef USE_VTX_TABLE
5949 CLI_COMMAND_DEF("vtxtable", "vtx frequency able", "<band> <bandname> <bandletter> <freq> ... <freq>\r\n", cliVtxTable
),
5953 static void cliHelp(char *cmdline
)
5957 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
5958 cliPrint(cmdTable
[i
].name
);
5960 if (cmdTable
[i
].description
) {
5961 cliPrintf(" - %s", cmdTable
[i
].description
);
5963 if (cmdTable
[i
].args
) {
5964 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
5971 void cliProcess(void)
5977 // Be a little bit tricky. Flush the last inputs buffer, if any.
5978 bufWriterFlush(cliWriter
);
5980 while (serialRxBytesWaiting(cliPort
)) {
5981 uint8_t c
= serialRead(cliPort
);
5982 if (c
== '\t' || c
== '?') {
5983 // do tab completion
5984 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
5985 uint32_t i
= bufferIndex
;
5986 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
5987 if (bufferIndex
&& (strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
5993 if (pstart
) { /* Buffer matches one or more commands */
5994 for (; ; bufferIndex
++) {
5995 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
5997 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
5998 /* Unambiguous -- append a space */
5999 cliBuffer
[bufferIndex
++] = ' ';
6000 cliBuffer
[bufferIndex
] = '\0';
6003 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
6006 if (!bufferIndex
|| pstart
!= pend
) {
6007 /* Print list of ambiguous matches */
6008 cliPrint("\r\033[K");
6009 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
6010 cliPrint(cmd
->name
);
6014 i
= 0; /* Redraw prompt */
6016 for (; i
< bufferIndex
; i
++)
6017 cliWrite(cliBuffer
[i
]);
6018 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
6021 } else if (c
== 12) { // NewPage / CTRL-L
6023 cliPrint("\033[2J\033[1;1H");
6025 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
6029 // Strip comment starting with # from line
6030 char *p
= cliBuffer
;
6033 bufferIndex
= (uint32_t)(p
- cliBuffer
);
6036 // Strip trailing whitespace
6037 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
6041 // Process non-empty lines
6042 if (bufferIndex
> 0) {
6043 cliBuffer
[bufferIndex
] = 0; // null terminate
6045 const clicmd_t
*cmd
;
6047 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
6048 if ((options
= checkCommand(cliBuffer
, cmd
->name
))) {
6052 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
)) {
6055 cliPrintError("UNKNOWN COMMAND, TRY 'HELP'");
6060 memset(cliBuffer
, 0, sizeof(cliBuffer
));
6062 // 'exit' will reset this flag, so we don't need to print prompt again
6067 } else if (c
== 127) {
6070 cliBuffer
[--bufferIndex
] = 0;
6071 cliPrint("\010 \010");
6073 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
6074 if (!bufferIndex
&& c
== ' ')
6075 continue; // Ignore leading spaces
6076 cliBuffer
[bufferIndex
++] = c
;
6082 void cliEnter(serialPort_t
*serialPort
)
6085 cliPort
= serialPort
;
6086 setPrintfSerialPort(cliPort
);
6087 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
6089 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics
);
6092 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6094 cliPrintLine("\r\nCLI");
6096 setArmingDisabled(ARMING_DISABLED_CLI
);
6100 #ifdef USE_CLI_BATCH
6101 resetCommandBatch();
6105 void cliInit(const serialConfig_t
*serialConfig
)
6107 UNUSED(serialConfig
);