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"
49 #include "common/axis.h"
50 #include "common/color.h"
51 #include "common/maths.h"
52 #include "common/printf.h"
53 #include "common/strtol.h"
54 #include "common/time.h"
55 #include "common/typeconversion.h"
56 #include "common/utils.h"
58 #include "config/config_eeprom.h"
59 #include "config/feature.h"
61 #include "drivers/accgyro/accgyro.h"
62 #include "drivers/adc.h"
63 #include "drivers/buf_writer.h"
64 #include "drivers/bus_spi.h"
65 #include "drivers/camera_control.h"
66 #include "drivers/compass/compass.h"
67 #include "drivers/display.h"
68 #include "drivers/dma.h"
69 #include "drivers/flash.h"
70 #include "drivers/inverter.h"
71 #include "drivers/io.h"
72 #include "drivers/io_impl.h"
73 #include "drivers/light_led.h"
74 #include "drivers/rangefinder/rangefinder_hcsr04.h"
75 #include "drivers/sdcard.h"
76 #include "drivers/sensor.h"
77 #include "drivers/serial.h"
78 #include "drivers/serial_escserial.h"
79 #include "drivers/sound_beeper.h"
80 #include "drivers/stack_check.h"
81 #include "drivers/system.h"
82 #include "drivers/time.h"
83 #include "drivers/timer.h"
84 #include "drivers/transponder_ir.h"
85 #include "drivers/usb_msc.h"
86 #include "drivers/vtx_common.h"
88 #include "fc/board_info.h"
89 #include "fc/config.h"
90 #include "fc/controlrate_profile.h"
93 #include "fc/rc_adjustments.h"
94 #include "fc/rc_controls.h"
95 #include "fc/runtime_config.h"
97 #include "flight/failsafe.h"
98 #include "flight/imu.h"
99 #include "flight/mixer.h"
100 #include "flight/pid.h"
101 #include "flight/position.h"
102 #include "flight/servos.h"
104 #include "interface/cli.h"
105 #include "interface/msp.h"
106 #include "interface/msp_box.h"
107 #include "interface/msp_protocol.h"
108 #include "interface/settings.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"
117 #include "io/serial.h"
118 #include "io/transponder_ir.h"
119 #include "io/usb_msc.h"
120 #include "io/vtx_control.h"
124 #include "pg/beeper.h"
125 #include "pg/beeper_dev.h"
126 #include "pg/board.h"
127 #include "pg/bus_i2c.h"
128 #include "pg/bus_spi.h"
129 #include "pg/max7456.h"
130 #include "pg/pinio.h"
132 #include "pg/pg_ids.h"
134 #include "pg/rx_spi.h"
135 #include "pg/rx_pwm.h"
136 #include "pg/timerio.h"
140 #include "rx/spektrum.h"
141 #include "rx/cc2500_frsky_common.h"
142 #include "rx/cc2500_frsky_x.h"
144 #include "scheduler/scheduler.h"
146 #include "sensors/acceleration.h"
147 #include "sensors/adcinternal.h"
148 #include "sensors/barometer.h"
149 #include "sensors/battery.h"
150 #include "sensors/boardalignment.h"
151 #include "sensors/compass.h"
152 #include "sensors/esc_sensor.h"
153 #include "sensors/gyro.h"
154 #include "sensors/sensors.h"
156 #include "telemetry/frsky_hub.h"
157 #include "telemetry/telemetry.h"
160 static serialPort_t
*cliPort
;
163 #define CLI_IN_BUFFER_SIZE 128
165 // Space required to set array parameters
166 #define CLI_IN_BUFFER_SIZE 256
168 #define CLI_OUT_BUFFER_SIZE 64
170 static bufWriter_t
*cliWriter
;
171 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + CLI_OUT_BUFFER_SIZE
];
173 static char cliBuffer
[CLI_IN_BUFFER_SIZE
];
174 static uint32_t bufferIndex
= 0;
176 static bool configIsInCopy
= false;
178 #define CURRENT_PROFILE_INDEX -1
179 static int8_t pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
180 static int8_t rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
182 static bool featureMaskIsCopied
= false;
183 static uint32_t featureMaskCopy
;
185 #if defined(USE_BOARD_INFO)
186 static bool boardInformationUpdated
= false;
187 #if defined(USE_SIGNATURE)
188 static bool signatureUpdated
= false;
190 #endif // USE_BOARD_INFO
192 static const char* const emptyName
= "-";
193 static const char* const emptyString
= "";
195 #ifndef USE_QUAD_MIXER_ONLY
196 // sync this with mixerMode_e
197 static const char * const mixerNames
[] = {
198 "TRI", "QUADP", "QUADX", "BI",
199 "GIMBAL", "Y6", "HEX6",
200 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
201 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
202 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
203 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
207 // sync this with features_e
208 static const char * const featureNames
[] = {
209 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
210 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
211 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
212 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
213 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
214 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
217 // sync this with rxFailsafeChannelMode_e
218 static const char rxFailsafeModeCharacters
[] = "ahs";
220 static const rxFailsafeChannelMode_e rxFailsafeModesTable
[RX_FAILSAFE_TYPE_COUNT
][RX_FAILSAFE_MODE_COUNT
] = {
221 { RX_FAILSAFE_MODE_AUTO
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_INVALID
},
222 { RX_FAILSAFE_MODE_INVALID
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
}
225 #if defined(USE_SENSOR_NAMES)
226 // sync this with sensors_e
227 static const char * const sensorTypeNames
[] = {
228 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
231 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
233 static const char * const *sensorHardwareNames
[] = {
234 lookupTableGyroHardware
, lookupTableAccHardware
, lookupTableBaroHardware
, lookupTableMagHardware
, lookupTableRangefinderHardware
236 #endif // USE_SENSOR_NAMES
238 static void backupPgConfig(const pgRegistry_t
*pg
)
240 memcpy(pg
->copy
, pg
->address
, pg
->size
);
243 static void restorePgConfig(const pgRegistry_t
*pg
)
245 memcpy(pg
->address
, pg
->copy
, pg
->size
);
248 static void backupConfigs(void)
250 // make copies of configs to do differencing
255 configIsInCopy
= true;
258 static void restoreConfigs(void)
264 configIsInCopy
= false;
267 static void backupAndResetConfigs(void)
270 // reset all configs to defaults to do differencing
274 static void cliPrint(const char *str
)
277 bufWriterAppend(cliWriter
, *str
++);
279 bufWriterFlush(cliWriter
);
282 static void cliPrintLinefeed(void)
287 static void cliPrintLine(const char *str
)
294 #define cliPrintHashLine(str)
296 static void cliPrintHashLine(const char *str
)
303 static void cliPutp(void *p
, char ch
)
305 bufWriterAppend(p
, ch
);
309 DUMP_MASTER
= (1 << 0),
310 DUMP_PROFILE
= (1 << 1),
311 DUMP_RATES
= (1 << 2),
314 SHOW_DEFAULTS
= (1 << 5),
315 HIDE_UNUSED
= (1 << 6)
318 static void cliPrintfva(const char *format
, va_list va
)
320 tfp_format(cliWriter
, cliPutp
, format
, va
);
321 bufWriterFlush(cliWriter
);
324 static bool cliDumpPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
326 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
328 va_start(va
, format
);
329 cliPrintfva(format
, va
);
338 static void cliWrite(uint8_t ch
)
340 bufWriterAppend(cliWriter
, ch
);
343 static bool cliDefaultPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
345 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
349 va_start(va
, format
);
350 cliPrintfva(format
, va
);
359 static void cliPrintf(const char *format
, ...)
362 va_start(va
, format
);
363 cliPrintfva(format
, va
);
368 static void cliPrintLinef(const char *format
, ...)
371 va_start(va
, format
);
372 cliPrintfva(format
, va
);
377 static void cliPrintErrorLinef(const char *format
, ...)
379 cliPrint("###ERROR### ");
381 va_start(va
, format
);
382 cliPrintfva(format
, va
);
387 static void cliPrintCorruptMessage(int value
)
389 cliPrintf("%d ###CORRUPTED CONFIG###", value
);
392 static void printValuePointer(const clivalue_t
*var
, const void *valuePointer
, bool full
)
394 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
395 for (int i
= 0; i
< var
->config
.array
.length
; i
++) {
396 switch (var
->type
& VALUE_TYPE_MASK
) {
400 cliPrintf("%d", ((uint8_t *)valuePointer
)[i
]);
405 cliPrintf("%d", ((int8_t *)valuePointer
)[i
]);
410 cliPrintf("%d", ((uint16_t *)valuePointer
)[i
]);
415 cliPrintf("%d", ((int16_t *)valuePointer
)[i
]);
419 if (i
< var
->config
.array
.length
- 1) {
426 switch (var
->type
& VALUE_TYPE_MASK
) {
428 value
= *(uint8_t *)valuePointer
;
432 value
= *(int8_t *)valuePointer
;
437 value
= *(int16_t *)valuePointer
;
440 value
= *(uint32_t *)valuePointer
;
444 switch (var
->type
& VALUE_MODE_MASK
) {
446 if ((value
< var
->config
.minmax
.min
) || (value
> var
->config
.minmax
.max
)) {
447 cliPrintCorruptMessage(value
);
449 cliPrintf("%d", value
);
451 cliPrintf(" %d %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
456 if (value
< lookupTables
[var
->config
.lookup
.tableIndex
].valueCount
) {
457 cliPrint(lookupTables
[var
->config
.lookup
.tableIndex
].values
[value
]);
459 cliPrintCorruptMessage(value
);
463 if (value
& 1 << var
->config
.bitpos
) {
473 static bool valuePtrEqualsDefault(const clivalue_t
*var
, const void *ptr
, const void *ptrDefault
)
476 int elementCount
= 1;
477 uint32_t mask
= 0xffffffff;
479 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
480 elementCount
= var
->config
.array
.length
;
482 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
483 mask
= 1 << var
->config
.bitpos
;
485 for (int i
= 0; i
< elementCount
; i
++) {
486 switch (var
->type
& VALUE_TYPE_MASK
) {
488 result
= result
&& (((uint8_t *)ptr
)[i
] & mask
) == (((uint8_t *)ptrDefault
)[i
] & mask
);
492 result
= result
&& ((int8_t *)ptr
)[i
] == ((int8_t *)ptrDefault
)[i
];
496 result
= result
&& (((int16_t *)ptr
)[i
] & mask
) == (((int16_t *)ptrDefault
)[i
] & mask
);
499 result
= result
&& ((int16_t *)ptr
)[i
] == ((int16_t *)ptrDefault
)[i
];
502 result
= result
&& (((uint32_t *)ptr
)[i
] & mask
) == (((uint32_t *)ptrDefault
)[i
] & mask
);
510 static uint8_t getPidProfileIndexToUse()
512 return pidProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentPidProfileIndex() : pidProfileIndexToUse
;
515 static uint8_t getRateProfileIndexToUse()
517 return rateProfileIndexToUse
== CURRENT_PROFILE_INDEX
? getCurrentControlRateProfileIndex() : rateProfileIndexToUse
;
521 static uint16_t getValueOffset(const clivalue_t
*value
)
523 switch (value
->type
& VALUE_SECTION_MASK
) {
525 return value
->offset
;
527 return value
->offset
+ sizeof(pidProfile_t
) * getPidProfileIndexToUse();
528 case PROFILE_RATE_VALUE
:
529 return value
->offset
+ sizeof(controlRateConfig_t
) * getRateProfileIndexToUse();
534 void *cliGetValuePointer(const clivalue_t
*value
)
536 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
537 if (configIsInCopy
) {
538 return CONST_CAST(void *, rec
->copy
+ getValueOffset(value
));
540 return CONST_CAST(void *, rec
->address
+ getValueOffset(value
));
544 const void *cliGetDefaultPointer(const clivalue_t
*value
)
546 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
547 return rec
->address
+ getValueOffset(value
);
550 static void dumpPgValue(const clivalue_t
*value
, uint8_t dumpMask
)
552 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
555 cliPrintLinef("VALUE %s ERROR", value
->name
);
556 return; // if it's not found, the pgn shouldn't be in the value table!
560 const char *format
= "set %s = ";
561 const char *defaultFormat
= "#set %s = ";
562 const int valueOffset
= getValueOffset(value
);
563 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
565 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
566 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
567 cliPrintf(defaultFormat
, value
->name
);
568 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
571 cliPrintf(format
, value
->name
);
572 printValuePointer(value
, pg
->copy
+ valueOffset
, false);
577 static void dumpAllValues(uint16_t valueSection
, uint8_t dumpMask
)
579 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
580 const clivalue_t
*value
= &valueTable
[i
];
581 bufWriterFlush(cliWriter
);
582 if ((value
->type
& VALUE_SECTION_MASK
) == valueSection
) {
583 dumpPgValue(value
, dumpMask
);
588 static void cliPrintVar(const clivalue_t
*var
, bool full
)
590 const void *ptr
= cliGetValuePointer(var
);
592 printValuePointer(var
, ptr
, full
);
595 static void cliPrintVarRange(const clivalue_t
*var
)
597 switch (var
->type
& VALUE_MODE_MASK
) {
598 case (MODE_DIRECT
): {
599 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
602 case (MODE_LOOKUP
): {
603 const lookupTableEntry_t
*tableEntry
= &lookupTables
[var
->config
.lookup
.tableIndex
];
604 cliPrint("Allowed values: ");
605 bool firstEntry
= true;
606 for (unsigned i
= 0; i
< tableEntry
->valueCount
; i
++) {
607 if (tableEntry
->values
[i
]) {
611 cliPrintf("%s", tableEntry
->values
[i
]);
619 cliPrintLinef("Array length: %d", var
->config
.array
.length
);
622 case (MODE_BITSET
): {
623 cliPrintLinef("Allowed values: OFF, ON");
629 static void cliSetVar(const clivalue_t
*var
, const int16_t value
)
631 void *ptr
= cliGetValuePointer(var
);
635 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
636 switch (var
->type
& VALUE_TYPE_MASK
) {
638 mask
= (1 << var
->config
.bitpos
) & 0xff;
640 workValue
= *(uint8_t *)ptr
| mask
;
642 workValue
= *(uint8_t *)ptr
& ~mask
;
644 *(uint8_t *)ptr
= workValue
;
648 mask
= (1 << var
->config
.bitpos
) & 0xffff;
650 workValue
= *(uint16_t *)ptr
| mask
;
652 workValue
= *(uint16_t *)ptr
& ~mask
;
654 *(uint16_t *)ptr
= workValue
;
658 mask
= 1 << var
->config
.bitpos
;
660 workValue
= *(uint32_t *)ptr
| mask
;
662 workValue
= *(uint32_t *)ptr
& ~mask
;
664 *(uint32_t *)ptr
= workValue
;
669 switch (var
->type
& VALUE_TYPE_MASK
) {
671 *(uint8_t *)ptr
= value
;
675 *(int8_t *)ptr
= value
;
680 *(int16_t *)ptr
= value
;
686 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
687 static void cliRepeat(char ch
, uint8_t len
)
689 for (int i
= 0; i
< len
; i
++) {
690 bufWriterAppend(cliWriter
, ch
);
696 static void cliPrompt(void)
701 static void cliShowParseError(void)
703 cliPrintErrorLinef("Parse error");
706 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
708 cliPrintErrorLinef("%s not between %d and %d", name
, min
, max
);
711 static const char *nextArg(const char *currentArg
)
713 const char *ptr
= strchr(currentArg
, ' ');
714 while (ptr
&& *ptr
== ' ') {
721 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
723 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
727 val
= CHANNEL_VALUE_TO_STEP(val
);
728 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
730 range
->startStep
= val
;
732 range
->endStep
= val
;
734 (*validArgumentCount
)++;
742 // Check if a string's length is zero
743 static bool isEmpty(const char *string
)
745 return (string
== NULL
|| *string
== '\0') ? true : false;
748 static void printRxFailsafe(uint8_t dumpMask
, const rxFailsafeChannelConfig_t
*rxFailsafeChannelConfigs
, const rxFailsafeChannelConfig_t
*defaultRxFailsafeChannelConfigs
)
750 // print out rxConfig failsafe settings
751 for (uint32_t channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
752 const rxFailsafeChannelConfig_t
*channelFailsafeConfig
= &rxFailsafeChannelConfigs
[channel
];
753 const rxFailsafeChannelConfig_t
*defaultChannelFailsafeConfig
= &defaultRxFailsafeChannelConfigs
[channel
];
754 const bool equalsDefault
= !memcmp(channelFailsafeConfig
, defaultChannelFailsafeConfig
, sizeof(*channelFailsafeConfig
));
755 const bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
757 const char *format
= "rxfail %u %c %d";
758 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
760 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
],
761 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig
->step
)
763 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
765 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
],
766 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
769 const char *format
= "rxfail %u %c";
770 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
772 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
]
774 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
776 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
]
782 static void cliRxFailsafe(char *cmdline
)
787 if (isEmpty(cmdline
)) {
788 // print out rxConfig failsafe settings
789 for (channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
790 cliRxFailsafe(itoa(channel
, buf
, 10));
793 const char *ptr
= cmdline
;
794 channel
= atoi(ptr
++);
795 if ((channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
)) {
797 rxFailsafeChannelConfig_t
*channelFailsafeConfig
= rxFailsafeChannelConfigsMutable(channel
);
799 const rxFailsafeChannelType_e type
= (channel
< NON_AUX_CHANNEL_COUNT
) ? RX_FAILSAFE_TYPE_FLIGHT
: RX_FAILSAFE_TYPE_AUX
;
800 rxFailsafeChannelMode_e mode
= channelFailsafeConfig
->mode
;
801 bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
805 const char *p
= strchr(rxFailsafeModeCharacters
, *(ptr
));
807 const uint8_t requestedMode
= p
- rxFailsafeModeCharacters
;
808 mode
= rxFailsafeModesTable
[type
][requestedMode
];
810 mode
= RX_FAILSAFE_MODE_INVALID
;
812 if (mode
== RX_FAILSAFE_MODE_INVALID
) {
817 requireValue
= mode
== RX_FAILSAFE_MODE_SET
;
825 uint16_t value
= atoi(ptr
);
826 value
= CHANNEL_VALUE_TO_RXFAIL_STEP(value
);
827 if (value
> MAX_RXFAIL_RANGE_STEP
) {
828 cliPrintLine("Value out of range");
832 channelFailsafeConfig
->step
= value
;
833 } else if (requireValue
) {
837 channelFailsafeConfig
->mode
= mode
;
840 char modeCharacter
= rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
];
842 // double use of cliPrintf below
843 // 1. acknowledge interpretation on command,
844 // 2. query current setting on single item,
847 cliPrintLinef("rxfail %u %c %d",
850 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
853 cliPrintLinef("rxfail %u %c",
859 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT
- 1);
864 static void printAux(uint8_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
)
866 const char *format
= "aux %u %u %u %u %u %u %u";
867 // print out aux channel settings
868 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
869 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
870 bool equalsDefault
= false;
871 if (defaultModeActivationConditions
) {
872 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
873 equalsDefault
= !memcmp(mac
, macDefault
, sizeof(*mac
));
874 const box_t
*box
= findBoxByBoxId(macDefault
->modeId
);
875 const box_t
*linkedTo
= findBoxByBoxId(macDefault
->linkedTo
);
877 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
880 macDefault
->auxChannelIndex
,
881 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
882 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
),
883 macDefault
->modeLogic
,
884 linkedTo
? linkedTo
->permanentId
: 0
888 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
889 const box_t
*linkedTo
= findBoxByBoxId(mac
->linkedTo
);
891 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
894 mac
->auxChannelIndex
,
895 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
896 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
898 linkedTo
? linkedTo
->permanentId
: 0
904 static void cliAux(char *cmdline
)
909 if (isEmpty(cmdline
)) {
910 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
);
914 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
915 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
916 uint8_t validArgumentCount
= 0;
920 const box_t
*box
= findBoxByPermanentId(val
);
922 mac
->modeId
= box
->boxId
;
923 validArgumentCount
++;
929 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
930 mac
->auxChannelIndex
= val
;
931 validArgumentCount
++;
934 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
938 if (val
== MODELOGIC_OR
|| val
== MODELOGIC_AND
) {
939 mac
->modeLogic
= val
;
940 validArgumentCount
++;
946 const box_t
*box
= findBoxByPermanentId(val
);
948 mac
->linkedTo
= box
->boxId
;
949 validArgumentCount
++;
952 if (validArgumentCount
== 4) { // for backwards compatibility
953 mac
->modeLogic
= MODELOGIC_OR
;
954 } else if (validArgumentCount
== 5) { // for backwards compatibility
956 } else if (validArgumentCount
!= 6) {
957 memset(mac
, 0, sizeof(modeActivationCondition_t
));
959 cliPrintLinef( "aux %u %u %u %u %u %u %u",
962 mac
->auxChannelIndex
,
963 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
964 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
969 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
974 static void printSerial(uint8_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
)
976 const char *format
= "serial %d %d %ld %ld %ld %ld";
977 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
978 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
981 bool equalsDefault
= false;
982 if (serialConfigDefault
) {
983 equalsDefault
= !memcmp(&serialConfig
->portConfigs
[i
], &serialConfigDefault
->portConfigs
[i
], sizeof(serialConfig
->portConfigs
[i
]));
984 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
985 serialConfigDefault
->portConfigs
[i
].identifier
,
986 serialConfigDefault
->portConfigs
[i
].functionMask
,
987 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
988 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
989 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
990 baudRates
[serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
]
993 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
994 serialConfig
->portConfigs
[i
].identifier
,
995 serialConfig
->portConfigs
[i
].functionMask
,
996 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
997 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
998 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
999 baudRates
[serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
]
1004 static void cliSerial(char *cmdline
)
1006 const char *format
= "serial %d %d %ld %ld %ld %ld";
1007 if (isEmpty(cmdline
)) {
1008 printSerial(DUMP_MASTER
, serialConfig(), NULL
);
1011 serialPortConfig_t portConfig
;
1012 memset(&portConfig
, 0 , sizeof(portConfig
));
1014 serialPortConfig_t
*currentConfig
;
1016 uint8_t validArgumentCount
= 0;
1018 const char *ptr
= cmdline
;
1020 int val
= atoi(ptr
++);
1021 currentConfig
= serialFindPortConfiguration(val
);
1022 if (currentConfig
) {
1023 portConfig
.identifier
= val
;
1024 validArgumentCount
++;
1030 portConfig
.functionMask
= val
& 0xFFFF;
1031 validArgumentCount
++;
1034 for (int i
= 0; i
< 4; i
++) {
1042 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
1043 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
1049 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_1000000
) {
1052 portConfig
.msp_baudrateIndex
= baudRateIndex
;
1055 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
1058 portConfig
.gps_baudrateIndex
= baudRateIndex
;
1061 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
1064 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
1067 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_2470000
) {
1070 portConfig
.blackbox_baudrateIndex
= baudRateIndex
;
1074 validArgumentCount
++;
1077 if (validArgumentCount
< 6) {
1078 cliShowParseError();
1082 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
1084 cliDumpPrintLinef(0, false, format
,
1085 portConfig
.identifier
,
1086 portConfig
.functionMask
,
1087 baudRates
[portConfig
.msp_baudrateIndex
],
1088 baudRates
[portConfig
.gps_baudrateIndex
],
1089 baudRates
[portConfig
.telemetry_baudrateIndex
],
1090 baudRates
[portConfig
.blackbox_baudrateIndex
]
1095 #ifndef SKIP_SERIAL_PASSTHROUGH
1097 static void cbCtrlLine(void *context
, uint16_t ctrl
)
1099 int pinioDtr
= (int)(long)context
;
1101 pinioSet(pinioDtr
, !(ctrl
& CTRL_LINE_STATE_DTR
));
1103 #endif /* USE_PINIO */
1105 static void cliSerialPassthrough(char *cmdline
)
1107 if (isEmpty(cmdline
)) {
1108 cliShowParseError();
1114 bool enableBaudCb
= false;
1117 #endif /* USE_PINIO */
1120 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
1123 while (tok
!= NULL
) {
1132 if (strstr(tok
, "rx") || strstr(tok
, "RX"))
1134 if (strstr(tok
, "tx") || strstr(tok
, "TX"))
1139 pinioDtr
= atoi(tok
);
1141 #endif /* USE_PINIO */
1144 tok
= strtok_r(NULL
, " ", &saveptr
);
1148 enableBaudCb
= true;
1151 cliPrintf("Port %d ", id
);
1152 serialPort_t
*passThroughPort
;
1153 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
1154 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
1164 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
, NULL
,
1166 SERIAL_NOT_INVERTED
);
1167 if (!passThroughPort
) {
1168 cliPrintLine("could not be opened.");
1173 cliPrintf("opened, default baud = %d.\r\n", baud
);
1175 cliPrintf("opened, baud = %d.\r\n", baud
);
1178 passThroughPort
= passThroughPortUsage
->serialPort
;
1179 // If the user supplied a mode, override the port's mode, otherwise
1180 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1181 // Set the baud rate if specified
1183 cliPrintf("already open, setting baud = %d.\n\r", baud
);
1184 serialSetBaudRate(passThroughPort
, baud
);
1186 cliPrintf("already open, baud = %d.\n\r", passThroughPort
->baudRate
);
1189 if (mode
&& passThroughPort
->mode
!= mode
) {
1190 cliPrintf("Mode changed from %d to %d.\r\n",
1191 passThroughPort
->mode
, mode
);
1192 serialSetMode(passThroughPort
, mode
);
1195 // If this port has a rx callback associated we need to remove it now.
1196 // Otherwise no data will be pushed in the serial port buffer!
1197 if (passThroughPort
->rxCallback
) {
1198 passThroughPort
->rxCallback
= 0;
1202 // If no baud rate is specified allow to be set via USB
1204 cliPrintLine("Baud rate change over USB enabled.");
1205 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1206 // baud rate over USB without setting it using the serialpassthrough command
1207 serialSetBaudRateCb(cliPort
, serialSetBaudRate
, passThroughPort
);
1210 cliPrintLine("Forwarding, power cycle to exit.");
1213 // Register control line state callback
1215 serialSetCtrlLineStateCb(cliPort
, cbCtrlLine
, (void *)(intptr_t)(pinioDtr
- 1));
1217 #endif /* USE_PINIO */
1219 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
1223 static void printAdjustmentRange(uint8_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
)
1225 const char *format
= "adjrange %u %u %u %u %u %u %u %u %u";
1226 // print out adjustment ranges channel settings
1227 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
1228 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
1229 bool equalsDefault
= false;
1230 if (defaultAdjustmentRanges
) {
1231 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
1232 equalsDefault
= !memcmp(ar
, arDefault
, sizeof(*ar
));
1233 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1235 arDefault
->adjustmentIndex
,
1236 arDefault
->auxChannelIndex
,
1237 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1238 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1239 arDefault
->adjustmentFunction
,
1240 arDefault
->auxSwitchChannelIndex
,
1241 arDefault
->adjustmentCenter
,
1242 arDefault
->adjustmentScale
1245 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1247 ar
->adjustmentIndex
,
1248 ar
->auxChannelIndex
,
1249 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1250 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1251 ar
->adjustmentFunction
,
1252 ar
->auxSwitchChannelIndex
,
1253 ar
->adjustmentCenter
,
1259 static void cliAdjustmentRange(char *cmdline
)
1261 const char *format
= "adjrange %u %u %u %u %u %u %u %u %u";
1265 if (isEmpty(cmdline
)) {
1266 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
);
1270 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1271 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1272 uint8_t validArgumentCount
= 0;
1277 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
1278 ar
->adjustmentIndex
= val
;
1279 validArgumentCount
++;
1285 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1286 ar
->auxChannelIndex
= val
;
1287 validArgumentCount
++;
1291 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1296 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1297 ar
->adjustmentFunction
= val
;
1298 validArgumentCount
++;
1304 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1305 ar
->auxSwitchChannelIndex
= val
;
1306 validArgumentCount
++;
1310 if (validArgumentCount
!= 6) {
1311 memset(ar
, 0, sizeof(adjustmentRange_t
));
1312 cliShowParseError();
1316 // Optional arguments
1317 ar
->adjustmentCenter
= 0;
1318 ar
->adjustmentScale
= 0;
1323 ar
->adjustmentCenter
= val
;
1324 validArgumentCount
++;
1329 ar
->adjustmentScale
= val
;
1330 validArgumentCount
++;
1332 cliDumpPrintLinef(0, false, format
,
1334 ar
->adjustmentIndex
,
1335 ar
->auxChannelIndex
,
1336 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1337 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1338 ar
->adjustmentFunction
,
1339 ar
->auxSwitchChannelIndex
,
1340 ar
->adjustmentCenter
,
1345 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1350 #ifndef USE_QUAD_MIXER_ONLY
1351 static void printMotorMix(uint8_t dumpMask
, const motorMixer_t
*customMotorMixer
, const motorMixer_t
*defaultCustomMotorMixer
)
1353 const char *format
= "mmix %d %s %s %s %s";
1354 char buf0
[FTOA_BUFFER_LENGTH
];
1355 char buf1
[FTOA_BUFFER_LENGTH
];
1356 char buf2
[FTOA_BUFFER_LENGTH
];
1357 char buf3
[FTOA_BUFFER_LENGTH
];
1358 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1359 if (customMotorMixer
[i
].throttle
== 0.0f
)
1361 const float thr
= customMotorMixer
[i
].throttle
;
1362 const float roll
= customMotorMixer
[i
].roll
;
1363 const float pitch
= customMotorMixer
[i
].pitch
;
1364 const float yaw
= customMotorMixer
[i
].yaw
;
1365 bool equalsDefault
= false;
1366 if (defaultCustomMotorMixer
) {
1367 const float thrDefault
= defaultCustomMotorMixer
[i
].throttle
;
1368 const float rollDefault
= defaultCustomMotorMixer
[i
].roll
;
1369 const float pitchDefault
= defaultCustomMotorMixer
[i
].pitch
;
1370 const float yawDefault
= defaultCustomMotorMixer
[i
].yaw
;
1371 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1373 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1375 ftoa(thrDefault
, buf0
),
1376 ftoa(rollDefault
, buf1
),
1377 ftoa(pitchDefault
, buf2
),
1378 ftoa(yawDefault
, buf3
));
1380 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1388 #endif // USE_QUAD_MIXER_ONLY
1390 static void cliMotorMix(char *cmdline
)
1392 #ifdef USE_QUAD_MIXER_ONLY
1399 if (isEmpty(cmdline
)) {
1400 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
);
1401 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1402 // erase custom mixer
1403 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1404 customMotorMixerMutable(i
)->throttle
= 0.0f
;
1406 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1407 ptr
= nextArg(cmdline
);
1410 for (uint32_t i
= 0; ; i
++) {
1411 if (mixerNames
[i
] == NULL
) {
1412 cliPrintErrorLinef("Invalid name");
1415 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1416 mixerLoadMix(i
, customMotorMixerMutable(0));
1417 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1425 uint32_t i
= atoi(ptr
); // get motor number
1426 if (i
< MAX_SUPPORTED_MOTORS
) {
1429 customMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1434 customMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1439 customMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1444 customMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1448 cliShowParseError();
1450 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
);
1453 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
1459 static void printRxRange(uint8_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
)
1461 const char *format
= "rxrange %u %u %u";
1462 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1463 bool equalsDefault
= false;
1464 if (defaultChannelRangeConfigs
) {
1465 equalsDefault
= !memcmp(&channelRangeConfigs
[i
], &defaultChannelRangeConfigs
[i
], sizeof(channelRangeConfigs
[i
]));
1466 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1468 defaultChannelRangeConfigs
[i
].min
,
1469 defaultChannelRangeConfigs
[i
].max
1472 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1474 channelRangeConfigs
[i
].min
,
1475 channelRangeConfigs
[i
].max
1480 static void cliRxRange(char *cmdline
)
1482 const char *format
= "rxrange %u %u %u";
1483 int i
, validArgumentCount
= 0;
1486 if (isEmpty(cmdline
)) {
1487 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
);
1488 } else if (strcasecmp(cmdline
, "reset") == 0) {
1489 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1493 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1494 int rangeMin
= PWM_PULSE_MIN
, rangeMax
= PWM_PULSE_MAX
;
1498 rangeMin
= atoi(ptr
);
1499 validArgumentCount
++;
1504 rangeMax
= atoi(ptr
);
1505 validArgumentCount
++;
1508 if (validArgumentCount
!= 2) {
1509 cliShowParseError();
1510 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1511 cliShowParseError();
1513 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1514 channelRangeConfig
->min
= rangeMin
;
1515 channelRangeConfig
->max
= rangeMax
;
1516 cliDumpPrintLinef(0, false, format
,
1518 channelRangeConfig
->min
,
1519 channelRangeConfig
->max
1524 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT
- 1);
1529 #ifdef USE_LED_STRIP
1530 static void printLed(uint8_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
)
1532 const char *format
= "led %u %s";
1533 char ledConfigBuffer
[20];
1534 char ledConfigDefaultBuffer
[20];
1535 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1536 ledConfig_t ledConfig
= ledConfigs
[i
];
1537 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1538 bool equalsDefault
= false;
1539 if (defaultLedConfigs
) {
1540 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1541 equalsDefault
= ledConfig
== ledConfigDefault
;
1542 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1543 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1545 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1549 static void cliLed(char *cmdline
)
1551 const char *format
= "led %u %s";
1552 char ledConfigBuffer
[20];
1556 if (isEmpty(cmdline
)) {
1557 printLed(DUMP_MASTER
, ledStripConfig()->ledConfigs
, NULL
);
1561 if (i
< LED_MAX_STRIP_LENGTH
) {
1562 ptr
= nextArg(cmdline
);
1563 if (parseLedStripConfig(i
, ptr
)) {
1564 generateLedConfig((ledConfig_t
*)&ledStripConfig()->ledConfigs
[i
], ledConfigBuffer
, sizeof(ledConfigBuffer
));
1565 cliDumpPrintLinef(0, false, format
, i
, ledConfigBuffer
);
1567 cliShowParseError();
1570 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH
- 1);
1575 static void printColor(uint8_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
)
1577 const char *format
= "color %u %d,%u,%u";
1578 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1579 const hsvColor_t
*color
= &colors
[i
];
1580 bool equalsDefault
= false;
1581 if (defaultColors
) {
1582 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1583 equalsDefault
= !memcmp(color
, colorDefault
, sizeof(*color
));
1584 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1586 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1590 static void cliColor(char *cmdline
)
1592 const char *format
= "color %u %d,%u,%u";
1593 if (isEmpty(cmdline
)) {
1594 printColor(DUMP_MASTER
, ledStripConfig()->colors
, NULL
);
1596 const char *ptr
= cmdline
;
1597 const int i
= atoi(ptr
);
1598 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1599 ptr
= nextArg(cmdline
);
1600 if (parseColor(i
, ptr
)) {
1601 const hsvColor_t
*color
= &ledStripConfig()->colors
[i
];
1602 cliDumpPrintLinef(0, false, format
, i
, color
->h
, color
->s
, color
->v
);
1604 cliShowParseError();
1607 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1612 static void printModeColor(uint8_t dumpMask
, const ledStripConfig_t
*ledStripConfig
, const ledStripConfig_t
*defaultLedStripConfig
)
1614 const char *format
= "mode_color %u %u %u";
1615 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1616 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1617 int colorIndex
= ledStripConfig
->modeColors
[i
].color
[j
];
1618 bool equalsDefault
= false;
1619 if (defaultLedStripConfig
) {
1620 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1621 equalsDefault
= colorIndex
== colorIndexDefault
;
1622 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1624 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1628 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1629 const int colorIndex
= ledStripConfig
->specialColors
.color
[j
];
1630 bool equalsDefault
= false;
1631 if (defaultLedStripConfig
) {
1632 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1633 equalsDefault
= colorIndex
== colorIndexDefault
;
1634 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1636 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1639 const int ledStripAuxChannel
= ledStripConfig
->ledstrip_aux_channel
;
1640 bool equalsDefault
= false;
1641 if (defaultLedStripConfig
) {
1642 const int ledStripAuxChannelDefault
= defaultLedStripConfig
->ledstrip_aux_channel
;
1643 equalsDefault
= ledStripAuxChannel
== ledStripAuxChannelDefault
;
1644 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannelDefault
);
1646 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannel
);
1649 static void cliModeColor(char *cmdline
)
1651 if (isEmpty(cmdline
)) {
1652 printModeColor(DUMP_MASTER
, ledStripConfig(), NULL
);
1654 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1655 int args
[ARGS_COUNT
];
1658 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1659 while (ptr
&& argNo
< ARGS_COUNT
) {
1660 args
[argNo
++] = atoi(ptr
);
1661 ptr
= strtok_r(NULL
, " ", &saveptr
);
1664 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1665 cliShowParseError();
1669 int modeIdx
= args
[MODE
];
1670 int funIdx
= args
[FUNCTION
];
1671 int color
= args
[COLOR
];
1672 if (!setModeColor(modeIdx
, funIdx
, color
)) {
1673 cliShowParseError();
1676 // values are validated
1677 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1683 static void printServo(uint8_t dumpMask
, const servoParam_t
*servoParams
, const servoParam_t
*defaultServoParams
)
1685 // print out servo settings
1686 const char *format
= "servo %u %d %d %d %d %d";
1687 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1688 const servoParam_t
*servoConf
= &servoParams
[i
];
1689 bool equalsDefault
= false;
1690 if (defaultServoParams
) {
1691 const servoParam_t
*defaultServoConf
= &defaultServoParams
[i
];
1692 equalsDefault
= !memcmp(servoConf
, defaultServoConf
, sizeof(*servoConf
));
1693 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1695 defaultServoConf
->min
,
1696 defaultServoConf
->max
,
1697 defaultServoConf
->middle
,
1698 defaultServoConf
->rate
,
1699 defaultServoConf
->forwardFromChannel
1702 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1708 servoConf
->forwardFromChannel
1711 // print servo directions
1712 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1713 const char *format
= "smix reverse %d %d r";
1714 const servoParam_t
*servoConf
= &servoParams
[i
];
1715 const servoParam_t
*servoConfDefault
= &defaultServoParams
[i
];
1716 if (defaultServoParams
) {
1717 bool equalsDefault
= servoConf
->reversedSources
== servoConfDefault
->reversedSources
;
1718 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1719 equalsDefault
= ~(servoConf
->reversedSources
^ servoConfDefault
->reversedSources
) & (1 << channel
);
1720 if (servoConfDefault
->reversedSources
& (1 << channel
)) {
1721 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1723 if (servoConf
->reversedSources
& (1 << channel
)) {
1724 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1728 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1729 if (servoConf
->reversedSources
& (1 << channel
)) {
1730 cliDumpPrintLinef(dumpMask
, true, format
, i
, channel
);
1737 static void cliServo(char *cmdline
)
1739 const char *format
= "servo %u %d %d %d %d %d";
1740 enum { SERVO_ARGUMENT_COUNT
= 6 };
1741 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1743 servoParam_t
*servo
;
1748 if (isEmpty(cmdline
)) {
1749 printServo(DUMP_MASTER
, servoParams(0), NULL
);
1751 int validArgumentCount
= 0;
1755 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1757 // If command line doesn't fit the format, don't modify the config
1759 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1760 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1761 cliShowParseError();
1765 arguments
[validArgumentCount
++] = atoi(ptr
);
1769 } while (*ptr
>= '0' && *ptr
<= '9');
1770 } else if (*ptr
== ' ') {
1773 cliShowParseError();
1778 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
, FORWARD
};
1780 i
= arguments
[INDEX
];
1782 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1783 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1784 cliShowParseError();
1788 servo
= servoParamsMutable(i
);
1791 arguments
[MIN
] < PWM_PULSE_MIN
|| arguments
[MIN
] > PWM_PULSE_MAX
||
1792 arguments
[MAX
] < PWM_PULSE_MIN
|| arguments
[MAX
] > PWM_PULSE_MAX
||
1793 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
1794 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
1795 arguments
[RATE
] < -100 || arguments
[RATE
] > 100 ||
1796 arguments
[FORWARD
] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1798 cliShowParseError();
1802 servo
->min
= arguments
[MIN
];
1803 servo
->max
= arguments
[MAX
];
1804 servo
->middle
= arguments
[MIDDLE
];
1805 servo
->rate
= arguments
[RATE
];
1806 servo
->forwardFromChannel
= arguments
[FORWARD
];
1808 cliDumpPrintLinef(0, false, format
,
1814 servo
->forwardFromChannel
1822 static void printServoMix(uint8_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
)
1824 const char *format
= "smix %d %d %d %d %d %d %d %d";
1825 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1826 const servoMixer_t customServoMixer
= customServoMixers
[i
];
1827 if (customServoMixer
.rate
== 0) {
1831 bool equalsDefault
= false;
1832 if (defaultCustomServoMixers
) {
1833 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
1834 equalsDefault
= !memcmp(&customServoMixer
, &customServoMixerDefault
, sizeof(customServoMixer
));
1836 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1838 customServoMixerDefault
.targetChannel
,
1839 customServoMixerDefault
.inputSource
,
1840 customServoMixerDefault
.rate
,
1841 customServoMixerDefault
.speed
,
1842 customServoMixerDefault
.min
,
1843 customServoMixerDefault
.max
,
1844 customServoMixerDefault
.box
1847 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1849 customServoMixer
.targetChannel
,
1850 customServoMixer
.inputSource
,
1851 customServoMixer
.rate
,
1852 customServoMixer
.speed
,
1853 customServoMixer
.min
,
1854 customServoMixer
.max
,
1855 customServoMixer
.box
1862 static void cliServoMix(char *cmdline
)
1864 int args
[8], check
= 0;
1865 int len
= strlen(cmdline
);
1868 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
);
1869 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1870 // erase custom mixer
1871 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1872 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1873 servoParamsMutable(i
)->reversedSources
= 0;
1875 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1876 const char *ptr
= nextArg(cmdline
);
1879 for (uint32_t i
= 0; ; i
++) {
1880 if (mixerNames
[i
] == NULL
) {
1881 cliPrintErrorLinef("Invalid name");
1884 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1885 servoMixerLoadMix(i
);
1886 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1892 } else if (strncasecmp(cmdline
, "reverse", 7) == 0) {
1893 enum {SERVO
= 0, INPUT
, REVERSE
, ARGS_COUNT
};
1894 char *ptr
= strchr(cmdline
, ' ');
1898 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
1899 cliPrintf("\ti%d", inputSource
);
1902 for (uint32_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
1903 cliPrintf("%d", servoIndex
);
1904 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++) {
1905 cliPrintf("\t%s ", (servoParams(servoIndex
)->reversedSources
& (1 << inputSource
)) ? "r" : "n");
1913 ptr
= strtok_r(ptr
, " ", &saveptr
);
1914 while (ptr
!= NULL
&& check
< ARGS_COUNT
- 1) {
1915 args
[check
++] = atoi(ptr
);
1916 ptr
= strtok_r(NULL
, " ", &saveptr
);
1919 if (ptr
== NULL
|| check
!= ARGS_COUNT
- 1) {
1920 cliShowParseError();
1924 if (args
[SERVO
] >= 0 && args
[SERVO
] < MAX_SUPPORTED_SERVOS
1925 && args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
1926 && (*ptr
== 'r' || *ptr
== 'n')) {
1928 servoParamsMutable(args
[SERVO
])->reversedSources
|= 1 << args
[INPUT
];
1930 servoParamsMutable(args
[SERVO
])->reversedSources
&= ~(1 << args
[INPUT
]);
1933 cliShowParseError();
1937 cliServoMix("reverse");
1939 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, MIN
, MAX
, BOX
, ARGS_COUNT
};
1941 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1942 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1943 args
[check
++] = atoi(ptr
);
1944 ptr
= strtok_r(NULL
, " ", &saveptr
);
1947 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
1948 cliShowParseError();
1952 int32_t i
= args
[RULE
];
1953 if (i
>= 0 && i
< MAX_SERVO_RULES
&&
1954 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
1955 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
1956 args
[RATE
] >= -100 && args
[RATE
] <= 100 &&
1957 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
1958 args
[MIN
] >= 0 && args
[MIN
] <= 100 &&
1959 args
[MAX
] >= 0 && args
[MAX
] <= 100 && args
[MIN
] < args
[MAX
] &&
1960 args
[BOX
] >= 0 && args
[BOX
] <= MAX_SERVO_BOXES
) {
1961 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
1962 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
1963 customServoMixersMutable(i
)->rate
= args
[RATE
];
1964 customServoMixersMutable(i
)->speed
= args
[SPEED
];
1965 customServoMixersMutable(i
)->min
= args
[MIN
];
1966 customServoMixersMutable(i
)->max
= args
[MAX
];
1967 customServoMixersMutable(i
)->box
= args
[BOX
];
1970 cliShowParseError();
1978 static void cliWriteBytes(const uint8_t *buffer
, int count
)
1987 static void cliSdInfo(char *cmdline
)
1991 cliPrint("SD card: ");
1993 if (!sdcard_isInserted()) {
1994 cliPrintLine("None inserted");
1998 if (!sdcard_isInitialized()) {
1999 cliPrintLine("Startup failed");
2003 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
2005 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2006 metadata
->manufacturerID
,
2007 metadata
->numBlocks
/ 2, /* One block is half a kB */
2008 metadata
->productionMonth
,
2009 metadata
->productionYear
,
2010 metadata
->productRevisionMajor
,
2011 metadata
->productRevisionMinor
2014 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
2016 cliPrint("'\r\n" "Filesystem: ");
2018 switch (afatfs_getFilesystemState()) {
2019 case AFATFS_FILESYSTEM_STATE_READY
:
2022 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
2023 cliPrint("Initializing");
2025 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
2026 case AFATFS_FILESYSTEM_STATE_FATAL
:
2029 switch (afatfs_getLastError()) {
2030 case AFATFS_ERROR_BAD_MBR
:
2031 cliPrint(" - no FAT MBR partitions");
2033 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
2034 cliPrint(" - bad FAT header");
2036 case AFATFS_ERROR_GENERIC
:
2037 case AFATFS_ERROR_NONE
:
2038 ; // Nothing more detailed to print
2050 static void cliFlashInfo(char *cmdline
)
2052 const flashGeometry_t
*layout
= flashfsGetGeometry();
2056 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
2057 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
, flashfsGetOffset());
2061 static void cliFlashErase(char *cmdline
)
2065 if (!flashfsIsSupported()) {
2071 cliPrintLine("Erasing, please wait ... ");
2073 cliPrintLine("Erasing,");
2076 bufWriterFlush(cliWriter
);
2077 flashfsEraseCompletely();
2079 while (!flashfsIsReady()) {
2087 bufWriterFlush(cliWriter
);
2091 beeper(BEEPER_BLACKBOX_ERASE
);
2093 cliPrintLine("Done.");
2096 #ifdef USE_FLASH_TOOLS
2098 static void cliFlashWrite(char *cmdline
)
2100 const uint32_t address
= atoi(cmdline
);
2101 const char *text
= strchr(cmdline
, ' ');
2104 cliShowParseError();
2106 flashfsSeekAbs(address
);
2107 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2110 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2114 static void cliFlashRead(char *cmdline
)
2116 uint32_t address
= atoi(cmdline
);
2118 const char *nextArg
= strchr(cmdline
, ' ');
2121 cliShowParseError();
2123 uint32_t length
= atoi(nextArg
);
2125 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2128 while (length
> 0) {
2129 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2131 for (int i
= 0; i
< bytesRead
; i
++) {
2132 cliWrite(buffer
[i
]);
2135 length
-= bytesRead
;
2136 address
+= bytesRead
;
2138 if (bytesRead
== 0) {
2139 //Assume we reached the end of the volume or something fatal happened
2150 #ifdef USE_VTX_CONTROL
2151 static void printVtx(uint8_t dumpMask
, const vtxConfig_t
*vtxConfig
, const vtxConfig_t
*vtxConfigDefault
)
2153 // print out vtx channel settings
2154 const char *format
= "vtx %u %u %u %u %u %u";
2155 bool equalsDefault
= false;
2156 for (uint32_t i
= 0; i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
; i
++) {
2157 const vtxChannelActivationCondition_t
*cac
= &vtxConfig
->vtxChannelActivationConditions
[i
];
2158 if (vtxConfigDefault
) {
2159 const vtxChannelActivationCondition_t
*cacDefault
= &vtxConfigDefault
->vtxChannelActivationConditions
[i
];
2160 equalsDefault
= !memcmp(cac
, cacDefault
, sizeof(*cac
));
2161 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2163 cacDefault
->auxChannelIndex
,
2165 cacDefault
->channel
,
2166 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.startStep
),
2167 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.endStep
)
2170 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2172 cac
->auxChannelIndex
,
2175 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2176 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2181 static void cliVtx(char *cmdline
)
2183 const char *format
= "vtx %u %u %u %u %u %u";
2187 if (isEmpty(cmdline
)) {
2188 printVtx(DUMP_MASTER
, vtxConfig(), NULL
);
2192 if (i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
) {
2193 vtxChannelActivationCondition_t
*cac
= &vtxConfigMutable()->vtxChannelActivationConditions
[i
];
2194 uint8_t validArgumentCount
= 0;
2198 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
2199 cac
->auxChannelIndex
= val
;
2200 validArgumentCount
++;
2206 // FIXME Use VTX API to get min/max
2207 if (val
>= VTX_SETTINGS_MIN_BAND
&& val
<= VTX_SETTINGS_MAX_BAND
) {
2209 validArgumentCount
++;
2215 // FIXME Use VTX API to get min/max
2216 if (val
>= VTX_SETTINGS_MIN_CHANNEL
&& val
<= VTX_SETTINGS_MAX_CHANNEL
) {
2218 validArgumentCount
++;
2221 ptr
= processChannelRangeArgs(ptr
, &cac
->range
, &validArgumentCount
);
2223 if (validArgumentCount
!= 5) {
2224 memset(cac
, 0, sizeof(vtxChannelActivationCondition_t
));
2225 cliShowParseError();
2227 cliDumpPrintLinef(0, false, format
,
2229 cac
->auxChannelIndex
,
2232 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2233 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2237 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
- 1);
2242 #endif // VTX_CONTROL
2244 static void printName(uint8_t dumpMask
, const pilotConfig_t
*pilotConfig
)
2246 const bool equalsDefault
= strlen(pilotConfig
->name
) == 0;
2247 cliDumpPrintLinef(dumpMask
, equalsDefault
, "name %s", equalsDefault
? emptyName
: pilotConfig
->name
);
2250 static void cliName(char *cmdline
)
2252 const unsigned int len
= strlen(cmdline
);
2254 memset(pilotConfigMutable()->name
, 0, ARRAYLEN(pilotConfig()->name
));
2255 if (strncmp(cmdline
, emptyName
, len
)) {
2256 strncpy(pilotConfigMutable()->name
, cmdline
, MIN(len
, MAX_NAME_LENGTH
));
2259 printName(DUMP_MASTER
, pilotConfig());
2262 #if defined(USE_BOARD_INFO)
2264 #define ERROR_MESSAGE "%s cannot be changed. Current value: '%s'"
2266 static void cliBoardName(char *cmdline
)
2268 const unsigned int len
= strlen(cmdline
);
2269 if (len
> 0 && boardInformationIsSet() && (len
!= strlen(getBoardName()) || strncmp(getBoardName(), cmdline
, len
))) {
2270 cliPrintErrorLinef(ERROR_MESSAGE
, "board_name", getBoardName());
2273 setBoardName(cmdline
);
2274 boardInformationUpdated
= true;
2276 cliPrintLinef("board_name %s", getBoardName());
2280 static void cliManufacturerId(char *cmdline
)
2282 const unsigned int len
= strlen(cmdline
);
2283 if (len
> 0 && boardInformationIsSet() && (len
!= strlen(getManufacturerId()) || strncmp(getManufacturerId(), cmdline
, len
))) {
2284 cliPrintErrorLinef(ERROR_MESSAGE
, "manufacturer_id", getManufacturerId());
2287 setManufacturerId(cmdline
);
2288 boardInformationUpdated
= true;
2290 cliPrintLinef("manufacturer_id %s", getManufacturerId());
2294 #if defined(USE_SIGNATURE)
2295 static void writeSignature(char *signatureStr
, uint8_t *signature
)
2297 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
2298 tfp_sprintf(&signatureStr
[2 * i
], "%02x", signature
[i
]);
2302 static void cliSignature(char *cmdline
)
2304 const int len
= strlen(cmdline
);
2306 uint8_t signature
[SIGNATURE_LENGTH
] = {0};
2308 if (len
!= 2 * SIGNATURE_LENGTH
) {
2309 cliPrintErrorLinef("Invalid length: %d (expected: %d)", len
, 2 * SIGNATURE_LENGTH
);
2314 #define BLOCK_SIZE 2
2315 for (unsigned i
= 0; i
< SIGNATURE_LENGTH
; i
++) {
2316 char temp
[BLOCK_SIZE
+ 1];
2317 strncpy(temp
, &cmdline
[i
* BLOCK_SIZE
], BLOCK_SIZE
);
2318 temp
[BLOCK_SIZE
] = '\0';
2320 unsigned result
= strtoul(temp
, &end
, 16);
2321 if (end
== &temp
[BLOCK_SIZE
]) {
2322 signature
[i
] = result
;
2324 cliPrintErrorLinef("Invalid character found: %c", end
[0]);
2332 char signatureStr
[SIGNATURE_LENGTH
* 2 + 1] = {0};
2333 if (len
> 0 && signatureIsSet() && memcmp(signature
, getSignature(), SIGNATURE_LENGTH
)) {
2334 writeSignature(signatureStr
, getSignature());
2335 cliPrintErrorLinef(ERROR_MESSAGE
, "signature", signatureStr
);
2338 setSignature(signature
);
2340 signatureUpdated
= true;
2342 writeSignature(signatureStr
, getSignature());
2343 } else if (signatureUpdated
|| signatureIsSet()) {
2344 writeSignature(signatureStr
, getSignature());
2347 cliPrintLinef("signature %s", signatureStr
);
2352 #undef ERROR_MESSAGE
2354 #endif // USE_BOARD_INFO
2356 static void cliMcuId(char *cmdline
)
2360 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0
, U_ID_1
, U_ID_2
);
2363 static uint32_t getFeatureMask(const uint32_t featureMask
)
2365 if (featureMaskIsCopied
) {
2366 return featureMaskCopy
;
2372 static void printFeature(uint8_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
)
2374 const uint32_t mask
= getFeatureMask(featureConfig
->enabledFeatures
);
2375 const uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
2376 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // disabled features first
2377 if (strcmp(featureNames
[i
], emptyString
) != 0) { //Skip unused
2378 const char *format
= "feature -%s";
2379 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2380 cliDumpPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2383 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // enabled features
2384 if (strcmp(featureNames
[i
], emptyString
) != 0) { //Skip unused
2385 const char *format
= "feature %s";
2386 if (defaultMask
& (1 << i
)) {
2387 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2389 if (mask
& (1 << i
)) {
2390 cliDumpPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2396 static void cliFeature(char *cmdline
)
2398 uint32_t len
= strlen(cmdline
);
2399 const uint32_t mask
= getFeatureMask(featureMask());
2401 cliPrint("Enabled: ");
2402 for (uint32_t i
= 0; ; i
++) {
2403 if (featureNames
[i
] == NULL
) {
2406 if (mask
& (1 << i
)) {
2407 cliPrintf("%s ", featureNames
[i
]);
2411 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2412 cliPrint("Available:");
2413 for (uint32_t i
= 0; ; i
++) {
2414 if (featureNames
[i
] == NULL
)
2416 if (strcmp(featureNames
[i
], emptyString
) != 0) //Skip unused
2417 cliPrintf(" %s", featureNames
[i
]);
2422 if (!featureMaskIsCopied
) {
2423 featureMaskCopy
= featureMask();
2424 featureMaskIsCopied
= true;
2428 bool remove
= false;
2429 if (cmdline
[0] == '-') {
2432 cmdline
++; // skip over -
2436 for (uint32_t i
= 0; ; i
++) {
2437 if (featureNames
[i
] == NULL
) {
2438 cliPrintErrorLinef("Invalid name");
2442 if (strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
2445 if (feature
& FEATURE_GPS
) {
2446 cliPrintLine("unavailable");
2450 #ifndef USE_RANGEFINDER
2451 if (feature
& FEATURE_RANGEFINDER
) {
2452 cliPrintLine("unavailable");
2457 featureClear(feature
, &featureMaskCopy
);
2458 cliPrint("Disabled");
2460 featureSet(feature
, &featureMaskCopy
);
2461 cliPrint("Enabled");
2463 cliPrintLinef(" %s", featureNames
[i
]);
2470 #if defined(USE_BEEPER)
2471 static void printBeeper(uint8_t dumpMask
, const uint32_t offFlags
, const uint32_t offFlagsDefault
, const char *name
)
2473 const uint8_t beeperCount
= beeperTableEntryCount();
2474 for (int32_t i
= 0; i
< beeperCount
- 1; i
++) {
2475 const char *formatOff
= "%s -%s";
2476 const char *formatOn
= "%s %s";
2477 const uint32_t beeperModeMask
= beeperModeMaskForTableIndex(i
);
2478 cliDefaultPrintLinef(dumpMask
, ~(offFlags
^ offFlagsDefault
) & beeperModeMask
, offFlags
& beeperModeMask
? formatOn
: formatOff
, name
, beeperNameForTableIndex(i
));
2479 cliDumpPrintLinef(dumpMask
, ~(offFlags
^ offFlagsDefault
) & beeperModeMask
, offFlags
& beeperModeMask
? formatOff
: formatOn
, name
, beeperNameForTableIndex(i
));
2483 static void processBeeperCommand(char *cmdline
, uint32_t *offFlags
, const uint32_t allowedFlags
)
2485 uint32_t len
= strlen(cmdline
);
2486 uint8_t beeperCount
= beeperTableEntryCount();
2489 cliPrintf("Disabled:");
2490 for (int32_t i
= 0; ; i
++) {
2491 if (i
== beeperCount
- 1) {
2497 if (beeperModeMaskForTableIndex(i
) & *offFlags
)
2498 cliPrintf(" %s", beeperNameForTableIndex(i
));
2501 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2502 cliPrint("Available:");
2503 for (uint32_t i
= 0; i
< beeperCount
; i
++) {
2504 if (beeperModeMaskForTableIndex(i
) & allowedFlags
) {
2505 cliPrintf(" %s", beeperNameForTableIndex(i
));
2510 bool remove
= false;
2511 if (cmdline
[0] == '-') {
2512 remove
= true; // this is for beeper OFF condition
2517 for (uint32_t i
= 0; ; i
++) {
2518 if (i
== beeperCount
) {
2519 cliPrintErrorLinef("Invalid name");
2522 if (strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0 && beeperModeMaskForTableIndex(i
) & (allowedFlags
| BEEPER_GET_FLAG(BEEPER_ALL
))) {
2523 if (remove
) { // beeper off
2524 if (i
== BEEPER_ALL
- 1) {
2525 *offFlags
= allowedFlags
;
2527 *offFlags
|= beeperModeMaskForTableIndex(i
);
2529 cliPrint("Disabled");
2532 if (i
== BEEPER_ALL
- 1) {
2535 *offFlags
&= ~beeperModeMaskForTableIndex(i
);
2537 cliPrint("Enabled");
2539 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
2546 #if defined(USE_DSHOT)
2547 static void cliBeacon(char *cmdline
)
2549 processBeeperCommand(cmdline
, &(beeperConfigMutable()->dshotBeaconOffFlags
), DSHOT_BEACON_ALLOWED_MODES
);
2553 static void cliBeeper(char *cmdline
)
2555 processBeeperCommand(cmdline
, &(beeperConfigMutable()->beeper_off_flags
), BEEPER_ALLOWED_MODES
);
2559 #ifdef USE_RX_FRSKY_SPI
2560 void cliFrSkyBind(char *cmdline
){
2562 switch (rxSpiConfig()->rx_spi_protocol
) {
2563 case RX_SPI_FRSKY_D
:
2564 case RX_SPI_FRSKY_X
:
2567 cliPrint("Binding...");
2571 cliPrint("Not supported.");
2578 static void printMap(uint8_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
)
2580 bool equalsDefault
= true;
2582 char bufDefault
[16];
2584 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2585 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2586 if (defaultRxConfig
) {
2587 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2588 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
2593 const char *formatMap
= "map %s";
2594 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
2595 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
2599 static void cliMap(char *cmdline
)
2602 char buf
[RX_MAPPABLE_CHANNEL_COUNT
+ 1];
2604 uint32_t len
= strlen(cmdline
);
2605 if (len
== RX_MAPPABLE_CHANNEL_COUNT
) {
2607 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2608 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
2612 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2613 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
2615 if (strchr(rcChannelLetters
, buf
[i
]) && !strchr(buf
+ i
+ 1, buf
[i
]))
2618 cliShowParseError();
2621 parseRcChannels(buf
, rxConfigMutable());
2622 } else if (len
> 0) {
2623 cliShowParseError();
2627 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2628 buf
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
2632 cliPrintLinef("map %s", buf
);
2635 static char *skipSpace(char *buffer
)
2637 while (*(buffer
) == ' ') {
2644 static char *checkCommand(char *cmdLine
, const char *command
)
2646 if (!strncasecmp(cmdLine
, command
, strlen(command
)) // command names match
2647 && (isspace((unsigned)cmdLine
[strlen(command
)]) || cmdLine
[strlen(command
)] == 0)) {
2648 return skipSpace(cmdLine
+ strlen(command
) + 1);
2654 static void cliRebootEx(bool bootLoader
)
2656 cliPrint("\r\nRebooting");
2657 bufWriterFlush(cliWriter
);
2658 waitForSerialPortToFinishTransmitting(cliPort
);
2661 systemResetToBootloader();
2667 static void cliReboot(void)
2672 static void cliBootloader(char *cmdLine
)
2676 cliPrintHashLine("restarting in bootloader mode");
2680 static void cliExit(char *cmdline
)
2684 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2685 bufWriterFlush(cliWriter
);
2690 // incase a motor was left running during motortest, clear it here
2691 mixerResetDisarmedMotors();
2698 static void cliGpsPassthrough(char *cmdline
)
2702 gpsEnablePassthrough(cliPort
);
2706 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
2707 static void cliPrintGyroRegisters(uint8_t whichSensor
)
2709 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor
, MPU_RA_WHO_AM_I
));
2710 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_CONFIG
));
2711 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor
, MPU_RA_GYRO_CONFIG
));
2714 static void cliDumpGyroRegisters(char *cmdline
)
2716 #ifdef USE_DUAL_GYRO
2717 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_1
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
2718 cliPrintLinef("\r\n# Gyro 1");
2719 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
2721 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_2
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
2722 cliPrintLinef("\r\n# Gyro 2");
2723 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2
);
2726 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
2727 #endif // USE_DUAL_GYRO
2733 static int parseOutputIndex(char *pch
, bool allowAllEscs
) {
2734 int outputIndex
= atoi(pch
);
2735 if ((outputIndex
>= 0) && (outputIndex
< getMotorCount())) {
2736 cliPrintLinef("Using output %d.", outputIndex
);
2737 } else if (allowAllEscs
&& outputIndex
== ALL_MOTORS
) {
2738 cliPrintLinef("Using all outputs.");
2740 cliPrintErrorLinef("Invalid output number. Range: 0 %d.", getMotorCount() - 1);
2748 #if defined(USE_DSHOT)
2749 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2751 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
2752 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
2753 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
2761 #define ESC_INFO_VERSION_POSITION 12
2763 void printEscInfo(const uint8_t *escInfoBuffer
, uint8_t bytesRead
)
2765 bool escInfoReceived
= false;
2766 if (bytesRead
> ESC_INFO_VERSION_POSITION
) {
2767 uint8_t escInfoVersion
;
2768 uint8_t frameLength
;
2769 if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 254) {
2770 escInfoVersion
= ESC_INFO_BLHELI32
;
2771 frameLength
= ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
;
2772 } else if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 255) {
2773 escInfoVersion
= ESC_INFO_KISS_V2
;
2774 frameLength
= ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE
;
2776 escInfoVersion
= ESC_INFO_KISS_V1
;
2777 frameLength
= ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE
;
2780 if (bytesRead
== frameLength
) {
2781 escInfoReceived
= true;
2783 if (calculateCrc8(escInfoBuffer
, frameLength
- 1) == escInfoBuffer
[frameLength
- 1]) {
2784 uint8_t firmwareVersion
= 0;
2785 uint8_t firmwareSubVersion
= 0;
2786 uint8_t escType
= 0;
2787 switch (escInfoVersion
) {
2788 case ESC_INFO_KISS_V1
:
2789 firmwareVersion
= escInfoBuffer
[12];
2790 firmwareSubVersion
= (escInfoBuffer
[13] & 0x1f) + 97;
2791 escType
= (escInfoBuffer
[13] & 0xe0) >> 5;
2794 case ESC_INFO_KISS_V2
:
2795 firmwareVersion
= escInfoBuffer
[13];
2796 firmwareSubVersion
= escInfoBuffer
[14];
2797 escType
= escInfoBuffer
[15];
2800 case ESC_INFO_BLHELI32
:
2801 firmwareVersion
= escInfoBuffer
[13];
2802 firmwareSubVersion
= escInfoBuffer
[14];
2803 escType
= escInfoBuffer
[15];
2808 cliPrint("ESC Type: ");
2809 switch (escInfoVersion
) {
2810 case ESC_INFO_KISS_V1
:
2811 case ESC_INFO_KISS_V2
:
2814 cliPrintLine("KISS8A");
2818 cliPrintLine("KISS16A");
2822 cliPrintLine("KISS24A");
2826 cliPrintLine("KISS Ultralite");
2830 cliPrintLine("unknown");
2836 case ESC_INFO_BLHELI32
:
2838 char *escType
= (char *)(escInfoBuffer
+ 31);
2840 cliPrintLine(escType
);
2846 cliPrint("MCU Serial No: 0x");
2847 for (int i
= 0; i
< 12; i
++) {
2848 if (i
&& (i
% 3 == 0)) {
2851 cliPrintf("%02x", escInfoBuffer
[i
]);
2855 switch (escInfoVersion
) {
2856 case ESC_INFO_KISS_V1
:
2857 case ESC_INFO_KISS_V2
:
2858 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion
/ 100, firmwareVersion
% 100, (char)firmwareSubVersion
);
2861 case ESC_INFO_BLHELI32
:
2862 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion
, firmwareSubVersion
);
2866 if (escInfoVersion
== ESC_INFO_KISS_V2
|| escInfoVersion
== ESC_INFO_BLHELI32
) {
2867 cliPrintLinef("Rotation Direction: %s", escInfoBuffer
[16] ? "reversed" : "normal");
2868 cliPrintLinef("3D: %s", escInfoBuffer
[17] ? "on" : "off");
2869 if (escInfoVersion
== ESC_INFO_BLHELI32
) {
2870 uint8_t setting
= escInfoBuffer
[18];
2871 cliPrint("Low voltage Limit: ");
2874 cliPrintLine("off");
2878 cliPrintLine("unsupported");
2882 cliPrintLinef("%d.%01d", setting
/ 10, setting
% 10);
2887 setting
= escInfoBuffer
[19];
2888 cliPrint("Current Limit: ");
2891 cliPrintLine("off");
2895 cliPrintLine("unsupported");
2899 cliPrintLinef("%d", setting
);
2904 for (int i
= 0; i
< 4; i
++) {
2905 setting
= escInfoBuffer
[i
+ 20];
2906 cliPrintLinef("LED %d: %s", i
, setting
? (setting
== 255) ? "unsupported" : "on" : "off");
2911 cliPrintErrorLinef("Checksum Error.");
2916 if (!escInfoReceived
) {
2917 cliPrintLine("No Info.");
2921 static void executeEscInfoCommand(uint8_t escIndex
)
2923 cliPrintLinef("Info for ESC %d:", escIndex
);
2925 uint8_t escInfoBuffer
[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
];
2927 startEscDataRead(escInfoBuffer
, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
);
2929 pwmWriteDshotCommand(escIndex
, getMotorCount(), DSHOT_CMD_ESC_INFO
, true);
2933 printEscInfo(escInfoBuffer
, getNumberEscBytesRead());
2935 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
2937 static void cliDshotProg(char *cmdline
)
2939 if (isEmpty(cmdline
) || motorConfig()->dev
.motorPwmProtocol
< PWM_TYPE_DSHOT150
) {
2940 cliShowParseError();
2946 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2949 bool firstCommand
= true;
2950 while (pch
!= NULL
) {
2953 escIndex
= parseOutputIndex(pch
, true);
2954 if (escIndex
== -1) {
2961 int command
= atoi(pch
);
2962 if (command
>= 0 && command
< DSHOT_MIN_THROTTLE
) {
2966 if (command
== DSHOT_CMD_ESC_INFO
) {
2967 delay(5); // Wait for potential ESC telemetry transmission to finish
2972 firstCommand
= false;
2975 if (command
!= DSHOT_CMD_ESC_INFO
) {
2976 pwmWriteDshotCommand(escIndex
, getMotorCount(), command
, true);
2978 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2979 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
2980 if (escIndex
!= ALL_MOTORS
) {
2981 executeEscInfoCommand(escIndex
);
2983 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
2984 executeEscInfoCommand(i
);
2990 cliPrintLine("Not supported.");
2994 cliPrintLinef("Command Sent: %d", command
);
2997 cliPrintErrorLinef("Invalid command. Range: 1 - %d.", DSHOT_MIN_THROTTLE
- 1);
3005 pch
= strtok_r(NULL
, " ", &saveptr
);
3012 #ifdef USE_ESCSERIAL
3013 static void cliEscPassthrough(char *cmdline
)
3015 if (isEmpty(cmdline
)) {
3016 cliShowParseError();
3022 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3026 while (pch
!= NULL
) {
3029 if (strncasecmp(pch
, "sk", strlen(pch
)) == 0) {
3030 mode
= PROTOCOL_SIMONK
;
3031 } else if (strncasecmp(pch
, "bl", strlen(pch
)) == 0) {
3032 mode
= PROTOCOL_BLHELI
;
3033 } else if (strncasecmp(pch
, "ki", strlen(pch
)) == 0) {
3034 mode
= PROTOCOL_KISS
;
3035 } else if (strncasecmp(pch
, "cc", strlen(pch
)) == 0) {
3036 mode
= PROTOCOL_KISSALL
;
3038 cliShowParseError();
3044 escIndex
= parseOutputIndex(pch
, mode
== PROTOCOL_KISS
);
3045 if (escIndex
== -1) {
3051 cliShowParseError();
3059 pch
= strtok_r(NULL
, " ", &saveptr
);
3062 escEnablePassthrough(cliPort
, escIndex
, mode
);
3066 #ifndef USE_QUAD_MIXER_ONLY
3067 static void cliMixer(char *cmdline
)
3071 len
= strlen(cmdline
);
3074 cliPrintLinef("Mixer: %s", mixerNames
[mixerConfig()->mixerMode
- 1]);
3076 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3077 cliPrint("Available:");
3078 for (uint32_t i
= 0; ; i
++) {
3079 if (mixerNames
[i
] == NULL
)
3081 cliPrintf(" %s", mixerNames
[i
]);
3087 for (uint32_t i
= 0; ; i
++) {
3088 if (mixerNames
[i
] == NULL
) {
3089 cliPrintErrorLinef("Invalid name");
3092 if (strncasecmp(cmdline
, mixerNames
[i
], len
) == 0) {
3093 mixerConfigMutable()->mixerMode
= i
+ 1;
3102 static void cliMotor(char *cmdline
)
3104 if (isEmpty(cmdline
)) {
3105 cliShowParseError();
3114 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
3116 while (pch
!= NULL
) {
3119 motorIndex
= parseOutputIndex(pch
, true);
3120 if (motorIndex
== -1) {
3126 motorValue
= atoi(pch
);
3131 pch
= strtok_r(NULL
, " ", &saveptr
);
3135 if (motorValue
< PWM_RANGE_MIN
|| motorValue
> PWM_RANGE_MAX
) {
3136 cliShowArgumentRangeError("value", 1000, 2000);
3138 uint32_t motorOutputValue
= convertExternalToMotor(motorValue
);
3140 if (motorIndex
!= ALL_MOTORS
) {
3141 motor_disarmed
[motorIndex
] = motorOutputValue
;
3143 cliPrintLinef("motor %d: %d", motorIndex
, motorOutputValue
);
3145 for (int i
= 0; i
< getMotorCount(); i
++) {
3146 motor_disarmed
[i
] = motorOutputValue
;
3149 cliPrintLinef("all motors: %d", motorOutputValue
);
3153 cliShowParseError();
3158 static void cliPlaySound(char *cmdline
)
3162 static int lastSoundIdx
= -1;
3164 if (isEmpty(cmdline
)) {
3165 i
= lastSoundIdx
+ 1; //next sound index
3166 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3167 while (true) { //no name for index; try next one
3168 if (++i
>= beeperTableEntryCount())
3169 i
= 0; //if end then wrap around to first entry
3170 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
3171 break; //if name OK then play sound below
3172 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
3173 cliPrintErrorLinef("Error playing sound");
3178 } else { //index value was given
3180 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3181 cliPrintLinef("No sound for index %d", i
);
3187 cliPrintLinef("Playing sound %d: %s", i
, name
);
3188 beeper(beeperModeForTableIndex(i
));
3192 static void cliProfile(char *cmdline
)
3194 if (isEmpty(cmdline
)) {
3195 cliPrintLinef("profile %d", getPidProfileIndexToUse());
3198 const int i
= atoi(cmdline
);
3199 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
3200 changePidProfile(i
);
3206 static void cliRateProfile(char *cmdline
)
3208 if (isEmpty(cmdline
)) {
3209 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
3212 const int i
= atoi(cmdline
);
3213 if (i
>= 0 && i
< CONTROL_RATE_PROFILE_COUNT
) {
3214 changeControlRateProfile(i
);
3220 static void cliDumpPidProfile(uint8_t pidProfileIndex
, uint8_t dumpMask
)
3222 if (pidProfileIndex
>= MAX_PROFILE_COUNT
) {
3227 pidProfileIndexToUse
= pidProfileIndex
;
3229 cliPrintHashLine("profile");
3232 dumpAllValues(PROFILE_VALUE
, dumpMask
);
3234 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
3237 static void cliDumpRateProfile(uint8_t rateProfileIndex
, uint8_t dumpMask
)
3239 if (rateProfileIndex
>= CONTROL_RATE_PROFILE_COUNT
) {
3244 rateProfileIndexToUse
= rateProfileIndex
;
3246 cliPrintHashLine("rateprofile");
3249 dumpAllValues(PROFILE_RATE_VALUE
, dumpMask
);
3251 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
3254 static void cliSave(char *cmdline
)
3258 cliPrintHashLine("saving");
3260 #if defined(USE_BOARD_INFO)
3261 if (boardInformationUpdated
) {
3262 persistBoardInformation();
3264 #if defined(USE_SIGNATURE)
3265 if (signatureUpdated
) {
3269 #endif // USE_BOARD_INFO
3271 if (featureMaskIsCopied
) {
3272 writeEEPROMWithFeatures(featureMaskCopy
);
3280 static void cliDefaults(char *cmdline
)
3284 if (isEmpty(cmdline
)) {
3286 } else if (strncasecmp(cmdline
, "nosave", 6) == 0) {
3287 saveConfigs
= false;
3292 cliPrintHashLine("resetting to defaults");
3301 void cliPrintVarDefault(const clivalue_t
*value
)
3303 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
3305 const char *defaultFormat
= "Default value: ";
3306 const int valueOffset
= getValueOffset(value
);
3307 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
3308 if (!equalsDefault
) {
3309 cliPrintf(defaultFormat
, value
->name
);
3310 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
3316 STATIC_UNIT_TESTED
void cliGet(char *cmdline
)
3318 const clivalue_t
*val
;
3319 int matchedCommands
= 0;
3321 pidProfileIndexToUse
= getCurrentPidProfileIndex();
3322 rateProfileIndexToUse
= getCurrentControlRateProfileIndex();
3324 backupAndResetConfigs();
3326 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
3327 if (strcasestr(valueTable
[i
].name
, cmdline
)) {
3328 val
= &valueTable
[i
];
3329 if (matchedCommands
> 0) {
3332 cliPrintf("%s = ", valueTable
[i
].name
);
3333 cliPrintVar(val
, 0);
3335 switch (val
->type
& VALUE_SECTION_MASK
) {
3340 case PROFILE_RATE_VALUE
:
3348 cliPrintVarRange(val
);
3349 cliPrintVarDefault(val
);
3356 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
3357 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
3359 if (matchedCommands
) {
3363 cliPrintErrorLinef("Invalid name");
3366 static uint8_t getWordLength(char *bufBegin
, char *bufEnd
)
3368 while (*(bufEnd
- 1) == ' ') {
3372 return bufEnd
- bufBegin
;
3375 STATIC_UNIT_TESTED
void cliSet(char *cmdline
)
3377 const uint32_t len
= strlen(cmdline
);
3380 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
3381 cliPrintLine("Current settings: ");
3383 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
3384 const clivalue_t
*val
= &valueTable
[i
];
3385 cliPrintf("%s = ", valueTable
[i
].name
);
3386 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3389 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
3392 uint8_t variableNameLength
= getWordLength(cmdline
, eqptr
);
3394 // skip the '=' and any ' ' characters
3396 eqptr
= skipSpace(eqptr
);
3398 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
3399 const clivalue_t
*val
= &valueTable
[i
];
3401 // ensure exact match when setting to prevent setting variables with shorter names
3402 if (strncasecmp(cmdline
, val
->name
, strlen(val
->name
)) == 0 && variableNameLength
== strlen(val
->name
)) {
3404 bool valueChanged
= false;
3406 switch (val
->type
& VALUE_MODE_MASK
) {
3408 int16_t value
= atoi(eqptr
);
3410 if (value
>= val
->config
.minmax
.min
&& value
<= val
->config
.minmax
.max
) {
3411 cliSetVar(val
, value
);
3412 valueChanged
= true;
3420 if ((val
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
3421 tableIndex
= TABLE_OFF_ON
;
3423 tableIndex
= val
->config
.lookup
.tableIndex
;
3425 const lookupTableEntry_t
*tableEntry
= &lookupTables
[tableIndex
];
3426 bool matched
= false;
3427 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
3428 matched
= tableEntry
->values
[tableValueIndex
] && strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
3431 value
= tableValueIndex
;
3433 cliSetVar(val
, value
);
3434 valueChanged
= true;
3442 const uint8_t arrayLength
= val
->config
.array
.length
;
3443 char *valPtr
= eqptr
;
3446 while (i
< arrayLength
&& valPtr
!= NULL
) {
3448 valPtr
= skipSpace(valPtr
);
3450 // process substring starting at valPtr
3451 // note: no need to copy substrings for atoi()
3452 // it stops at the first character that cannot be converted...
3453 switch (val
->type
& VALUE_TYPE_MASK
) {
3457 // fetch data pointer
3458 uint8_t *data
= (uint8_t *)cliGetValuePointer(val
) + i
;
3460 *data
= (uint8_t)atoi((const char*) valPtr
);
3466 // fetch data pointer
3467 int8_t *data
= (int8_t *)cliGetValuePointer(val
) + i
;
3469 *data
= (int8_t)atoi((const char*) valPtr
);
3475 // fetch data pointer
3476 uint16_t *data
= (uint16_t *)cliGetValuePointer(val
) + i
;
3478 *data
= (uint16_t)atoi((const char*) valPtr
);
3484 // fetch data pointer
3485 int16_t *data
= (int16_t *)cliGetValuePointer(val
) + i
;
3487 *data
= (int16_t)atoi((const char*) valPtr
);
3493 // find next comma (or end of string)
3494 valPtr
= strchr(valPtr
, ',') + 1;
3501 valueChanged
= true;
3508 cliPrintf("%s set to ", val
->name
);
3509 cliPrintVar(val
, 0);
3511 cliPrintErrorLinef("Invalid value");
3512 cliPrintVarRange(val
);
3518 cliPrintErrorLinef("Invalid name");
3520 // no equals, check for matching variables.
3525 static void cliStatus(char *cmdline
)
3529 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3532 char buf
[FORMATTED_DATE_TIME_BUFSIZE
];
3534 if (rtcGetDateTime(&dt
)) {
3535 dateTimeFormatLocal(buf
, &dt
);
3536 cliPrintLinef("Current Time: %s", buf
);
3540 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
3542 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock
/ 1000000));
3544 #ifdef USE_ADC_INTERNAL
3545 uint16_t vrefintMv
= getVrefMv();
3546 int16_t coretemp
= getCoreTemperatureCelsius();
3547 cliPrintf(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv
/ 1000, (vrefintMv
% 1000) / 10, coretemp
);
3550 #if defined(USE_SENSOR_NAMES)
3551 const uint32_t detectedSensorsMask
= sensorsMask();
3552 for (uint32_t i
= 0; ; i
++) {
3553 if (sensorTypeNames
[i
] == NULL
) {
3556 const uint32_t mask
= (1 << i
);
3557 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
3558 const uint8_t sensorHardwareIndex
= detectedSensors
[i
];
3559 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
3560 cliPrintf(", %s=%s", sensorTypeNames
[i
], sensorHardware
);
3561 if (mask
== SENSOR_ACC
&& acc
.dev
.revisionCode
) {
3562 cliPrintf(".%c", acc
.dev
.revisionCode
);
3566 #endif /* USE_SENSOR_NAMES */
3574 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
3576 const uint16_t i2cErrorCounter
= 0;
3580 cliPrintf("Stack used: %d, ", stackUsedSize());
3582 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
3583 #ifdef EEPROM_IN_RAM
3584 #define CONFIG_SIZE EEPROM_SIZE
3586 #define CONFIG_SIZE (&__config_end - &__config_start)
3588 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter
, getEEPROMConfigSize(), CONFIG_SIZE
);
3590 const int gyroRate
= getTaskDeltaTime(TASK_GYROPID
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_GYROPID
)));
3591 const int rxRate
= currentRxRefreshRate
== 0 ? 0 : (int)(1000000.0f
/ ((float)currentRxRefreshRate
));
3592 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
3593 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
3594 constrain(averageSystemLoadPercent
, 0, 100), getTaskDeltaTime(TASK_GYROPID
), gyroRate
, rxRate
, systemRate
);
3595 cliPrint("Arming disable flags:");
3596 armingDisableFlags_e flags
= getArmingDisableFlags();
3598 const int bitpos
= ffs(flags
) - 1;
3599 flags
&= ~(1 << bitpos
);
3600 cliPrintf(" %s", armingDisableFlagNames
[bitpos
]);
3605 #ifndef SKIP_TASK_STATISTICS
3606 static void cliTasks(char *cmdline
)
3610 int averageLoadSum
= 0;
3613 if (systemConfig()->task_statistics
) {
3614 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
3616 cliPrintLine("Task list");
3619 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
3620 cfTaskInfo_t taskInfo
;
3621 getTaskInfo(taskId
, &taskInfo
);
3622 if (taskInfo
.isEnabled
) {
3624 int subTaskFrequency
= 0;
3625 if (taskId
== TASK_GYROPID
) {
3626 subTaskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3627 taskFrequency
= subTaskFrequency
/ pidConfig()->pid_process_denom
;
3628 if (pidConfig()->pid_process_denom
> 1) {
3629 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
3631 taskFrequency
= subTaskFrequency
;
3632 cliPrintf("%02d - (%11s/%3s) ", taskId
, taskInfo
.subTaskName
, taskInfo
.taskName
);
3635 taskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3636 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
3638 const int maxLoad
= taskInfo
.maxExecutionTime
== 0 ? 0 :(taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
3639 const int averageLoad
= taskInfo
.averageExecutionTime
== 0 ? 0 : (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
3640 if (taskId
!= TASK_SERIAL
) {
3641 maxLoadSum
+= maxLoad
;
3642 averageLoadSum
+= averageLoad
;
3644 if (systemConfig()->task_statistics
) {
3645 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
3646 taskFrequency
, taskInfo
.maxExecutionTime
, taskInfo
.averageExecutionTime
,
3647 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, taskInfo
.totalExecutionTime
/ 1000);
3649 cliPrintLinef("%6d", taskFrequency
);
3651 if (taskId
== TASK_GYROPID
&& pidConfig()->pid_process_denom
> 1) {
3652 cliPrintLinef(" - (%15s) %6d", taskInfo
.subTaskName
, subTaskFrequency
);
3655 schedulerResetTaskMaxExecutionTime(taskId
);
3658 if (systemConfig()->task_statistics
) {
3659 cfCheckFuncInfo_t checkFuncInfo
;
3660 getCheckFuncInfo(&checkFuncInfo
);
3661 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo
.maxExecutionTime
, checkFuncInfo
.averageExecutionTime
, checkFuncInfo
.totalExecutionTime
/ 1000);
3662 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
3667 static void cliVersion(char *cmdline
)
3671 cliPrintLinef("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
3674 systemConfig()->boardIdentifier
,
3679 MSP_API_VERSION_STRING
3683 #ifdef USE_RC_SMOOTHING_FILTER
3684 static void cliRcSmoothing(char *cmdline
)
3687 cliPrint("# RC Smoothing Type: ");
3688 if (rxConfig()->rc_smoothing_type
== RC_SMOOTHING_TYPE_FILTER
) {
3689 cliPrintLine("FILTER");
3690 uint16_t avgRxFrameMs
= rcSmoothingGetValue(RC_SMOOTHING_VALUE_AVERAGE_FRAME
);
3691 if (rcSmoothingAutoCalculate()) {
3692 cliPrint("# Detected RX frame rate: ");
3693 if (avgRxFrameMs
== 0) {
3694 cliPrintLine("NO SIGNAL");
3696 cliPrintLinef("%d.%dms", avgRxFrameMs
/ 1000, avgRxFrameMs
% 1000);
3699 cliPrint("# Input filter type: ");
3700 cliPrintLinef(lookupTables
[TABLE_RC_SMOOTHING_INPUT_TYPE
].values
[rxConfig()->rc_smoothing_input_type
]);
3701 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingGetValue(RC_SMOOTHING_VALUE_INPUT_ACTIVE
));
3702 if (rxConfig()->rc_smoothing_input_cutoff
== 0) {
3703 cliPrintLine("(auto)");
3705 cliPrintLine("(manual)");
3707 cliPrint("# Derivative filter type: ");
3708 cliPrintLinef(lookupTables
[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE
].values
[rxConfig()->rc_smoothing_derivative_type
]);
3709 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingGetValue(RC_SMOOTHING_VALUE_DERIVATIVE_ACTIVE
));
3710 if (rxConfig()->rc_smoothing_derivative_type
== RC_SMOOTHING_DERIVATIVE_OFF
) {
3711 cliPrintLine("off)");
3713 if (rxConfig()->rc_smoothing_derivative_cutoff
== 0) {
3714 cliPrintLine("auto)");
3716 cliPrintLine("manual)");
3720 cliPrintLine("INTERPOLATION");
3723 #endif // USE_RC_SMOOTHING_FILTER
3725 #if defined(USE_RESOURCE_MGMT)
3727 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
3730 const uint8_t owner
;
3734 const uint8_t maxIndex
;
3735 } cliResourceValue_t
;
3737 // Handy macros for keeping the table tidy.
3738 // DEFS : Single entry
3739 // DEFA : Array of uint8_t (stride = 1)
3740 // DEFW : Wider stride case; array of structs.
3742 #define DEFS(owner, pgn, type, member) \
3743 { owner, pgn, 0, offsetof(type, member), 0 }
3745 #define DEFA(owner, pgn, type, member, max) \
3746 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
3748 #define DEFW(owner, pgn, type, member, max) \
3749 { owner, pgn, sizeof(type), offsetof(type, member), max }
3751 const cliResourceValue_t resourceTable
[] = {
3753 DEFS( OWNER_BEEPER
, PG_BEEPER_DEV_CONFIG
, beeperDevConfig_t
, ioTag
) ,
3755 DEFA( OWNER_MOTOR
, PG_MOTOR_CONFIG
, motorConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_MOTORS
),
3757 DEFA( OWNER_SERVO
, PG_SERVO_CONFIG
, servoConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_SERVOS
),
3759 #if defined(USE_PPM)
3760 DEFS( OWNER_PPMINPUT
, PG_PPM_CONFIG
, ppmConfig_t
, ioTag
),
3762 #if defined(USE_PWM)
3763 DEFA( OWNER_PWMINPUT
, PG_PWM_CONFIG
, pwmConfig_t
, ioTags
[0], PWM_INPUT_PORT_COUNT
),
3765 #ifdef USE_RANGEFINDER_HCSR04
3766 DEFS( OWNER_SONAR_TRIGGER
, PG_SONAR_CONFIG
, sonarConfig_t
, triggerTag
),
3767 DEFS( OWNER_SONAR_ECHO
, PG_SONAR_CONFIG
, sonarConfig_t
, echoTag
),
3769 #ifdef USE_LED_STRIP
3770 DEFS( OWNER_LED_STRIP
, PG_LED_STRIP_CONFIG
, ledStripConfig_t
, ioTag
),
3772 DEFA( OWNER_SERIAL_TX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagTx
[0], SERIAL_PORT_MAX_INDEX
),
3773 DEFA( OWNER_SERIAL_RX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagRx
[0], SERIAL_PORT_MAX_INDEX
),
3775 DEFA( OWNER_INVERTER
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagInverter
[0], SERIAL_PORT_MAX_INDEX
),
3778 DEFW( OWNER_I2C_SCL
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagScl
, I2CDEV_COUNT
),
3779 DEFW( OWNER_I2C_SDA
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagSda
, I2CDEV_COUNT
),
3781 DEFA( OWNER_LED
, PG_STATUS_LED_CONFIG
, statusLedConfig_t
, ioTags
[0], STATUS_LED_NUMBER
),
3782 #ifdef USE_SPEKTRUM_BIND
3783 DEFS( OWNER_RX_BIND
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_pin_override_ioTag
),
3784 DEFS( OWNER_RX_BIND_PLUG
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_plug_ioTag
),
3786 #ifdef USE_TRANSPONDER
3787 DEFS( OWNER_TRANSPONDER
, PG_TRANSPONDER_CONFIG
, transponderConfig_t
, ioTag
),
3790 DEFW( OWNER_SPI_SCK
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagSck
, SPIDEV_COUNT
),
3791 DEFW( OWNER_SPI_MISO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMiso
, SPIDEV_COUNT
),
3792 DEFW( OWNER_SPI_MOSI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMosi
, SPIDEV_COUNT
),
3794 #ifdef USE_ESCSERIAL
3795 DEFS( OWNER_ESCSERIAL
, PG_ESCSERIAL_CONFIG
, escSerialConfig_t
, ioTag
),
3797 #ifdef USE_CAMERA_CONTROL
3798 DEFS( OWNER_CAMERA_CONTROL
, PG_CAMERA_CONTROL_CONFIG
, cameraControlConfig_t
, ioTag
),
3801 DEFS( OWNER_ADC_BATT
, PG_ADC_CONFIG
, adcConfig_t
, vbat
.ioTag
),
3802 DEFS( OWNER_ADC_RSSI
, PG_ADC_CONFIG
, adcConfig_t
, rssi
.ioTag
),
3803 DEFS( OWNER_ADC_CURR
, PG_ADC_CONFIG
, adcConfig_t
, current
.ioTag
),
3804 DEFS( OWNER_ADC_EXT
, PG_ADC_CONFIG
, adcConfig_t
, external1
.ioTag
),
3807 DEFS( OWNER_BARO_CS
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_spi_csn
),
3810 DEFS( OWNER_COMPASS_CS
, PG_COMPASS_CONFIG
, compassConfig_t
, mag_spi_csn
),
3811 #ifdef USE_MAG_DATA_READY_SIGNAL
3812 DEFS( OWNER_COMPASS_EXTI
, PG_COMPASS_CONFIG
, compassConfig_t
, interruptTag
),
3816 DEFS( OWNER_SDCARD_CS
, PG_SDCARD_CONFIG
, sdcardConfig_t
, chipSelectTag
),
3817 DEFS( OWNER_SDCARD_DETECT
, PG_SDCARD_CONFIG
, sdcardConfig_t
, cardDetectTag
),
3820 DEFA( OWNER_PINIO
, PG_PINIO_CONFIG
, pinioConfig_t
, ioTag
, PINIO_COUNT
),
3822 #if defined(USE_USB_MSC)
3823 DEFS( OWNER_USB_MSC_PIN
, PG_USB_CONFIG
, usbDev_t
, mscButtonPin
),
3826 DEFS( OWNER_FLASH_CS
, PG_FLASH_CONFIG
, flashConfig_t
, csTag
),
3829 DEFS( OWNER_OSD_CS
, PG_MAX7456_CONFIG
, max7456Config_t
, csTag
),
3832 DEFA( OWNER_SPI_PREINIT_IPU
, PG_SPI_PREINIT_IPU_CONFIG
, spiCs_t
, csnTag
, SPI_PREINIT_IPU_COUNT
),
3833 DEFA( OWNER_SPI_PREINIT_OPU
, PG_SPI_PREINIT_OPU_CONFIG
, spiCs_t
, csnTag
, SPI_PREINIT_OPU_COUNT
),
3836 DEFS( OWNER_RX_SPI_CS
, PG_RX_SPI_CONFIG
, rxSpiConfig_t
, csnTag
),
3844 static ioTag_t
*getIoTag(const cliResourceValue_t value
, uint8_t index
)
3846 const pgRegistry_t
* rec
= pgFind(value
.pgn
);
3847 return CONST_CAST(ioTag_t
*, rec
->address
+ value
.stride
* index
+ value
.offset
);
3850 static void printResource(uint8_t dumpMask
)
3852 for (unsigned int i
= 0; i
< ARRAYLEN(resourceTable
); i
++) {
3853 const char* owner
= ownerNames
[resourceTable
[i
].owner
];
3854 const pgRegistry_t
* pg
= pgFind(resourceTable
[i
].pgn
);
3855 const void *currentConfig
;
3856 const void *defaultConfig
;
3857 if (configIsInCopy
) {
3858 currentConfig
= pg
->copy
;
3859 defaultConfig
= pg
->address
;
3861 currentConfig
= pg
->address
;
3862 defaultConfig
= NULL
;
3865 for (int index
= 0; index
< MAX_RESOURCE_INDEX(resourceTable
[i
].maxIndex
); index
++) {
3866 const ioTag_t ioTag
= *((const uint8_t *)currentConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
3867 const ioTag_t ioTagDefault
= *((const uint8_t *)defaultConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
3869 bool equalsDefault
= ioTag
== ioTagDefault
;
3870 const char *format
= "resource %s %d %c%02d";
3871 const char *formatUnassigned
= "resource %s %d NONE";
3872 if (!ioTagDefault
) {
3873 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
3875 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTagDefault
) + 'A', IO_GPIOPinIdxByTag(ioTagDefault
));
3878 if (!(dumpMask
& HIDE_UNUSED
)) {
3879 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
3882 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
));
3888 static void printResourceOwner(uint8_t owner
, uint8_t index
)
3890 cliPrintf("%s", ownerNames
[resourceTable
[owner
].owner
]);
3892 if (resourceTable
[owner
].maxIndex
> 0) {
3893 cliPrintf(" %d", RESOURCE_INDEX(index
));
3897 static void resourceCheck(uint8_t resourceIndex
, uint8_t index
, ioTag_t newTag
)
3903 const char * format
= "\r\nNOTE: %c%02d already assigned to ";
3904 for (int r
= 0; r
< (int)ARRAYLEN(resourceTable
); r
++) {
3905 for (int i
= 0; i
< MAX_RESOURCE_INDEX(resourceTable
[r
].maxIndex
); i
++) {
3906 ioTag_t
*tag
= getIoTag(resourceTable
[r
], i
);
3907 if (*tag
== newTag
) {
3908 bool cleared
= false;
3909 if (r
== resourceIndex
) {
3917 cliPrintf(format
, DEFIO_TAG_GPIOID(newTag
) + 'A', DEFIO_TAG_PIN(newTag
));
3919 printResourceOwner(r
, i
);
3923 printResourceOwner(r
, i
);
3924 cliPrintf(" disabled");
3933 static bool strToPin(char *pch
, ioTag_t
*tag
)
3935 if (strcasecmp(pch
, "NONE") == 0) {
3940 unsigned port
= (*pch
>= 'a') ? *pch
- 'a' : *pch
- 'A';
3946 *tag
= DEFIO_TAG_MAKE(port
, pin
);
3954 static void cliResource(char *cmdline
)
3956 int len
= strlen(cmdline
);
3959 printResource(DUMP_MASTER
| HIDE_UNUSED
);
3962 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3966 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
3969 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
3971 owner
= ownerNames
[ioRecs
[i
].owner
];
3973 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
);
3974 if (ioRecs
[i
].index
> 0) {
3975 cliPrintf(" %d", ioRecs
[i
].index
);
3981 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
3987 uint8_t resourceIndex
= 0;
3992 pch
= strtok_r(cmdline
, " ", &saveptr
);
3993 for (resourceIndex
= 0; ; resourceIndex
++) {
3994 if (resourceIndex
>= ARRAYLEN(resourceTable
)) {
3995 cliPrintErrorLinef("Invalid");
3999 if (strncasecmp(pch
, ownerNames
[resourceTable
[resourceIndex
].owner
], len
) == 0) {
4004 pch
= strtok_r(NULL
, " ", &saveptr
);
4007 if (resourceTable
[resourceIndex
].maxIndex
> 0 || index
> 0) {
4008 if (index
<= 0 || index
> MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
)) {
4009 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
));
4014 pch
= strtok_r(NULL
, " ", &saveptr
);
4017 ioTag_t
*tag
= getIoTag(resourceTable
[resourceIndex
], index
);
4019 if (strlen(pch
) > 0) {
4020 if (strToPin(pch
, tag
)) {
4021 if (*tag
== IO_TAG_NONE
) {
4023 cliPrintLine("Freed");
4025 cliPrintLine("Resource is freed");
4029 ioRec_t
*rec
= IO_Rec(IOGetByTag(*tag
));
4031 resourceCheck(resourceIndex
, index
, *tag
);
4033 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
4035 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
4038 cliShowParseError();
4045 cliShowParseError();
4048 static void printDma(void)
4053 cliPrintLine("DMA:");
4055 cliPrintLine("Currently active DMA:");
4058 for (int i
= 1; i
<= DMA_LAST_HANDLER
; i
++) {
4060 owner
= ownerNames
[dmaGetOwner(i
)];
4062 cliPrintf(DMA_OUTPUT_STRING
, DMA_DEVICE_NO(i
), DMA_DEVICE_INDEX(i
));
4063 uint8_t resourceIndex
= dmaGetResourceIndex(i
);
4064 if (resourceIndex
> 0) {
4065 cliPrintLinef(" %s %d", owner
, resourceIndex
);
4067 cliPrintLinef(" %s", owner
);
4072 static void cliDma(char* cmdLine
)
4077 #endif /* USE_RESOURCE_MGMT */
4079 #ifdef USE_TIMER_MGMT
4081 static void printTimer(uint8_t dumpMask
)
4083 cliPrintLine("# examples: ");
4084 const char *format
= "timer %c%02d %d";
4086 cliPrintLinef(format
, 'A', 1, 1);
4089 cliPrintLinef(format
, 'A', 1, 0);
4091 for (unsigned int i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
4093 const ioTag_t ioTag
= timerIOConfig(i
)->ioTag
;
4094 const uint8_t timerIndex
= timerIOConfig(i
)->index
;
4100 if (timerIndex
!= 0 && !(dumpMask
& HIDE_UNUSED
)) {
4101 cliDumpPrintLinef(dumpMask
, false, format
,
4102 IO_GPIOPortIdxByTag(ioTag
) + 'A',
4103 IO_GPIOPinIdxByTag(ioTag
),
4110 static void cliTimer(char *cmdline
)
4112 int len
= strlen(cmdline
);
4115 printTimer(DUMP_MASTER
| HIDE_UNUSED
);
4117 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
4118 printTimer(DUMP_MASTER
);
4124 int timerIOIndex
= -1;
4127 pch
= strtok_r(cmdline
, " ", &saveptr
);
4128 if (!pch
|| !(strToPin(pch
, &ioTag
) && IOGetByTag(ioTag
))) {
4132 /* find existing entry, or go for next available */
4133 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
4134 if (timerIOConfig(i
)->ioTag
== ioTag
) {
4139 /* first available empty slot */
4140 if (timerIOIndex
< 0 && timerIOConfig(i
)->ioTag
== IO_TAG_NONE
) {
4145 if (timerIOIndex
< 0) {
4146 cliPrintErrorLinef("Index out of range.");
4150 uint8_t timerIndex
= 0;
4151 pch
= strtok_r(NULL
, " ", &saveptr
);
4153 if (strcasecmp(pch
, "list") == 0) {
4154 /* output the list of available options */
4156 for (unsigned i
= 0; i
< USABLE_TIMER_CHANNEL_COUNT
; i
++) {
4157 if (timerHardware
[i
].tag
== ioTag
) {
4158 cliPrintLinef("# %d. TIM%d CH%d",
4160 timerGetTIMNumber(timerHardware
[i
].tim
),
4161 CC_INDEX_FROM_CHANNEL(timerHardware
[i
].channel
)
4167 } else if (strcasecmp(pch
, "none") == 0) {
4170 timerIndex
= atoi(pch
);
4177 timerIOConfigMutable(timerIOIndex
)->ioTag
= timerIndex
== 0 ? IO_TAG_NONE
: ioTag
;
4178 timerIOConfigMutable(timerIOIndex
)->index
= timerIndex
;
4180 cliPrintLine("Success");
4184 cliShowParseError();
4188 static void printConfig(char *cmdline
, bool doDiff
)
4190 uint8_t dumpMask
= DUMP_MASTER
;
4192 if ((options
= checkCommand(cmdline
, "master"))) {
4193 dumpMask
= DUMP_MASTER
; // only
4194 } else if ((options
= checkCommand(cmdline
, "profile"))) {
4195 dumpMask
= DUMP_PROFILE
; // only
4196 } else if ((options
= checkCommand(cmdline
, "rates"))) {
4197 dumpMask
= DUMP_RATES
; // only
4198 } else if ((options
= checkCommand(cmdline
, "all"))) {
4199 dumpMask
= DUMP_ALL
; // all profiles and rates
4205 dumpMask
= dumpMask
| DO_DIFF
;
4208 backupAndResetConfigs();
4209 if (checkCommand(options
, "defaults")) {
4210 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
4213 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
4214 cliPrintHashLine("version");
4218 #if defined(USE_BOARD_INFO)
4220 cliManufacturerId("");
4223 if (dumpMask
& DUMP_ALL
) {
4225 #if defined(USE_BOARD_INFO) && defined(USE_SIGNATURE)
4230 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
4231 cliPrintHashLine("reset configuration to default settings");
4232 cliPrint("defaults nosave");
4236 cliPrintHashLine("name");
4237 printName(dumpMask
, &pilotConfig_Copy
);
4239 #ifdef USE_RESOURCE_MGMT
4240 cliPrintHashLine("resources");
4241 printResource(dumpMask
);
4244 #ifndef USE_QUAD_MIXER_ONLY
4245 cliPrintHashLine("mixer");
4246 const bool equalsDefault
= mixerConfig_Copy
.mixerMode
== mixerConfig()->mixerMode
;
4247 const char *formatMixer
= "mixer %s";
4248 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig()->mixerMode
- 1]);
4249 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig_Copy
.mixerMode
- 1]);
4251 cliDumpPrintLinef(dumpMask
, customMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
4253 printMotorMix(dumpMask
, customMotorMixer_CopyArray
, customMotorMixer(0));
4256 cliPrintHashLine("servo");
4257 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0));
4259 cliPrintHashLine("servo mix");
4260 // print custom servo mixer if exists
4261 cliDumpPrintLinef(dumpMask
, customServoMixers(0)->rate
== 0, "smix reset\r\n");
4262 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0));
4266 cliPrintHashLine("feature");
4267 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig());
4269 #if defined(USE_BEEPER)
4270 cliPrintHashLine("beeper");
4271 printBeeper(dumpMask
, beeperConfig_Copy
.beeper_off_flags
, beeperConfig()->beeper_off_flags
, "beeper");
4273 #if defined(USE_DSHOT)
4274 cliPrintHashLine("beacon");
4275 printBeeper(dumpMask
, beeperConfig_Copy
.dshotBeaconOffFlags
, beeperConfig()->dshotBeaconOffFlags
, "beacon");
4277 #endif // USE_BEEPER
4279 cliPrintHashLine("map");
4280 printMap(dumpMask
, &rxConfig_Copy
, rxConfig());
4282 cliPrintHashLine("serial");
4283 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig());
4285 #ifdef USE_LED_STRIP
4286 cliPrintHashLine("led");
4287 printLed(dumpMask
, ledStripConfig_Copy
.ledConfigs
, ledStripConfig()->ledConfigs
);
4289 cliPrintHashLine("color");
4290 printColor(dumpMask
, ledStripConfig_Copy
.colors
, ledStripConfig()->colors
);
4292 cliPrintHashLine("mode_color");
4293 printModeColor(dumpMask
, &ledStripConfig_Copy
, ledStripConfig());
4296 cliPrintHashLine("aux");
4297 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0));
4299 cliPrintHashLine("adjrange");
4300 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0));
4302 cliPrintHashLine("rxrange");
4303 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0));
4305 #ifdef USE_VTX_CONTROL
4306 cliPrintHashLine("vtx");
4307 printVtx(dumpMask
, &vtxConfig_Copy
, vtxConfig());
4310 cliPrintHashLine("rxfail");
4311 printRxFailsafe(dumpMask
, rxFailsafeChannelConfigs_CopyArray
, rxFailsafeChannelConfigs(0));
4313 cliPrintHashLine("master");
4314 dumpAllValues(MASTER_VALUE
, dumpMask
);
4316 if (dumpMask
& DUMP_ALL
) {
4317 for (uint32_t pidProfileIndex
= 0; pidProfileIndex
< MAX_PROFILE_COUNT
; pidProfileIndex
++) {
4318 cliDumpPidProfile(pidProfileIndex
, dumpMask
);
4320 cliPrintHashLine("restore original profile selection");
4322 pidProfileIndexToUse
= systemConfig_Copy
.pidProfileIndex
;
4326 pidProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4328 for (uint32_t rateIndex
= 0; rateIndex
< CONTROL_RATE_PROFILE_COUNT
; rateIndex
++) {
4329 cliDumpRateProfile(rateIndex
, dumpMask
);
4331 cliPrintHashLine("restore original rateprofile selection");
4333 rateProfileIndexToUse
= systemConfig_Copy
.activeRateProfile
;
4337 rateProfileIndexToUse
= CURRENT_PROFILE_INDEX
;
4339 cliPrintHashLine("save configuration");
4342 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
4344 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
4348 if (dumpMask
& DUMP_PROFILE
) {
4349 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
4352 if (dumpMask
& DUMP_RATES
) {
4353 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
4355 // restore configs from copies
4359 static void cliDump(char *cmdline
)
4361 printConfig(cmdline
, false);
4364 static void cliDiff(char *cmdline
)
4366 printConfig(cmdline
, true);
4369 #if defined(USE_USB_MSC)
4370 static void cliMsc(char *cmdline
)
4374 if (mscCheckFilesystemReady()) {
4375 cliPrintHashLine("Restarting in mass storage mode");
4376 cliPrint("\r\nRebooting");
4377 bufWriterFlush(cliWriter
);
4378 waitForSerialPortToFinishTransmitting(cliPort
);
4383 cliPrintHashLine("Storage not present or failed to initialize!");
4391 const char *description
;
4394 void (*func
)(char *cmdline
);
4398 #define CLI_COMMAND_DEF(name, description, args, method) \
4406 #define CLI_COMMAND_DEF(name, description, args, method) \
4413 static void cliHelp(char *cmdline
);
4415 // should be sorted a..z for bsearch()
4416 const clicmd_t cmdTable
[] = {
4417 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
4418 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux
),
4419 #if defined(USE_BEEPER)
4420 #if defined(USE_DSHOT)
4421 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
4422 "\t<->[name]", cliBeacon
),
4424 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
4425 "\t<->[name]", cliBeeper
),
4426 #endif // USE_BEEPER
4427 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL
, cliBootloader
),
4428 #if defined(USE_BOARD_INFO)
4429 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName
),
4431 #ifdef USE_LED_STRIP
4432 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
4434 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults
),
4435 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|all] {defaults}", cliDiff
),
4436 #ifdef USE_RESOURCE_MGMT
4437 CLI_COMMAND_DEF("dma", "list dma utilisation", NULL
, cliDma
),
4440 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg
),
4442 CLI_COMMAND_DEF("dump", "dump configuration",
4443 "[master|profile|rates|all] {defaults}", cliDump
),
4444 #ifdef USE_ESCSERIAL
4445 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough
),
4447 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
4448 CLI_COMMAND_DEF("feature", "configure features",
4450 "\t<+|->[name]", cliFeature
),
4452 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
4453 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
4454 #ifdef USE_FLASH_TOOLS
4455 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
4456 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
4459 #ifdef USE_RX_FRSKY_SPI
4460 CLI_COMMAND_DEF("frsky_bind", "initiate binding for FrSky SPI RX", NULL
, cliFrSkyBind
),
4462 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
4464 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
4466 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
4467 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL
, cliDumpGyroRegisters
),
4469 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
4470 #ifdef USE_LED_STRIP
4471 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
4473 #if defined(USE_BOARD_INFO)
4474 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId
),
4476 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
4477 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL
, cliMcuId
),
4478 #ifndef USE_QUAD_MIXER_ONLY
4479 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer
),
4481 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
4482 #ifdef USE_LED_STRIP
4483 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
4485 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
4487 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
4489 CLI_COMMAND_DEF("name", "name of craft", NULL
, cliName
),
4491 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]", cliPlaySound
),
4493 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile
),
4494 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile
),
4495 #ifdef USE_RC_SMOOTHING_FILTER
4496 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL
, cliRcSmoothing
),
4497 #endif // USE_RC_SMOOTHING_FILTER
4498 #ifdef USE_RESOURCE_MGMT
4499 CLI_COMMAND_DEF("resource", "show/set resources", NULL
, cliResource
),
4501 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL
, cliRxFailsafe
),
4502 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
4503 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
4505 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
4507 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
4508 #ifndef SKIP_SERIAL_PASSTHROUGH
4509 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [DTR PINIO]: passthrough to serial", cliSerialPassthrough
),
4512 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
4514 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
4515 #if defined(USE_BOARD_INFO) && defined(USE_SIGNATURE)
4516 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature
),
4519 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
4521 "\tload <mixer>\r\n"
4522 "\treverse <servo> <source> r|n", cliServoMix
),
4524 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
4525 #ifndef SKIP_TASK_STATISTICS
4526 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
4528 #ifdef USE_TIMER_MGMT
4529 CLI_COMMAND_DEF("timer", "show timer configuration", NULL
, cliTimer
),
4531 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
4532 #ifdef USE_VTX_CONTROL
4533 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL
, cliVtx
),
4537 static void cliHelp(char *cmdline
)
4541 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
4542 cliPrint(cmdTable
[i
].name
);
4544 if (cmdTable
[i
].description
) {
4545 cliPrintf(" - %s", cmdTable
[i
].description
);
4547 if (cmdTable
[i
].args
) {
4548 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
4555 void cliProcess(void)
4561 // Be a little bit tricky. Flush the last inputs buffer, if any.
4562 bufWriterFlush(cliWriter
);
4564 while (serialRxBytesWaiting(cliPort
)) {
4565 uint8_t c
= serialRead(cliPort
);
4566 if (c
== '\t' || c
== '?') {
4567 // do tab completion
4568 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
4569 uint32_t i
= bufferIndex
;
4570 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4571 if (bufferIndex
&& (strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
4577 if (pstart
) { /* Buffer matches one or more commands */
4578 for (; ; bufferIndex
++) {
4579 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
4581 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
4582 /* Unambiguous -- append a space */
4583 cliBuffer
[bufferIndex
++] = ' ';
4584 cliBuffer
[bufferIndex
] = '\0';
4587 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
4590 if (!bufferIndex
|| pstart
!= pend
) {
4591 /* Print list of ambiguous matches */
4592 cliPrint("\r\033[K");
4593 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
4594 cliPrint(cmd
->name
);
4598 i
= 0; /* Redraw prompt */
4600 for (; i
< bufferIndex
; i
++)
4601 cliWrite(cliBuffer
[i
]);
4602 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
4605 } else if (c
== 12) { // NewPage / CTRL-L
4607 cliPrint("\033[2J\033[1;1H");
4609 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
4613 // Strip comment starting with # from line
4614 char *p
= cliBuffer
;
4617 bufferIndex
= (uint32_t)(p
- cliBuffer
);
4620 // Strip trailing whitespace
4621 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
4625 // Process non-empty lines
4626 if (bufferIndex
> 0) {
4627 cliBuffer
[bufferIndex
] = 0; // null terminate
4629 const clicmd_t
*cmd
;
4631 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4632 if ((options
= checkCommand(cliBuffer
, cmd
->name
))) {
4636 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
))
4639 cliPrint("Unknown command, try 'help'");
4643 memset(cliBuffer
, 0, sizeof(cliBuffer
));
4645 // 'exit' will reset this flag, so we don't need to print prompt again
4650 } else if (c
== 127) {
4653 cliBuffer
[--bufferIndex
] = 0;
4654 cliPrint("\010 \010");
4656 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
4657 if (!bufferIndex
&& c
== ' ')
4658 continue; // Ignore leading spaces
4659 cliBuffer
[bufferIndex
++] = c
;
4665 void cliEnter(serialPort_t
*serialPort
)
4668 cliPort
= serialPort
;
4669 setPrintfSerialPort(cliPort
);
4670 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
4672 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics
);
4675 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4677 cliPrintLine("\r\nCLI");
4679 setArmingDisabled(ARMING_DISABLED_CLI
);
4684 void cliInit(const serialConfig_t
*serialConfig
)
4686 UNUSED(serialConfig
);