2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
27 #include "common/time.h"
29 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
30 // signal that we're in cli mode
33 extern uint8_t __config_start
; // configured via linker script when building binaries.
34 extern uint8_t __config_end
;
39 #include "blackbox/blackbox.h"
41 #include "build/build_config.h"
42 #include "build/debug.h"
43 #include "build/version.h"
47 #include "common/axis.h"
48 #include "common/color.h"
49 #include "common/maths.h"
50 #include "common/printf.h"
51 #include "common/typeconversion.h"
52 #include "common/utils.h"
54 #include "config/config_eeprom.h"
55 #include "config/feature.h"
57 #include "drivers/accgyro/accgyro.h"
58 #include "drivers/adc.h"
59 #include "drivers/buf_writer.h"
60 #include "drivers/bus_spi.h"
61 #include "drivers/compass/compass.h"
62 #include "drivers/display.h"
63 #include "drivers/dma.h"
64 #include "drivers/flash.h"
65 #include "drivers/io.h"
66 #include "drivers/io_impl.h"
67 #include "drivers/inverter.h"
68 #include "drivers/sdcard.h"
69 #include "drivers/sensor.h"
70 #include "drivers/serial.h"
71 #include "drivers/serial_escserial.h"
72 #include "drivers/rangefinder/rangefinder_hcsr04.h"
73 #include "drivers/sound_beeper.h"
74 #include "drivers/stack_check.h"
75 #include "drivers/system.h"
76 #include "drivers/transponder_ir.h"
77 #include "drivers/time.h"
78 #include "drivers/timer.h"
79 #include "drivers/light_led.h"
80 #include "drivers/camera_control.h"
81 #include "drivers/vtx_common.h"
83 #include "fc/config.h"
84 #include "fc/controlrate_profile.h"
85 #include "fc/fc_core.h"
86 #include "fc/rc_adjustments.h"
87 #include "fc/rc_controls.h"
88 #include "fc/runtime_config.h"
90 #include "flight/altitude.h"
91 #include "flight/failsafe.h"
92 #include "flight/imu.h"
93 #include "flight/mixer.h"
94 #include "flight/navigation.h"
95 #include "flight/pid.h"
96 #include "flight/servos.h"
98 #include "interface/cli.h"
99 #include "interface/msp.h"
100 #include "interface/msp_box.h"
101 #include "interface/msp_protocol.h"
102 #include "interface/settings.h"
104 #include "io/asyncfatfs/asyncfatfs.h"
105 #include "io/beeper.h"
106 #include "io/flashfs.h"
107 #include "io/gimbal.h"
109 #include "io/ledstrip.h"
111 #include "io/serial.h"
112 #include "io/transponder_ir.h"
113 #include "io/vtx_control.h"
117 #include "pg/beeper.h"
118 #include "pg/beeper_dev.h"
119 #include "pg/bus_i2c.h"
120 #include "pg/bus_spi.h"
122 #include "pg/pg_ids.h"
123 #include "pg/rx_pwm.h"
126 #include "rx/spektrum.h"
127 #include "rx/cc2500_frsky_common.h"
128 #include "rx/cc2500_frsky_x.h"
130 #include "scheduler/scheduler.h"
132 #include "sensors/acceleration.h"
133 #include "sensors/adcinternal.h"
134 #include "sensors/barometer.h"
135 #include "sensors/battery.h"
136 #include "sensors/boardalignment.h"
137 #include "sensors/compass.h"
138 #include "sensors/esc_sensor.h"
139 #include "sensors/gyro.h"
140 #include "sensors/sensors.h"
142 #include "telemetry/frsky_hub.h"
143 #include "telemetry/telemetry.h"
146 static serialPort_t
*cliPort
;
149 #define CLI_IN_BUFFER_SIZE 128
151 // Space required to set array parameters
152 #define CLI_IN_BUFFER_SIZE 256
154 #define CLI_OUT_BUFFER_SIZE 64
156 static bufWriter_t
*cliWriter
;
157 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + CLI_OUT_BUFFER_SIZE
];
159 static char cliBuffer
[CLI_IN_BUFFER_SIZE
];
160 static uint32_t bufferIndex
= 0;
162 static bool configIsInCopy
= false;
164 static const char* const emptyName
= "-";
165 static const char* const emptryString
= "";
167 #ifndef USE_QUAD_MIXER_ONLY
168 // sync this with mixerMode_e
169 static const char * const mixerNames
[] = {
170 "TRI", "QUADP", "QUADX", "BI",
171 "GIMBAL", "Y6", "HEX6",
172 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
173 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
174 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
175 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
179 // sync this with features_e
180 static const char * const featureNames
[] = {
181 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
182 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
183 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
184 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
185 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
186 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
189 // sync this with rxFailsafeChannelMode_e
190 static const char rxFailsafeModeCharacters
[] = "ahs";
192 static const rxFailsafeChannelMode_e rxFailsafeModesTable
[RX_FAILSAFE_TYPE_COUNT
][RX_FAILSAFE_MODE_COUNT
] = {
193 { RX_FAILSAFE_MODE_AUTO
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_INVALID
},
194 { RX_FAILSAFE_MODE_INVALID
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
}
197 #if defined(USE_SENSOR_NAMES)
198 // sync this with sensors_e
199 static const char * const sensorTypeNames
[] = {
200 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
203 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
205 static const char * const *sensorHardwareNames
[] = {
206 lookupTableGyroHardware
, lookupTableAccHardware
, lookupTableBaroHardware
, lookupTableMagHardware
, lookupTableRangefinderHardware
208 #endif // USE_SENSOR_NAMES
210 static void cliPrint(const char *str
)
213 bufWriterAppend(cliWriter
, *str
++);
215 bufWriterFlush(cliWriter
);
218 static void cliPrintLinefeed(void)
223 static void cliPrintLine(const char *str
)
230 #define cliPrintHashLine(str)
232 static void cliPrintHashLine(const char *str
)
239 static void cliPutp(void *p
, char ch
)
241 bufWriterAppend(p
, ch
);
245 DUMP_MASTER
= (1 << 0),
246 DUMP_PROFILE
= (1 << 1),
247 DUMP_RATES
= (1 << 2),
250 SHOW_DEFAULTS
= (1 << 5),
251 HIDE_UNUSED
= (1 << 6)
254 static void cliPrintfva(const char *format
, va_list va
)
256 tfp_format(cliWriter
, cliPutp
, format
, va
);
257 bufWriterFlush(cliWriter
);
260 static void cliPrintLinefva(const char *format
, va_list va
)
262 tfp_format(cliWriter
, cliPutp
, format
, va
);
263 bufWriterFlush(cliWriter
);
267 static bool cliDumpPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
269 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
271 va_start(va
, format
);
272 cliPrintLinefva(format
, va
);
280 static void cliWrite(uint8_t ch
)
282 bufWriterAppend(cliWriter
, ch
);
285 static bool cliDefaultPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
287 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
291 va_start(va
, format
);
292 cliPrintLinefva(format
, va
);
300 static void cliPrintf(const char *format
, ...)
303 va_start(va
, format
);
304 cliPrintfva(format
, va
);
309 static void cliPrintLinef(const char *format
, ...)
312 va_start(va
, format
);
313 cliPrintLinefva(format
, va
);
318 static void printValuePointer(const clivalue_t
*var
, const void *valuePointer
, bool full
)
320 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
321 for (int i
= 0; i
< var
->config
.array
.length
; i
++) {
322 switch (var
->type
& VALUE_TYPE_MASK
) {
326 cliPrintf("%d", ((uint8_t *)valuePointer
)[i
]);
331 cliPrintf("%d", ((int8_t *)valuePointer
)[i
]);
336 cliPrintf("%d", ((uint16_t *)valuePointer
)[i
]);
341 cliPrintf("%d", ((int16_t *)valuePointer
)[i
]);
345 if (i
< var
->config
.array
.length
- 1) {
352 switch (var
->type
& VALUE_TYPE_MASK
) {
354 value
= *(uint8_t *)valuePointer
;
358 value
= *(int8_t *)valuePointer
;
363 value
= *(int16_t *)valuePointer
;
367 switch (var
->type
& VALUE_MODE_MASK
) {
369 cliPrintf("%d", value
);
371 cliPrintf(" %d %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
375 cliPrint(lookupTables
[var
->config
.lookup
.tableIndex
].values
[value
]);
381 static bool valuePtrEqualsDefault(uint8_t type
, const void *ptr
, const void *ptrDefault
)
384 switch (type
& VALUE_TYPE_MASK
) {
386 result
= *(uint8_t *)ptr
== *(uint8_t *)ptrDefault
;
390 result
= *(int8_t *)ptr
== *(int8_t *)ptrDefault
;
395 result
= *(int16_t *)ptr
== *(int16_t *)ptrDefault
;
402 static uint16_t getValueOffset(const clivalue_t
*value
)
404 switch (value
->type
& VALUE_SECTION_MASK
) {
406 return value
->offset
;
408 return value
->offset
+ sizeof(pidProfile_t
) * getCurrentPidProfileIndex();
409 case PROFILE_RATE_VALUE
:
410 return value
->offset
+ sizeof(controlRateConfig_t
) * getCurrentControlRateProfileIndex();
415 void *cliGetValuePointer(const clivalue_t
*value
)
417 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
418 return CONST_CAST(void *, rec
->address
+ getValueOffset(value
));
421 const void *cliGetDefaultPointer(const clivalue_t
*value
)
423 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
424 return rec
->address
+ getValueOffset(value
);
427 static void dumpPgValue(const clivalue_t
*value
, uint8_t dumpMask
)
429 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
432 cliPrintLinef("VALUE %s ERROR", value
->name
);
433 return; // if it's not found, the pgn shouldn't be in the value table!
437 const char *format
= "set %s = ";
438 const char *defaultFormat
= "#set %s = ";
439 const int valueOffset
= getValueOffset(value
);
440 const bool equalsDefault
= valuePtrEqualsDefault(value
->type
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
442 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
443 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
444 cliPrintf(defaultFormat
, value
->name
);
445 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
448 cliPrintf(format
, value
->name
);
449 printValuePointer(value
, pg
->copy
+ valueOffset
, false);
454 static void dumpAllValues(uint16_t valueSection
, uint8_t dumpMask
)
456 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
457 const clivalue_t
*value
= &valueTable
[i
];
458 bufWriterFlush(cliWriter
);
459 if ((value
->type
& VALUE_SECTION_MASK
) == valueSection
) {
460 dumpPgValue(value
, dumpMask
);
465 static void cliPrintVar(const clivalue_t
*var
, bool full
)
467 const void *ptr
= cliGetValuePointer(var
);
469 printValuePointer(var
, ptr
, full
);
472 static void cliPrintVarRange(const clivalue_t
*var
)
474 switch (var
->type
& VALUE_MODE_MASK
) {
475 case (MODE_DIRECT
): {
476 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
479 case (MODE_LOOKUP
): {
480 const lookupTableEntry_t
*tableEntry
= &lookupTables
[var
->config
.lookup
.tableIndex
];
481 cliPrint("Allowed values:");
482 for (uint32_t i
= 0; i
< tableEntry
->valueCount
; i
++) {
485 cliPrintf(" %s", tableEntry
->values
[i
]);
491 cliPrintLinef("Array length: %d", var
->config
.array
.length
);
498 static void cliSetVar(const clivalue_t
*var
, const int16_t value
)
500 void *ptr
= cliGetValuePointer(var
);
502 switch (var
->type
& VALUE_TYPE_MASK
) {
504 *(uint8_t *)ptr
= value
;
508 *(int8_t *)ptr
= value
;
513 *(int16_t *)ptr
= value
;
518 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
519 static void cliRepeat(char ch
, uint8_t len
)
521 for (int i
= 0; i
< len
; i
++) {
522 bufWriterAppend(cliWriter
, ch
);
528 static void cliPrompt(void)
533 static void cliShowParseError(void)
535 cliPrintLine("Parse error");
538 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
540 cliPrintLinef("%s not between %d and %d", name
, min
, max
);
543 static const char *nextArg(const char *currentArg
)
545 const char *ptr
= strchr(currentArg
, ' ');
546 while (ptr
&& *ptr
== ' ') {
553 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
555 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
559 val
= CHANNEL_VALUE_TO_STEP(val
);
560 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
562 range
->startStep
= val
;
564 range
->endStep
= val
;
566 (*validArgumentCount
)++;
574 // Check if a string's length is zero
575 static bool isEmpty(const char *string
)
577 return (string
== NULL
|| *string
== '\0') ? true : false;
580 static void printRxFailsafe(uint8_t dumpMask
, const rxFailsafeChannelConfig_t
*rxFailsafeChannelConfigs
, const rxFailsafeChannelConfig_t
*defaultRxFailsafeChannelConfigs
)
582 // print out rxConfig failsafe settings
583 for (uint32_t channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
584 const rxFailsafeChannelConfig_t
*channelFailsafeConfig
= &rxFailsafeChannelConfigs
[channel
];
585 const rxFailsafeChannelConfig_t
*defaultChannelFailsafeConfig
= &defaultRxFailsafeChannelConfigs
[channel
];
586 const bool equalsDefault
= channelFailsafeConfig
->mode
== defaultChannelFailsafeConfig
->mode
587 && channelFailsafeConfig
->step
== defaultChannelFailsafeConfig
->step
;
588 const bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
590 const char *format
= "rxfail %u %c %d";
591 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
593 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
],
594 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig
->step
)
596 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
598 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
],
599 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
602 const char *format
= "rxfail %u %c";
603 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
605 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
]
607 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
609 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
]
615 static void cliRxFailsafe(char *cmdline
)
620 if (isEmpty(cmdline
)) {
621 // print out rxConfig failsafe settings
622 for (channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
623 cliRxFailsafe(itoa(channel
, buf
, 10));
626 const char *ptr
= cmdline
;
627 channel
= atoi(ptr
++);
628 if ((channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
)) {
630 rxFailsafeChannelConfig_t
*channelFailsafeConfig
= rxFailsafeChannelConfigsMutable(channel
);
632 const rxFailsafeChannelType_e type
= (channel
< NON_AUX_CHANNEL_COUNT
) ? RX_FAILSAFE_TYPE_FLIGHT
: RX_FAILSAFE_TYPE_AUX
;
633 rxFailsafeChannelMode_e mode
= channelFailsafeConfig
->mode
;
634 bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
638 const char *p
= strchr(rxFailsafeModeCharacters
, *(ptr
));
640 const uint8_t requestedMode
= p
- rxFailsafeModeCharacters
;
641 mode
= rxFailsafeModesTable
[type
][requestedMode
];
643 mode
= RX_FAILSAFE_MODE_INVALID
;
645 if (mode
== RX_FAILSAFE_MODE_INVALID
) {
650 requireValue
= mode
== RX_FAILSAFE_MODE_SET
;
658 uint16_t value
= atoi(ptr
);
659 value
= CHANNEL_VALUE_TO_RXFAIL_STEP(value
);
660 if (value
> MAX_RXFAIL_RANGE_STEP
) {
661 cliPrintLine("Value out of range");
665 channelFailsafeConfig
->step
= value
;
666 } else if (requireValue
) {
670 channelFailsafeConfig
->mode
= mode
;
673 char modeCharacter
= rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
];
675 // double use of cliPrintf below
676 // 1. acknowledge interpretation on command,
677 // 2. query current setting on single item,
680 cliPrintLinef("rxfail %u %c %d",
683 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
686 cliPrintLinef("rxfail %u %c",
692 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT
- 1);
697 static void printAux(uint8_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
)
699 const char *format
= "aux %u %u %u %u %u %u";
700 // print out aux channel settings
701 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
702 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
703 bool equalsDefault
= false;
704 if (defaultModeActivationConditions
) {
705 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
706 equalsDefault
= mac
->modeId
== macDefault
->modeId
707 && mac
->auxChannelIndex
== macDefault
->auxChannelIndex
708 && mac
->range
.startStep
== macDefault
->range
.startStep
709 && mac
->range
.endStep
== macDefault
->range
.endStep
710 && mac
->modeLogic
== macDefault
->modeLogic
;
711 const box_t
*box
= findBoxByBoxId(macDefault
->modeId
);
713 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
716 macDefault
->auxChannelIndex
,
717 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
718 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
),
719 macDefault
->modeLogic
723 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
725 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
728 mac
->auxChannelIndex
,
729 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
730 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
737 static void cliAux(char *cmdline
)
742 if (isEmpty(cmdline
)) {
743 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
);
747 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
748 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
749 uint8_t validArgumentCount
= 0;
753 const box_t
*box
= findBoxByPermanentId(val
);
755 mac
->modeId
= box
->boxId
;
756 validArgumentCount
++;
762 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
763 mac
->auxChannelIndex
= val
;
764 validArgumentCount
++;
767 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
771 if (val
== MODELOGIC_OR
|| val
== MODELOGIC_AND
) {
772 mac
->modeLogic
= val
;
773 validArgumentCount
++;
776 if (validArgumentCount
== 4) { // for backwards compatibility
777 mac
->modeLogic
= MODELOGIC_OR
;
778 } else if (validArgumentCount
!= 5) {
779 memset(mac
, 0, sizeof(modeActivationCondition_t
));
781 cliPrintLinef( "aux %u %u %u %u %u %u",
784 mac
->auxChannelIndex
,
785 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
786 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
790 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
795 static void printSerial(uint8_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
)
797 const char *format
= "serial %d %d %ld %ld %ld %ld";
798 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
799 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
802 bool equalsDefault
= false;
803 if (serialConfigDefault
) {
804 equalsDefault
= serialConfig
->portConfigs
[i
].identifier
== serialConfigDefault
->portConfigs
[i
].identifier
805 && serialConfig
->portConfigs
[i
].functionMask
== serialConfigDefault
->portConfigs
[i
].functionMask
806 && serialConfig
->portConfigs
[i
].msp_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
807 && serialConfig
->portConfigs
[i
].gps_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
808 && serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
809 && serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
;
810 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
811 serialConfigDefault
->portConfigs
[i
].identifier
,
812 serialConfigDefault
->portConfigs
[i
].functionMask
,
813 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
814 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
815 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
816 baudRates
[serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
]
819 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
820 serialConfig
->portConfigs
[i
].identifier
,
821 serialConfig
->portConfigs
[i
].functionMask
,
822 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
823 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
824 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
825 baudRates
[serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
]
830 static void cliSerial(char *cmdline
)
832 if (isEmpty(cmdline
)) {
833 printSerial(DUMP_MASTER
, serialConfig(), NULL
);
836 serialPortConfig_t portConfig
;
837 memset(&portConfig
, 0 , sizeof(portConfig
));
839 serialPortConfig_t
*currentConfig
;
841 uint8_t validArgumentCount
= 0;
843 const char *ptr
= cmdline
;
845 int val
= atoi(ptr
++);
846 currentConfig
= serialFindPortConfiguration(val
);
848 portConfig
.identifier
= val
;
849 validArgumentCount
++;
855 portConfig
.functionMask
= val
& 0xFFFF;
856 validArgumentCount
++;
859 for (int i
= 0; i
< 4; i
++) {
867 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
868 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
874 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_1000000
) {
877 portConfig
.msp_baudrateIndex
= baudRateIndex
;
880 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
883 portConfig
.gps_baudrateIndex
= baudRateIndex
;
886 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
889 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
892 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_2470000
) {
895 portConfig
.blackbox_baudrateIndex
= baudRateIndex
;
899 validArgumentCount
++;
902 if (validArgumentCount
< 6) {
907 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
910 #ifndef SKIP_SERIAL_PASSTHROUGH
911 static void cliSerialPassthrough(char *cmdline
)
913 if (isEmpty(cmdline
)) {
922 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
925 while (tok
!= NULL
) {
934 if (strstr(tok
, "rx") || strstr(tok
, "RX"))
936 if (strstr(tok
, "tx") || strstr(tok
, "TX"))
941 tok
= strtok_r(NULL
, " ", &saveptr
);
944 cliPrintf("Port %d ", id
);
945 serialPort_t
*passThroughPort
;
946 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
947 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
949 cliPrintLine("closed, specify baud.");
955 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
, NULL
,
957 SERIAL_NOT_INVERTED
);
958 if (!passThroughPort
) {
959 cliPrintLine("could not be opened.");
962 cliPrintf("opened, baud = %d.\r\n", baud
);
964 passThroughPort
= passThroughPortUsage
->serialPort
;
965 // If the user supplied a mode, override the port's mode, otherwise
966 // leave the mode unchanged. serialPassthrough() handles one-way ports.
967 cliPrintLine("already open.");
968 if (mode
&& passThroughPort
->mode
!= mode
) {
969 cliPrintf("mode changed from %d to %d.\r\n",
970 passThroughPort
->mode
, mode
);
971 serialSetMode(passThroughPort
, mode
);
973 // If this port has a rx callback associated we need to remove it now.
974 // Otherwise no data will be pushed in the serial port buffer!
975 if (passThroughPort
->rxCallback
) {
976 passThroughPort
->rxCallback
= 0;
980 cliPrintLine("Forwarding, power cycle to exit.");
982 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
986 static void printAdjustmentRange(uint8_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
)
988 const char *format
= "adjrange %u %u %u %u %u %u %u";
989 // print out adjustment ranges channel settings
990 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
991 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
992 bool equalsDefault
= false;
993 if (defaultAdjustmentRanges
) {
994 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
995 equalsDefault
= ar
->auxChannelIndex
== arDefault
->auxChannelIndex
996 && ar
->range
.startStep
== arDefault
->range
.startStep
997 && ar
->range
.endStep
== arDefault
->range
.endStep
998 && ar
->adjustmentFunction
== arDefault
->adjustmentFunction
999 && ar
->auxSwitchChannelIndex
== arDefault
->auxSwitchChannelIndex
1000 && ar
->adjustmentIndex
== arDefault
->adjustmentIndex
;
1001 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1003 arDefault
->adjustmentIndex
,
1004 arDefault
->auxChannelIndex
,
1005 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1006 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1007 arDefault
->adjustmentFunction
,
1008 arDefault
->auxSwitchChannelIndex
1011 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1013 ar
->adjustmentIndex
,
1014 ar
->auxChannelIndex
,
1015 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1016 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1017 ar
->adjustmentFunction
,
1018 ar
->auxSwitchChannelIndex
1023 static void cliAdjustmentRange(char *cmdline
)
1028 if (isEmpty(cmdline
)) {
1029 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
);
1033 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1034 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1035 uint8_t validArgumentCount
= 0;
1040 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
1041 ar
->adjustmentIndex
= val
;
1042 validArgumentCount
++;
1048 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1049 ar
->auxChannelIndex
= val
;
1050 validArgumentCount
++;
1054 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1059 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1060 ar
->adjustmentFunction
= val
;
1061 validArgumentCount
++;
1067 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1068 ar
->auxSwitchChannelIndex
= val
;
1069 validArgumentCount
++;
1073 if (validArgumentCount
!= 6) {
1074 memset(ar
, 0, sizeof(adjustmentRange_t
));
1075 cliShowParseError();
1078 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1083 #ifndef USE_QUAD_MIXER_ONLY
1084 static void printMotorMix(uint8_t dumpMask
, const motorMixer_t
*customMotorMixer
, const motorMixer_t
*defaultCustomMotorMixer
)
1086 const char *format
= "mmix %d %s %s %s %s";
1087 char buf0
[FTOA_BUFFER_LENGTH
];
1088 char buf1
[FTOA_BUFFER_LENGTH
];
1089 char buf2
[FTOA_BUFFER_LENGTH
];
1090 char buf3
[FTOA_BUFFER_LENGTH
];
1091 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1092 if (customMotorMixer
[i
].throttle
== 0.0f
)
1094 const float thr
= customMotorMixer
[i
].throttle
;
1095 const float roll
= customMotorMixer
[i
].roll
;
1096 const float pitch
= customMotorMixer
[i
].pitch
;
1097 const float yaw
= customMotorMixer
[i
].yaw
;
1098 bool equalsDefault
= false;
1099 if (defaultCustomMotorMixer
) {
1100 const float thrDefault
= defaultCustomMotorMixer
[i
].throttle
;
1101 const float rollDefault
= defaultCustomMotorMixer
[i
].roll
;
1102 const float pitchDefault
= defaultCustomMotorMixer
[i
].pitch
;
1103 const float yawDefault
= defaultCustomMotorMixer
[i
].yaw
;
1104 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1106 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1108 ftoa(thrDefault
, buf0
),
1109 ftoa(rollDefault
, buf1
),
1110 ftoa(pitchDefault
, buf2
),
1111 ftoa(yawDefault
, buf3
));
1113 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1121 #endif // USE_QUAD_MIXER_ONLY
1123 static void cliMotorMix(char *cmdline
)
1125 #ifdef USE_QUAD_MIXER_ONLY
1132 if (isEmpty(cmdline
)) {
1133 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
);
1134 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1135 // erase custom mixer
1136 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1137 customMotorMixerMutable(i
)->throttle
= 0.0f
;
1139 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1140 ptr
= nextArg(cmdline
);
1143 for (uint32_t i
= 0; ; i
++) {
1144 if (mixerNames
[i
] == NULL
) {
1145 cliPrintLine("Invalid name");
1148 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1149 mixerLoadMix(i
, customMotorMixerMutable(0));
1150 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1158 uint32_t i
= atoi(ptr
); // get motor number
1159 if (i
< MAX_SUPPORTED_MOTORS
) {
1162 customMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1167 customMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1172 customMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1177 customMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1181 cliShowParseError();
1183 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
);
1186 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
1192 static void printRxRange(uint8_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
)
1194 const char *format
= "rxrange %u %u %u";
1195 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1196 bool equalsDefault
= false;
1197 if (defaultChannelRangeConfigs
) {
1198 equalsDefault
= channelRangeConfigs
[i
].min
== defaultChannelRangeConfigs
[i
].min
1199 && channelRangeConfigs
[i
].max
== defaultChannelRangeConfigs
[i
].max
;
1200 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1202 defaultChannelRangeConfigs
[i
].min
,
1203 defaultChannelRangeConfigs
[i
].max
1206 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1208 channelRangeConfigs
[i
].min
,
1209 channelRangeConfigs
[i
].max
1214 static void cliRxRange(char *cmdline
)
1216 int i
, validArgumentCount
= 0;
1219 if (isEmpty(cmdline
)) {
1220 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
);
1221 } else if (strcasecmp(cmdline
, "reset") == 0) {
1222 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1226 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1227 int rangeMin
= PWM_PULSE_MIN
, rangeMax
= PWM_PULSE_MAX
;
1231 rangeMin
= atoi(ptr
);
1232 validArgumentCount
++;
1237 rangeMax
= atoi(ptr
);
1238 validArgumentCount
++;
1241 if (validArgumentCount
!= 2) {
1242 cliShowParseError();
1243 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1244 cliShowParseError();
1246 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1247 channelRangeConfig
->min
= rangeMin
;
1248 channelRangeConfig
->max
= rangeMax
;
1251 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT
- 1);
1256 #ifdef USE_LED_STRIP
1257 static void printLed(uint8_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
)
1259 const char *format
= "led %u %s";
1260 char ledConfigBuffer
[20];
1261 char ledConfigDefaultBuffer
[20];
1262 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1263 ledConfig_t ledConfig
= ledConfigs
[i
];
1264 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1265 bool equalsDefault
= false;
1266 if (defaultLedConfigs
) {
1267 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1268 equalsDefault
= ledConfig
== ledConfigDefault
;
1269 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1270 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1272 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1276 static void cliLed(char *cmdline
)
1281 if (isEmpty(cmdline
)) {
1282 printLed(DUMP_MASTER
, ledStripConfig()->ledConfigs
, NULL
);
1286 if (i
< LED_MAX_STRIP_LENGTH
) {
1287 ptr
= nextArg(cmdline
);
1288 if (!parseLedStripConfig(i
, ptr
)) {
1289 cliShowParseError();
1292 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH
- 1);
1297 static void printColor(uint8_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
)
1299 const char *format
= "color %u %d,%u,%u";
1300 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1301 const hsvColor_t
*color
= &colors
[i
];
1302 bool equalsDefault
= false;
1303 if (defaultColors
) {
1304 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1305 equalsDefault
= color
->h
== colorDefault
->h
1306 && color
->s
== colorDefault
->s
1307 && color
->v
== colorDefault
->v
;
1308 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1310 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1314 static void cliColor(char *cmdline
)
1316 if (isEmpty(cmdline
)) {
1317 printColor(DUMP_MASTER
, ledStripConfig()->colors
, NULL
);
1319 const char *ptr
= cmdline
;
1320 const int i
= atoi(ptr
);
1321 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1322 ptr
= nextArg(cmdline
);
1323 if (!parseColor(i
, ptr
)) {
1324 cliShowParseError();
1327 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1332 static void printModeColor(uint8_t dumpMask
, const ledStripConfig_t
*ledStripConfig
, const ledStripConfig_t
*defaultLedStripConfig
)
1334 const char *format
= "mode_color %u %u %u";
1335 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1336 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1337 int colorIndex
= ledStripConfig
->modeColors
[i
].color
[j
];
1338 bool equalsDefault
= false;
1339 if (defaultLedStripConfig
) {
1340 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1341 equalsDefault
= colorIndex
== colorIndexDefault
;
1342 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1344 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1348 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1349 const int colorIndex
= ledStripConfig
->specialColors
.color
[j
];
1350 bool equalsDefault
= false;
1351 if (defaultLedStripConfig
) {
1352 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1353 equalsDefault
= colorIndex
== colorIndexDefault
;
1354 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1356 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1359 const int ledStripAuxChannel
= ledStripConfig
->ledstrip_aux_channel
;
1360 bool equalsDefault
= false;
1361 if (defaultLedStripConfig
) {
1362 const int ledStripAuxChannelDefault
= defaultLedStripConfig
->ledstrip_aux_channel
;
1363 equalsDefault
= ledStripAuxChannel
== ledStripAuxChannelDefault
;
1364 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannelDefault
);
1366 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannel
);
1369 static void cliModeColor(char *cmdline
)
1371 if (isEmpty(cmdline
)) {
1372 printModeColor(DUMP_MASTER
, ledStripConfig(), NULL
);
1374 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1375 int args
[ARGS_COUNT
];
1378 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1379 while (ptr
&& argNo
< ARGS_COUNT
) {
1380 args
[argNo
++] = atoi(ptr
);
1381 ptr
= strtok_r(NULL
, " ", &saveptr
);
1384 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1385 cliShowParseError();
1389 int modeIdx
= args
[MODE
];
1390 int funIdx
= args
[FUNCTION
];
1391 int color
= args
[COLOR
];
1392 if (!setModeColor(modeIdx
, funIdx
, color
)) {
1393 cliShowParseError();
1396 // values are validated
1397 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1403 static void printServo(uint8_t dumpMask
, const servoParam_t
*servoParams
, const servoParam_t
*defaultServoParams
)
1405 // print out servo settings
1406 const char *format
= "servo %u %d %d %d %d %d";
1407 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1408 const servoParam_t
*servoConf
= &servoParams
[i
];
1409 bool equalsDefault
= false;
1410 if (defaultServoParams
) {
1411 const servoParam_t
*defaultServoConf
= &defaultServoParams
[i
];
1412 equalsDefault
= servoConf
->min
== defaultServoConf
->min
1413 && servoConf
->max
== defaultServoConf
->max
1414 && servoConf
->middle
== defaultServoConf
->middle
1415 && servoConf
->rate
== defaultServoConf
->rate
1416 && servoConf
->forwardFromChannel
== defaultServoConf
->forwardFromChannel
;
1417 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1419 defaultServoConf
->min
,
1420 defaultServoConf
->max
,
1421 defaultServoConf
->middle
,
1422 defaultServoConf
->rate
,
1423 defaultServoConf
->forwardFromChannel
1426 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1432 servoConf
->forwardFromChannel
1435 // print servo directions
1436 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1437 const char *format
= "smix reverse %d %d r";
1438 const servoParam_t
*servoConf
= &servoParams
[i
];
1439 const servoParam_t
*servoConfDefault
= &defaultServoParams
[i
];
1440 if (defaultServoParams
) {
1441 bool equalsDefault
= servoConf
->reversedSources
== servoConfDefault
->reversedSources
;
1442 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1443 equalsDefault
= ~(servoConf
->reversedSources
^ servoConfDefault
->reversedSources
) & (1 << channel
);
1444 if (servoConfDefault
->reversedSources
& (1 << channel
)) {
1445 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1447 if (servoConf
->reversedSources
& (1 << channel
)) {
1448 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1452 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1453 if (servoConf
->reversedSources
& (1 << channel
)) {
1454 cliDumpPrintLinef(dumpMask
, true, format
, i
, channel
);
1461 static void cliServo(char *cmdline
)
1463 enum { SERVO_ARGUMENT_COUNT
= 6 };
1464 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1466 servoParam_t
*servo
;
1471 if (isEmpty(cmdline
)) {
1472 printServo(DUMP_MASTER
, servoParams(0), NULL
);
1474 int validArgumentCount
= 0;
1478 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1480 // If command line doesn't fit the format, don't modify the config
1482 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1483 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1484 cliShowParseError();
1488 arguments
[validArgumentCount
++] = atoi(ptr
);
1492 } while (*ptr
>= '0' && *ptr
<= '9');
1493 } else if (*ptr
== ' ') {
1496 cliShowParseError();
1501 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
, FORWARD
};
1503 i
= arguments
[INDEX
];
1505 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1506 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1507 cliShowParseError();
1511 servo
= servoParamsMutable(i
);
1514 arguments
[MIN
] < PWM_PULSE_MIN
|| arguments
[MIN
] > PWM_PULSE_MAX
||
1515 arguments
[MAX
] < PWM_PULSE_MIN
|| arguments
[MAX
] > PWM_PULSE_MAX
||
1516 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
1517 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
1518 arguments
[RATE
] < -100 || arguments
[RATE
] > 100 ||
1519 arguments
[FORWARD
] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1521 cliShowParseError();
1525 servo
->min
= arguments
[MIN
];
1526 servo
->max
= arguments
[MAX
];
1527 servo
->middle
= arguments
[MIDDLE
];
1528 servo
->rate
= arguments
[RATE
];
1529 servo
->forwardFromChannel
= arguments
[FORWARD
];
1535 static void printServoMix(uint8_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
)
1537 const char *format
= "smix %d %d %d %d %d %d %d %d";
1538 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1539 const servoMixer_t customServoMixer
= customServoMixers
[i
];
1540 if (customServoMixer
.rate
== 0) {
1544 bool equalsDefault
= false;
1545 if (defaultCustomServoMixers
) {
1546 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
1547 equalsDefault
= customServoMixer
.targetChannel
== customServoMixerDefault
.targetChannel
1548 && customServoMixer
.inputSource
== customServoMixerDefault
.inputSource
1549 && customServoMixer
.rate
== customServoMixerDefault
.rate
1550 && customServoMixer
.speed
== customServoMixerDefault
.speed
1551 && customServoMixer
.min
== customServoMixerDefault
.min
1552 && customServoMixer
.max
== customServoMixerDefault
.max
1553 && customServoMixer
.box
== customServoMixerDefault
.box
;
1555 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1557 customServoMixerDefault
.targetChannel
,
1558 customServoMixerDefault
.inputSource
,
1559 customServoMixerDefault
.rate
,
1560 customServoMixerDefault
.speed
,
1561 customServoMixerDefault
.min
,
1562 customServoMixerDefault
.max
,
1563 customServoMixerDefault
.box
1566 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1568 customServoMixer
.targetChannel
,
1569 customServoMixer
.inputSource
,
1570 customServoMixer
.rate
,
1571 customServoMixer
.speed
,
1572 customServoMixer
.min
,
1573 customServoMixer
.max
,
1574 customServoMixer
.box
1581 static void cliServoMix(char *cmdline
)
1583 int args
[8], check
= 0;
1584 int len
= strlen(cmdline
);
1587 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
);
1588 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1589 // erase custom mixer
1590 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1591 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1592 servoParamsMutable(i
)->reversedSources
= 0;
1594 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1595 const char *ptr
= nextArg(cmdline
);
1598 for (uint32_t i
= 0; ; i
++) {
1599 if (mixerNames
[i
] == NULL
) {
1600 cliPrintLine("Invalid name");
1603 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1604 servoMixerLoadMix(i
);
1605 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1611 } else if (strncasecmp(cmdline
, "reverse", 7) == 0) {
1612 enum {SERVO
= 0, INPUT
, REVERSE
, ARGS_COUNT
};
1613 char *ptr
= strchr(cmdline
, ' ');
1618 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
1619 cliPrintf("\ti%d", inputSource
);
1622 for (uint32_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
1623 cliPrintf("%d", servoIndex
);
1624 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
1625 cliPrintf("\t%s ", (servoParams(servoIndex
)->reversedSources
& (1 << inputSource
)) ? "r" : "n");
1632 ptr
= strtok_r(ptr
, " ", &saveptr
);
1633 while (ptr
!= NULL
&& check
< ARGS_COUNT
- 1) {
1634 args
[check
++] = atoi(ptr
);
1635 ptr
= strtok_r(NULL
, " ", &saveptr
);
1638 if (ptr
== NULL
|| check
!= ARGS_COUNT
- 1) {
1639 cliShowParseError();
1643 if (args
[SERVO
] >= 0 && args
[SERVO
] < MAX_SUPPORTED_SERVOS
1644 && args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
1645 && (*ptr
== 'r' || *ptr
== 'n')) {
1647 servoParamsMutable(args
[SERVO
])->reversedSources
|= 1 << args
[INPUT
];
1649 servoParamsMutable(args
[SERVO
])->reversedSources
&= ~(1 << args
[INPUT
]);
1651 cliShowParseError();
1653 cliServoMix("reverse");
1655 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, MIN
, MAX
, BOX
, ARGS_COUNT
};
1657 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1658 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1659 args
[check
++] = atoi(ptr
);
1660 ptr
= strtok_r(NULL
, " ", &saveptr
);
1663 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
1664 cliShowParseError();
1668 int32_t i
= args
[RULE
];
1669 if (i
>= 0 && i
< MAX_SERVO_RULES
&&
1670 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
1671 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
1672 args
[RATE
] >= -100 && args
[RATE
] <= 100 &&
1673 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
1674 args
[MIN
] >= 0 && args
[MIN
] <= 100 &&
1675 args
[MAX
] >= 0 && args
[MAX
] <= 100 && args
[MIN
] < args
[MAX
] &&
1676 args
[BOX
] >= 0 && args
[BOX
] <= MAX_SERVO_BOXES
) {
1677 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
1678 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
1679 customServoMixersMutable(i
)->rate
= args
[RATE
];
1680 customServoMixersMutable(i
)->speed
= args
[SPEED
];
1681 customServoMixersMutable(i
)->min
= args
[MIN
];
1682 customServoMixersMutable(i
)->max
= args
[MAX
];
1683 customServoMixersMutable(i
)->box
= args
[BOX
];
1686 cliShowParseError();
1694 static void cliWriteBytes(const uint8_t *buffer
, int count
)
1703 static void cliSdInfo(char *cmdline
)
1707 cliPrint("SD card: ");
1709 if (!sdcard_isInserted()) {
1710 cliPrintLine("None inserted");
1714 if (!sdcard_isInitialized()) {
1715 cliPrintLine("Startup failed");
1719 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
1721 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1722 metadata
->manufacturerID
,
1723 metadata
->numBlocks
/ 2, /* One block is half a kB */
1724 metadata
->productionMonth
,
1725 metadata
->productionYear
,
1726 metadata
->productRevisionMajor
,
1727 metadata
->productRevisionMinor
1730 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
1732 cliPrint("'\r\n" "Filesystem: ");
1734 switch (afatfs_getFilesystemState()) {
1735 case AFATFS_FILESYSTEM_STATE_READY
:
1738 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
1739 cliPrint("Initializing");
1741 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
1742 case AFATFS_FILESYSTEM_STATE_FATAL
:
1745 switch (afatfs_getLastError()) {
1746 case AFATFS_ERROR_BAD_MBR
:
1747 cliPrint(" - no FAT MBR partitions");
1749 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
1750 cliPrint(" - bad FAT header");
1752 case AFATFS_ERROR_GENERIC
:
1753 case AFATFS_ERROR_NONE
:
1754 ; // Nothing more detailed to print
1766 static void cliFlashInfo(char *cmdline
)
1768 const flashGeometry_t
*layout
= flashfsGetGeometry();
1772 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
1773 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
, flashfsGetOffset());
1777 static void cliFlashErase(char *cmdline
)
1783 cliPrintLine("Erasing, please wait ... ");
1785 cliPrintLine("Erasing,");
1788 bufWriterFlush(cliWriter
);
1789 flashfsEraseCompletely();
1791 while (!flashfsIsReady()) {
1799 bufWriterFlush(cliWriter
);
1803 beeper(BEEPER_BLACKBOX_ERASE
);
1805 cliPrintLine("Done.");
1808 #ifdef USE_FLASH_TOOLS
1810 static void cliFlashWrite(char *cmdline
)
1812 const uint32_t address
= atoi(cmdline
);
1813 const char *text
= strchr(cmdline
, ' ');
1816 cliShowParseError();
1818 flashfsSeekAbs(address
);
1819 flashfsWrite((uint8_t*)text
, strlen(text
), true);
1822 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
1826 static void cliFlashRead(char *cmdline
)
1828 uint32_t address
= atoi(cmdline
);
1830 const char *nextArg
= strchr(cmdline
, ' ');
1833 cliShowParseError();
1835 uint32_t length
= atoi(nextArg
);
1837 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
1840 while (length
> 0) {
1841 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
1843 for (int i
= 0; i
< bytesRead
; i
++) {
1844 cliWrite(buffer
[i
]);
1847 length
-= bytesRead
;
1848 address
+= bytesRead
;
1850 if (bytesRead
== 0) {
1851 //Assume we reached the end of the volume or something fatal happened
1862 #ifdef USE_VTX_CONTROL
1863 static void printVtx(uint8_t dumpMask
, const vtxConfig_t
*vtxConfig
, const vtxConfig_t
*vtxConfigDefault
)
1865 // print out vtx channel settings
1866 const char *format
= "vtx %u %u %u %u %u %u";
1867 bool equalsDefault
= false;
1868 for (uint32_t i
= 0; i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
; i
++) {
1869 const vtxChannelActivationCondition_t
*cac
= &vtxConfig
->vtxChannelActivationConditions
[i
];
1870 if (vtxConfigDefault
) {
1871 const vtxChannelActivationCondition_t
*cacDefault
= &vtxConfigDefault
->vtxChannelActivationConditions
[i
];
1872 equalsDefault
= cac
->auxChannelIndex
== cacDefault
->auxChannelIndex
1873 && cac
->band
== cacDefault
->band
1874 && cac
->channel
== cacDefault
->channel
1875 && cac
->range
.startStep
== cacDefault
->range
.startStep
1876 && cac
->range
.endStep
== cacDefault
->range
.endStep
;
1877 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1879 cacDefault
->auxChannelIndex
,
1881 cacDefault
->channel
,
1882 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.startStep
),
1883 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.endStep
)
1886 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1888 cac
->auxChannelIndex
,
1891 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
1892 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
1897 static void cliVtx(char *cmdline
)
1902 if (isEmpty(cmdline
)) {
1903 printVtx(DUMP_MASTER
, vtxConfig(), NULL
);
1907 if (i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
) {
1908 vtxChannelActivationCondition_t
*cac
= &vtxConfigMutable()->vtxChannelActivationConditions
[i
];
1909 uint8_t validArgumentCount
= 0;
1913 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1914 cac
->auxChannelIndex
= val
;
1915 validArgumentCount
++;
1921 // FIXME Use VTX API to get min/max
1922 if (val
>= VTX_SETTINGS_MIN_BAND
&& val
<= VTX_SETTINGS_MAX_BAND
) {
1924 validArgumentCount
++;
1930 // FIXME Use VTX API to get min/max
1931 if (val
>= VTX_SETTINGS_MIN_CHANNEL
&& val
<= VTX_SETTINGS_MAX_CHANNEL
) {
1933 validArgumentCount
++;
1936 ptr
= processChannelRangeArgs(ptr
, &cac
->range
, &validArgumentCount
);
1938 if (validArgumentCount
!= 5) {
1939 memset(cac
, 0, sizeof(vtxChannelActivationCondition_t
));
1942 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
- 1);
1947 #endif // VTX_CONTROL
1949 static void printName(uint8_t dumpMask
, const pilotConfig_t
*pilotConfig
)
1951 const bool equalsDefault
= strlen(pilotConfig
->name
) == 0;
1952 cliDumpPrintLinef(dumpMask
, equalsDefault
, "name %s", equalsDefault
? emptyName
: pilotConfig
->name
);
1955 static void cliName(char *cmdline
)
1957 const unsigned int len
= strlen(cmdline
);
1959 memset(pilotConfigMutable()->name
, 0, ARRAYLEN(pilotConfig()->name
));
1960 if (strncmp(cmdline
, emptyName
, len
)) {
1961 strncpy(pilotConfigMutable()->name
, cmdline
, MIN(len
, MAX_NAME_LENGTH
));
1964 printName(DUMP_MASTER
, pilotConfig());
1967 static void printFeature(uint8_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
)
1969 const uint32_t mask
= featureConfig
->enabledFeatures
;
1970 const uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
1971 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // disabled features first
1972 if (strcmp(featureNames
[i
], emptryString
) != 0) { //Skip unused
1973 const char *format
= "feature -%s";
1974 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
1975 cliDumpPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
1978 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // enabled features
1979 if (strcmp(featureNames
[i
], emptryString
) != 0) { //Skip unused
1980 const char *format
= "feature %s";
1981 if (defaultMask
& (1 << i
)) {
1982 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
1984 if (mask
& (1 << i
)) {
1985 cliDumpPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
1991 static void cliFeature(char *cmdline
)
1993 uint32_t len
= strlen(cmdline
);
1994 uint32_t mask
= featureMask();
1997 cliPrint("Enabled: ");
1998 for (uint32_t i
= 0; ; i
++) {
1999 if (featureNames
[i
] == NULL
)
2001 if (mask
& (1 << i
))
2002 cliPrintf("%s ", featureNames
[i
]);
2005 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2006 cliPrint("Available:");
2007 for (uint32_t i
= 0; ; i
++) {
2008 if (featureNames
[i
] == NULL
)
2010 if (strcmp(featureNames
[i
], emptryString
) != 0) //Skip unused
2011 cliPrintf(" %s", featureNames
[i
]);
2016 bool remove
= false;
2017 if (cmdline
[0] == '-') {
2020 cmdline
++; // skip over -
2024 for (uint32_t i
= 0; ; i
++) {
2025 if (featureNames
[i
] == NULL
) {
2026 cliPrintLine("Invalid name");
2030 if (strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
2034 if (mask
& FEATURE_GPS
) {
2035 cliPrintLine("unavailable");
2039 #ifndef USE_RANGEFINDER
2040 if (mask
& FEATURE_RANGEFINDER
) {
2041 cliPrintLine("unavailable");
2047 cliPrint("Disabled");
2050 cliPrint("Enabled");
2052 cliPrintLinef(" %s", featureNames
[i
]);
2060 static void printBeeper(uint8_t dumpMask
, const beeperConfig_t
*beeperConfig
, const beeperConfig_t
*beeperConfigDefault
)
2062 const uint8_t beeperCount
= beeperTableEntryCount();
2063 const uint32_t mask
= beeperConfig
->beeper_off_flags
;
2064 const uint32_t defaultMask
= beeperConfigDefault
->beeper_off_flags
;
2065 for (int32_t i
= 0; i
< beeperCount
- 2; i
++) {
2066 const char *formatOff
= "beeper -%s";
2067 const char *formatOn
= "beeper %s";
2068 const uint32_t beeperModeMask
= beeperModeMaskForTableIndex(i
);
2069 cliDefaultPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & beeperModeMask
, mask
& beeperModeMask
? formatOn
: formatOff
, beeperNameForTableIndex(i
));
2070 cliDumpPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & beeperModeMask
, mask
& beeperModeMask
? formatOff
: formatOn
, beeperNameForTableIndex(i
));
2074 static void cliBeeper(char *cmdline
)
2076 uint32_t len
= strlen(cmdline
);
2077 uint8_t beeperCount
= beeperTableEntryCount();
2078 uint32_t mask
= getBeeperOffMask();
2081 cliPrintf("Disabled:");
2082 for (int32_t i
= 0; ; i
++) {
2083 if (i
== beeperCount
- 2) {
2089 if (mask
& beeperModeMaskForTableIndex(i
))
2090 cliPrintf(" %s", beeperNameForTableIndex(i
));
2093 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2094 cliPrint("Available:");
2095 for (uint32_t i
= 0; i
< beeperCount
; i
++)
2096 cliPrintf(" %s", beeperNameForTableIndex(i
));
2100 bool remove
= false;
2101 if (cmdline
[0] == '-') {
2102 remove
= true; // this is for beeper OFF condition
2107 for (uint32_t i
= 0; ; i
++) {
2108 if (i
== beeperCount
) {
2109 cliPrintLine("Invalid name");
2112 if (strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0) {
2113 if (remove
) { // beeper off
2114 if (i
== BEEPER_ALL
-1)
2115 beeperOffSetAll(beeperCount
-2);
2117 if (i
== BEEPER_PREFERENCE
-1)
2118 setBeeperOffMask(getPreferredBeeperOffMask());
2120 beeperOffSet(beeperModeMaskForTableIndex(i
));
2122 cliPrint("Disabled");
2125 if (i
== BEEPER_ALL
-1)
2126 beeperOffClearAll();
2128 if (i
== BEEPER_PREFERENCE
-1)
2129 setPreferredBeeperOffMask(getBeeperOffMask());
2131 beeperOffClear(beeperModeMaskForTableIndex(i
));
2133 cliPrint("Enabled");
2135 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
2143 void cliFrSkyBind(char *cmdline
){
2145 switch (rxConfig()->rx_spi_protocol
) {
2146 #ifdef USE_RX_FRSKY_SPI
2147 case RX_SPI_FRSKY_D
:
2148 case RX_SPI_FRSKY_X
:
2151 cliPrint("Binding...");
2156 cliPrint("Not supported.");
2162 static void printMap(uint8_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
)
2164 bool equalsDefault
= true;
2166 char bufDefault
[16];
2168 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2169 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2170 if (defaultRxConfig
) {
2171 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2172 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
2177 const char *formatMap
= "map %s";
2178 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
2179 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
2183 static void cliMap(char *cmdline
)
2186 char buf
[RX_MAPPABLE_CHANNEL_COUNT
+ 1];
2188 uint32_t len
= strlen(cmdline
);
2189 if (len
== RX_MAPPABLE_CHANNEL_COUNT
) {
2191 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2192 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
2196 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2197 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
2199 if (strchr(rcChannelLetters
, buf
[i
]) && !strchr(buf
+ i
+ 1, buf
[i
]))
2202 cliShowParseError();
2205 parseRcChannels(buf
, rxConfigMutable());
2206 } else if (len
> 0) {
2207 cliShowParseError();
2211 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2212 buf
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
2216 cliPrintLinef("map %s", buf
);
2219 static char *checkCommand(char *cmdLine
, const char *command
)
2221 if (!strncasecmp(cmdLine
, command
, strlen(command
)) // command names match
2222 && (isspace((unsigned)cmdLine
[strlen(command
)]) || cmdLine
[strlen(command
)] == 0)) {
2223 return cmdLine
+ strlen(command
) + 1;
2229 static void cliRebootEx(bool bootLoader
)
2231 cliPrint("\r\nRebooting");
2232 bufWriterFlush(cliWriter
);
2233 waitForSerialPortToFinishTransmitting(cliPort
);
2236 systemResetToBootloader();
2242 static void cliReboot(void)
2247 static void cliBootloader(char *cmdLine
)
2251 cliPrintHashLine("restarting in bootloader mode");
2255 static void cliExit(char *cmdline
)
2259 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2260 bufWriterFlush(cliWriter
);
2265 // incase a motor was left running during motortest, clear it here
2266 mixerResetDisarmedMotors();
2273 static void cliGpsPassthrough(char *cmdline
)
2277 gpsEnablePassthrough(cliPort
);
2281 static int parseOutputIndex(char *pch
, bool allowAllEscs
) {
2282 int outputIndex
= atoi(pch
);
2283 if ((outputIndex
>= 0) && (outputIndex
< getMotorCount())) {
2284 tfp_printf("Using output %d.\r\n", outputIndex
);
2285 } else if (allowAllEscs
&& outputIndex
== ALL_MOTORS
) {
2286 tfp_printf("Using all outputs.\r\n");
2288 tfp_printf("Invalid output number. Range: 0 %d.\r\n", getMotorCount() - 1);
2298 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
2299 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
2300 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
2308 #define ESC_INFO_VERSION_POSITION 12
2310 void printEscInfo(const uint8_t *escInfoBuffer
, uint8_t bytesRead
)
2312 bool escInfoReceived
= false;
2313 if (bytesRead
> ESC_INFO_VERSION_POSITION
) {
2314 uint8_t escInfoVersion
;
2315 uint8_t frameLength
;
2316 if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 254) {
2317 escInfoVersion
= ESC_INFO_BLHELI32
;
2318 frameLength
= ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
;
2319 } else if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 255) {
2320 escInfoVersion
= ESC_INFO_KISS_V2
;
2321 frameLength
= ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE
;
2323 escInfoVersion
= ESC_INFO_KISS_V1
;
2324 frameLength
= ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE
;
2327 if (bytesRead
== frameLength
) {
2328 escInfoReceived
= true;
2330 if (calculateCrc8(escInfoBuffer
, frameLength
- 1) == escInfoBuffer
[frameLength
- 1]) {
2331 uint8_t firmwareVersion
= 0;
2332 uint8_t firmwareSubVersion
= 0;
2333 uint8_t escType
= 0;
2334 switch (escInfoVersion
) {
2335 case ESC_INFO_KISS_V1
:
2336 firmwareVersion
= escInfoBuffer
[12];
2337 firmwareSubVersion
= (escInfoBuffer
[13] & 0x1f) + 97;
2338 escType
= (escInfoBuffer
[13] & 0xe0) >> 5;
2341 case ESC_INFO_KISS_V2
:
2342 firmwareVersion
= escInfoBuffer
[13];
2343 firmwareSubVersion
= escInfoBuffer
[14];
2344 escType
= escInfoBuffer
[15];
2347 case ESC_INFO_BLHELI32
:
2348 firmwareVersion
= escInfoBuffer
[13];
2349 firmwareSubVersion
= escInfoBuffer
[14];
2350 escType
= escInfoBuffer
[15];
2355 cliPrint("ESC Type: ");
2356 switch (escInfoVersion
) {
2357 case ESC_INFO_KISS_V1
:
2358 case ESC_INFO_KISS_V2
:
2361 cliPrintLine("KISS8A");
2365 cliPrintLine("KISS16A");
2369 cliPrintLine("KISS24A");
2373 cliPrintLine("KISS Ultralite");
2377 cliPrintLine("unknown");
2383 case ESC_INFO_BLHELI32
:
2385 char *escType
= (char *)(escInfoBuffer
+ 31);
2387 cliPrintLine(escType
);
2393 cliPrint("MCU Serial No: 0x");
2394 for (int i
= 0; i
< 12; i
++) {
2395 if (i
&& (i
% 3 == 0)) {
2398 cliPrintf("%02x", escInfoBuffer
[i
]);
2402 switch (escInfoVersion
) {
2403 case ESC_INFO_KISS_V1
:
2404 case ESC_INFO_KISS_V2
:
2405 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion
/ 100, firmwareVersion
% 100, (char)firmwareSubVersion
);
2408 case ESC_INFO_BLHELI32
:
2409 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion
, firmwareSubVersion
);
2413 if (escInfoVersion
== ESC_INFO_KISS_V2
|| escInfoVersion
== ESC_INFO_BLHELI32
) {
2414 cliPrintLinef("Rotation Direction: %s", escInfoBuffer
[16] ? "reversed" : "normal");
2415 cliPrintLinef("3D: %s", escInfoBuffer
[17] ? "on" : "off");
2416 if (escInfoVersion
== ESC_INFO_BLHELI32
) {
2417 uint8_t setting
= escInfoBuffer
[18];
2418 cliPrint("Low voltage Limit: ");
2421 cliPrintLine("off");
2425 cliPrintLine("unsupported");
2429 cliPrintLinef("%d.%01d", setting
/ 10, setting
% 10);
2434 setting
= escInfoBuffer
[19];
2435 cliPrint("Current Limit: ");
2438 cliPrintLine("off");
2442 cliPrintLine("unsupported");
2446 cliPrintLinef("%d", setting
);
2451 for (int i
= 0; i
< 4; i
++) {
2452 setting
= escInfoBuffer
[i
+ 20];
2453 cliPrintLinef("LED %d: %s", i
, setting
? (setting
== 255) ? "unsupported" : "on" : "off");
2458 cliPrintLine("Checksum Error.");
2463 if (!escInfoReceived
) {
2464 cliPrintLine("No Info.");
2468 static void executeEscInfoCommand(uint8_t escIndex
)
2470 cliPrintLinef("Info for ESC %d:", escIndex
);
2472 uint8_t escInfoBuffer
[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
];
2474 startEscDataRead(escInfoBuffer
, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
);
2476 pwmWriteDshotCommand(escIndex
, getMotorCount(), DSHOT_CMD_ESC_INFO
);
2480 printEscInfo(escInfoBuffer
, getNumberEscBytesRead());
2483 static void cliDshotProg(char *cmdline
)
2485 if (isEmpty(cmdline
) || motorConfig()->dev
.motorPwmProtocol
< PWM_TYPE_DSHOT150
) {
2486 cliShowParseError();
2492 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2495 bool firstCommand
= true;
2496 while (pch
!= NULL
) {
2499 escIndex
= parseOutputIndex(pch
, true);
2500 if (escIndex
== -1) {
2507 int command
= atoi(pch
);
2508 if (command
>= 0 && command
< DSHOT_MIN_THROTTLE
) {
2512 if (command
== DSHOT_CMD_ESC_INFO
) {
2513 delay(5); // Wait for potential ESC telemetry transmission to finish
2518 firstCommand
= false;
2521 if (command
!= DSHOT_CMD_ESC_INFO
) {
2522 pwmWriteDshotCommand(escIndex
, getMotorCount(), command
);
2524 if (escIndex
!= ALL_MOTORS
) {
2525 executeEscInfoCommand(escIndex
);
2527 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
2528 executeEscInfoCommand(i
);
2533 cliPrintLinef("Command Sent: %d", command
);
2536 delay(20); // wait for sound output to finish
2539 cliPrintLinef("Invalid command. Range: 1 - %d.", DSHOT_MIN_THROTTLE
- 1);
2547 pch
= strtok_r(NULL
, " ", &saveptr
);
2554 #ifdef USE_ESCSERIAL
2555 static void cliEscPassthrough(char *cmdline
)
2557 if (isEmpty(cmdline
)) {
2558 cliShowParseError();
2564 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2568 while (pch
!= NULL
) {
2571 if (strncasecmp(pch
, "sk", strlen(pch
)) == 0) {
2572 mode
= PROTOCOL_SIMONK
;
2573 } else if (strncasecmp(pch
, "bl", strlen(pch
)) == 0) {
2574 mode
= PROTOCOL_BLHELI
;
2575 } else if (strncasecmp(pch
, "ki", strlen(pch
)) == 0) {
2576 mode
= PROTOCOL_KISS
;
2577 } else if (strncasecmp(pch
, "cc", strlen(pch
)) == 0) {
2578 mode
= PROTOCOL_KISSALL
;
2580 cliShowParseError();
2586 escIndex
= parseOutputIndex(pch
, mode
== PROTOCOL_KISS
);
2587 if (escIndex
== -1) {
2593 cliShowParseError();
2601 pch
= strtok_r(NULL
, " ", &saveptr
);
2604 escEnablePassthrough(cliPort
, escIndex
, mode
);
2608 #ifndef USE_QUAD_MIXER_ONLY
2609 static void cliMixer(char *cmdline
)
2613 len
= strlen(cmdline
);
2616 cliPrintLinef("Mixer: %s", mixerNames
[mixerConfig()->mixerMode
- 1]);
2618 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2619 cliPrint("Available:");
2620 for (uint32_t i
= 0; ; i
++) {
2621 if (mixerNames
[i
] == NULL
)
2623 cliPrintf(" %s", mixerNames
[i
]);
2629 for (uint32_t i
= 0; ; i
++) {
2630 if (mixerNames
[i
] == NULL
) {
2631 cliPrintLine("Invalid name");
2634 if (strncasecmp(cmdline
, mixerNames
[i
], len
) == 0) {
2635 mixerConfigMutable()->mixerMode
= i
+ 1;
2644 static void cliMotor(char *cmdline
)
2646 if (isEmpty(cmdline
)) {
2647 cliShowParseError();
2656 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2658 while (pch
!= NULL
) {
2661 motorIndex
= parseOutputIndex(pch
, true);
2662 if (motorIndex
== -1) {
2668 motorValue
= atoi(pch
);
2673 pch
= strtok_r(NULL
, " ", &saveptr
);
2677 if (motorValue
< PWM_RANGE_MIN
|| motorValue
> PWM_RANGE_MAX
) {
2678 cliShowArgumentRangeError("value", 1000, 2000);
2680 uint32_t motorOutputValue
= convertExternalToMotor(motorValue
);
2682 if (motorIndex
!= ALL_MOTORS
) {
2683 motor_disarmed
[motorIndex
] = motorOutputValue
;
2685 cliPrintLinef("motor %d: %d", motorIndex
, motorOutputValue
);
2687 for (int i
= 0; i
< getMotorCount(); i
++) {
2688 motor_disarmed
[i
] = motorOutputValue
;
2691 cliPrintLinef("all motors: %d", motorOutputValue
);
2695 cliShowParseError();
2700 static void cliPlaySound(char *cmdline
)
2704 static int lastSoundIdx
= -1;
2706 if (isEmpty(cmdline
)) {
2707 i
= lastSoundIdx
+ 1; //next sound index
2708 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
2709 while (true) { //no name for index; try next one
2710 if (++i
>= beeperTableEntryCount())
2711 i
= 0; //if end then wrap around to first entry
2712 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
2713 break; //if name OK then play sound below
2714 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
2715 cliPrintLine("Error playing sound");
2720 } else { //index value was given
2722 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
2723 cliPrintLinef("No sound for index %d", i
);
2729 cliPrintLinef("Playing sound %d: %s", i
, name
);
2730 beeper(beeperModeForTableIndex(i
));
2734 static void cliProfile(char *cmdline
)
2736 if (isEmpty(cmdline
)) {
2737 cliPrintLinef("profile %d", getCurrentPidProfileIndex());
2740 const int i
= atoi(cmdline
);
2741 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
2742 systemConfigMutable()->pidProfileIndex
= i
;
2748 static void cliRateProfile(char *cmdline
)
2750 if (isEmpty(cmdline
)) {
2751 cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
2754 const int i
= atoi(cmdline
);
2755 if (i
>= 0 && i
< CONTROL_RATE_PROFILE_COUNT
) {
2756 changeControlRateProfile(i
);
2762 static void cliDumpPidProfile(uint8_t pidProfileIndex
, uint8_t dumpMask
)
2764 if (pidProfileIndex
>= MAX_PROFILE_COUNT
) {
2768 changePidProfile(pidProfileIndex
);
2769 cliPrintHashLine("profile");
2772 dumpAllValues(PROFILE_VALUE
, dumpMask
);
2775 static void cliDumpRateProfile(uint8_t rateProfileIndex
, uint8_t dumpMask
)
2777 if (rateProfileIndex
>= CONTROL_RATE_PROFILE_COUNT
) {
2781 changeControlRateProfile(rateProfileIndex
);
2782 cliPrintHashLine("rateprofile");
2785 dumpAllValues(PROFILE_RATE_VALUE
, dumpMask
);
2788 static void cliSave(char *cmdline
)
2792 cliPrintHashLine("saving");
2797 static void cliDefaults(char *cmdline
)
2801 if (isEmpty(cmdline
)) {
2803 } else if (strncasecmp(cmdline
, "nosave", 6) == 0) {
2804 saveConfigs
= false;
2809 cliPrintHashLine("resetting to defaults");
2816 STATIC_UNIT_TESTED
void cliGet(char *cmdline
)
2818 const clivalue_t
*val
;
2819 int matchedCommands
= 0;
2821 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
2822 if (strstr(valueTable
[i
].name
, cmdline
)) {
2823 val
= &valueTable
[i
];
2824 cliPrintf("%s = ", valueTable
[i
].name
);
2825 cliPrintVar(val
, 0);
2827 cliPrintVarRange(val
);
2835 if (matchedCommands
) {
2839 cliPrintLine("Invalid name");
2842 static char *skipSpace(char *buffer
)
2844 while (*(buffer
) == ' ') {
2851 static uint8_t getWordLength(char *bufBegin
, char *bufEnd
)
2853 while (*(bufEnd
- 1) == ' ') {
2857 return bufEnd
- bufBegin
;
2860 STATIC_UNIT_TESTED
void cliSet(char *cmdline
)
2862 const uint32_t len
= strlen(cmdline
);
2865 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
2866 cliPrintLine("Current settings: ");
2868 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
2869 const clivalue_t
*val
= &valueTable
[i
];
2870 cliPrintf("%s = ", valueTable
[i
].name
);
2871 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
2874 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
2877 uint8_t variableNameLength
= getWordLength(cmdline
, eqptr
);
2879 // skip the '=' and any ' ' characters
2881 eqptr
= skipSpace(eqptr
);
2883 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
2884 const clivalue_t
*val
= &valueTable
[i
];
2886 // ensure exact match when setting to prevent setting variables with shorter names
2887 if (strncasecmp(cmdline
, val
->name
, strlen(val
->name
)) == 0 && variableNameLength
== strlen(val
->name
)) {
2889 bool valueChanged
= false;
2891 switch (val
->type
& VALUE_MODE_MASK
) {
2893 int16_t value
= atoi(eqptr
);
2895 if (value
>= val
->config
.minmax
.min
&& value
<= val
->config
.minmax
.max
) {
2896 cliSetVar(val
, value
);
2897 valueChanged
= true;
2903 const lookupTableEntry_t
*tableEntry
= &lookupTables
[val
->config
.lookup
.tableIndex
];
2904 bool matched
= false;
2905 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
2906 matched
= strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
2909 value
= tableValueIndex
;
2911 cliSetVar(val
, value
);
2912 valueChanged
= true;
2919 const uint8_t arrayLength
= val
->config
.array
.length
;
2920 char *valPtr
= eqptr
;
2922 for (int i
= 0; i
< arrayLength
; i
++) {
2924 valPtr
= skipSpace(valPtr
);
2925 // find next comma (or end of string)
2926 char *valEndPtr
= strchr(valPtr
, ',');
2928 // comma found or last item?
2929 if ((valEndPtr
!= NULL
) || (i
== arrayLength
- 1)){
2930 // process substring [valPtr, valEndPtr[
2931 // note: no need to copy substrings for atoi()
2932 // it stops at the first character that cannot be converted...
2933 switch (val
->type
& VALUE_TYPE_MASK
) {
2936 // fetch data pointer
2937 uint8_t *data
= (uint8_t *)cliGetValuePointer(val
) + i
;
2939 *data
= (uint8_t)atoi((const char*) valPtr
);
2944 // fetch data pointer
2945 int8_t *data
= (int8_t *)cliGetValuePointer(val
) + i
;
2947 *data
= (int8_t)atoi((const char*) valPtr
);
2952 // fetch data pointer
2953 uint16_t *data
= (uint16_t *)cliGetValuePointer(val
) + i
;
2955 *data
= (uint16_t)atoi((const char*) valPtr
);
2960 // fetch data pointer
2961 int16_t *data
= (int16_t *)cliGetValuePointer(val
) + i
;
2963 *data
= (int16_t)atoi((const char*) valPtr
);
2968 valueChanged
= true;
2970 // prepare to parse next item
2971 valPtr
= valEndPtr
+ 1;
2979 cliPrintf("%s set to ", val
->name
);
2980 cliPrintVar(val
, 0);
2982 cliPrintLine("Invalid value");
2983 cliPrintVarRange(val
);
2989 cliPrintLine("Invalid name");
2991 // no equals, check for matching variables.
2996 static void cliStatus(char *cmdline
)
3000 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3003 char buf
[FORMATTED_DATE_TIME_BUFSIZE
];
3005 if (rtcGetDateTime(&dt
)) {
3006 dateTimeFormatLocal(buf
, &dt
);
3007 cliPrintLinef("Current Time: %s", buf
);
3011 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
3013 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock
/ 1000000));
3015 #ifdef USE_ADC_INTERNAL
3016 uint16_t vrefintMv
= getVrefMv();
3017 uint16_t coretemp
= getCoreTemperatureCelsius();
3018 cliPrintf(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv
/ 1000, (vrefintMv
% 1000) / 10, coretemp
);
3021 #if defined(USE_SENSOR_NAMES)
3022 const uint32_t detectedSensorsMask
= sensorsMask();
3023 for (uint32_t i
= 0; ; i
++) {
3024 if (sensorTypeNames
[i
] == NULL
) {
3027 const uint32_t mask
= (1 << i
);
3028 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
3029 const uint8_t sensorHardwareIndex
= detectedSensors
[i
];
3030 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
3031 cliPrintf(", %s=%s", sensorTypeNames
[i
], sensorHardware
);
3032 if (mask
== SENSOR_ACC
&& acc
.dev
.revisionCode
) {
3033 cliPrintf(".%c", acc
.dev
.revisionCode
);
3037 #endif /* USE_SENSOR_NAMES */
3045 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
3047 const uint16_t i2cErrorCounter
= 0;
3051 cliPrintf("Stack used: %d, ", stackUsedSize());
3053 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
3054 #ifdef EEPROM_IN_RAM
3055 #define CONFIG_SIZE EEPROM_SIZE
3057 #define CONFIG_SIZE (&__config_end - &__config_start)
3059 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter
, getEEPROMConfigSize(), CONFIG_SIZE
);
3061 const int gyroRate
= getTaskDeltaTime(TASK_GYROPID
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_GYROPID
)));
3062 const int rxRate
= getTaskDeltaTime(TASK_RX
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_RX
)));
3063 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
3064 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
3065 constrain(averageSystemLoadPercent
, 0, 100), getTaskDeltaTime(TASK_GYROPID
), gyroRate
, rxRate
, systemRate
);
3066 cliPrint("Arming disable flags:");
3067 armingDisableFlags_e flags
= getArmingDisableFlags();
3069 const int bitpos
= ffs(flags
) - 1;
3070 flags
&= ~(1 << bitpos
);
3071 cliPrintf(" %s", armingDisableFlagNames
[bitpos
]);
3076 #ifndef SKIP_TASK_STATISTICS
3077 static void cliTasks(char *cmdline
)
3081 int averageLoadSum
= 0;
3084 if (systemConfig()->task_statistics
) {
3085 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
3087 cliPrintLine("Task list");
3090 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
3091 cfTaskInfo_t taskInfo
;
3092 getTaskInfo(taskId
, &taskInfo
);
3093 if (taskInfo
.isEnabled
) {
3095 int subTaskFrequency
= 0;
3096 if (taskId
== TASK_GYROPID
) {
3097 subTaskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3098 taskFrequency
= subTaskFrequency
/ pidConfig()->pid_process_denom
;
3099 if (pidConfig()->pid_process_denom
> 1) {
3100 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
3102 taskFrequency
= subTaskFrequency
;
3103 cliPrintf("%02d - (%11s/%3s) ", taskId
, taskInfo
.subTaskName
, taskInfo
.taskName
);
3106 taskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3107 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
3109 const int maxLoad
= taskInfo
.maxExecutionTime
== 0 ? 0 :(taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
3110 const int averageLoad
= taskInfo
.averageExecutionTime
== 0 ? 0 : (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
3111 if (taskId
!= TASK_SERIAL
) {
3112 maxLoadSum
+= maxLoad
;
3113 averageLoadSum
+= averageLoad
;
3115 if (systemConfig()->task_statistics
) {
3116 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
3117 taskFrequency
, taskInfo
.maxExecutionTime
, taskInfo
.averageExecutionTime
,
3118 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, taskInfo
.totalExecutionTime
/ 1000);
3120 cliPrintLinef("%6d", taskFrequency
);
3122 if (taskId
== TASK_GYROPID
&& pidConfig()->pid_process_denom
> 1) {
3123 cliPrintLinef(" - (%15s) %6d", taskInfo
.subTaskName
, subTaskFrequency
);
3127 if (systemConfig()->task_statistics
) {
3128 cfCheckFuncInfo_t checkFuncInfo
;
3129 getCheckFuncInfo(&checkFuncInfo
);
3130 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo
.maxExecutionTime
, checkFuncInfo
.averageExecutionTime
, checkFuncInfo
.totalExecutionTime
/ 1000);
3131 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
3136 static void cliVersion(char *cmdline
)
3140 cliPrintLinef("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
3143 systemConfig()->boardIdentifier
,
3148 MSP_API_VERSION_STRING
3152 #if defined(USE_RESOURCE_MGMT)
3154 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
3157 const uint8_t owner
;
3160 const uint8_t maxIndex
;
3161 } cliResourceValue_t
;
3163 const cliResourceValue_t resourceTable
[] = {
3165 { OWNER_BEEPER
, PG_BEEPER_DEV_CONFIG
, offsetof(beeperDevConfig_t
, ioTag
), 0 },
3167 { OWNER_MOTOR
, PG_MOTOR_CONFIG
, offsetof(motorConfig_t
, dev
.ioTags
[0]), MAX_SUPPORTED_MOTORS
},
3169 { OWNER_SERVO
, PG_SERVO_CONFIG
, offsetof(servoConfig_t
, dev
.ioTags
[0]), MAX_SUPPORTED_SERVOS
},
3171 #if defined(USE_PWM) || defined(USE_PPM)
3172 { OWNER_PPMINPUT
, PG_PPM_CONFIG
, offsetof(ppmConfig_t
, ioTag
), 0 },
3173 { OWNER_PWMINPUT
, PG_PWM_CONFIG
, offsetof(pwmConfig_t
, ioTags
[0]), PWM_INPUT_PORT_COUNT
},
3175 #ifdef USE_RANGEFINDER_HCSR04
3176 { OWNER_SONAR_TRIGGER
, PG_SONAR_CONFIG
, offsetof(sonarConfig_t
, triggerTag
), 0 },
3177 { OWNER_SONAR_ECHO
, PG_SONAR_CONFIG
, offsetof(sonarConfig_t
, echoTag
), 0 },
3179 #ifdef USE_LED_STRIP
3180 { OWNER_LED_STRIP
, PG_LED_STRIP_CONFIG
, offsetof(ledStripConfig_t
, ioTag
), 0 },
3182 { OWNER_SERIAL_TX
, PG_SERIAL_PIN_CONFIG
, offsetof(serialPinConfig_t
, ioTagTx
[0]), SERIAL_PORT_MAX_INDEX
},
3183 { OWNER_SERIAL_RX
, PG_SERIAL_PIN_CONFIG
, offsetof(serialPinConfig_t
, ioTagRx
[0]), SERIAL_PORT_MAX_INDEX
},
3185 { OWNER_INVERTER
, PG_SERIAL_PIN_CONFIG
, offsetof(serialPinConfig_t
, ioTagInverter
[0]), SERIAL_PORT_MAX_INDEX
},
3188 { OWNER_I2C_SCL
, PG_I2C_CONFIG
, offsetof(i2cConfig_t
, ioTagScl
[0]), I2CDEV_COUNT
},
3189 { OWNER_I2C_SDA
, PG_I2C_CONFIG
, offsetof(i2cConfig_t
, ioTagSda
[0]), I2CDEV_COUNT
},
3191 { OWNER_LED
, PG_STATUS_LED_CONFIG
, offsetof(statusLedConfig_t
, ioTags
[0]), STATUS_LED_NUMBER
},
3192 #ifdef USE_SPEKTRUM_BIND
3193 { OWNER_RX_BIND
, PG_RX_CONFIG
, offsetof(rxConfig_t
, spektrum_bind_pin_override_ioTag
), 0 },
3194 { OWNER_RX_BIND_PLUG
, PG_RX_CONFIG
, offsetof(rxConfig_t
, spektrum_bind_plug_ioTag
), 0 },
3196 #ifdef USE_TRANSPONDER
3197 { OWNER_TRANSPONDER
, PG_TRANSPONDER_CONFIG
, offsetof(transponderConfig_t
, ioTag
), 0 },
3200 { OWNER_SPI_SCK
, PG_SPI_PIN_CONFIG
, offsetof(spiPinConfig_t
, ioTagSck
[0]), SPIDEV_COUNT
},
3201 { OWNER_SPI_MISO
, PG_SPI_PIN_CONFIG
, offsetof(spiPinConfig_t
, ioTagMiso
[0]), SPIDEV_COUNT
},
3202 { OWNER_SPI_MOSI
, PG_SPI_PIN_CONFIG
, offsetof(spiPinConfig_t
, ioTagMosi
[0]), SPIDEV_COUNT
},
3204 #ifdef USE_ESCSERIAL
3205 { OWNER_ESCSERIAL
, PG_ESCSERIAL_CONFIG
, offsetof(escSerialConfig_t
, ioTag
), 0 },
3207 #ifdef USE_CAMERA_CONTROL
3208 { OWNER_CAMERA_CONTROL
, PG_CAMERA_CONTROL_CONFIG
, offsetof(cameraControlConfig_t
, ioTag
), 0 },
3211 { OWNER_ADC_BATT
, PG_ADC_CONFIG
, offsetof(adcConfig_t
, vbat
.ioTag
), 0 },
3212 { OWNER_ADC_RSSI
, PG_ADC_CONFIG
, offsetof(adcConfig_t
, rssi
.ioTag
), 0 },
3213 { OWNER_ADC_CURR
, PG_ADC_CONFIG
, offsetof(adcConfig_t
, current
.ioTag
), 0 },
3214 { OWNER_ADC_EXT
, PG_ADC_CONFIG
, offsetof(adcConfig_t
, external1
.ioTag
), 0 },
3217 { OWNER_BARO_CS
, PG_BAROMETER_CONFIG
, offsetof(barometerConfig_t
, baro_spi_csn
), 0 },
3220 { OWNER_COMPASS_CS
, PG_COMPASS_CONFIG
, offsetof(compassConfig_t
, mag_spi_csn
), 0 },
3224 static ioTag_t
*getIoTag(const cliResourceValue_t value
, uint8_t index
)
3226 const pgRegistry_t
* rec
= pgFind(value
.pgn
);
3227 return CONST_CAST(ioTag_t
*, rec
->address
+ value
.offset
+ index
);
3230 static void printResource(uint8_t dumpMask
)
3232 for (unsigned int i
= 0; i
< ARRAYLEN(resourceTable
); i
++) {
3233 const char* owner
= ownerNames
[resourceTable
[i
].owner
];
3234 const pgRegistry_t
* pg
= pgFind(resourceTable
[i
].pgn
);
3235 const void *currentConfig
;
3236 const void *defaultConfig
;
3237 if (configIsInCopy
) {
3238 currentConfig
= pg
->copy
;
3239 defaultConfig
= pg
->address
;
3241 currentConfig
= pg
->address
;
3242 defaultConfig
= NULL
;
3245 for (int index
= 0; index
< MAX_RESOURCE_INDEX(resourceTable
[i
].maxIndex
); index
++) {
3246 const ioTag_t ioTag
= *((const ioTag_t
*)currentConfig
+ resourceTable
[i
].offset
+ index
);
3247 const ioTag_t ioTagDefault
= *((const ioTag_t
*)defaultConfig
+ resourceTable
[i
].offset
+ index
);
3249 bool equalsDefault
= ioTag
== ioTagDefault
;
3250 const char *format
= "resource %s %d %c%02d";
3251 const char *formatUnassigned
= "resource %s %d NONE";
3252 if (!ioTagDefault
) {
3253 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
3255 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTagDefault
) + 'A', IO_GPIOPinIdxByTag(ioTagDefault
));
3258 if (!(dumpMask
& HIDE_UNUSED
)) {
3259 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
3262 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
));
3268 static void printResourceOwner(uint8_t owner
, uint8_t index
)
3270 cliPrintf("%s", ownerNames
[resourceTable
[owner
].owner
]);
3272 if (resourceTable
[owner
].maxIndex
> 0) {
3273 cliPrintf(" %d", RESOURCE_INDEX(index
));
3277 static void resourceCheck(uint8_t resourceIndex
, uint8_t index
, ioTag_t newTag
)
3283 const char * format
= "\r\nNOTE: %c%02d already assigned to ";
3284 for (int r
= 0; r
< (int)ARRAYLEN(resourceTable
); r
++) {
3285 for (int i
= 0; i
< MAX_RESOURCE_INDEX(resourceTable
[r
].maxIndex
); i
++) {
3286 ioTag_t
*tag
= getIoTag(resourceTable
[r
], i
);
3287 if (*tag
== newTag
) {
3288 bool cleared
= false;
3289 if (r
== resourceIndex
) {
3297 cliPrintf(format
, DEFIO_TAG_GPIOID(newTag
) + 'A', DEFIO_TAG_PIN(newTag
));
3299 printResourceOwner(r
, i
);
3303 printResourceOwner(r
, i
);
3304 cliPrintf(" disabled");
3313 static bool strToPin(char *pch
, ioTag_t
*tag
)
3315 if (strcasecmp(pch
, "NONE") == 0) {
3320 unsigned port
= (*pch
>= 'a') ? *pch
- 'a' : *pch
- 'A';
3326 *tag
= DEFIO_TAG_MAKE(port
, pin
);
3334 static void cliResource(char *cmdline
)
3336 int len
= strlen(cmdline
);
3339 printResource(DUMP_MASTER
| HIDE_UNUSED
);
3342 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3346 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
3349 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
3351 owner
= ownerNames
[ioRecs
[i
].owner
];
3353 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
);
3354 if (ioRecs
[i
].index
> 0) {
3355 cliPrintf(" %d", ioRecs
[i
].index
);
3361 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
3367 uint8_t resourceIndex
= 0;
3372 pch
= strtok_r(cmdline
, " ", &saveptr
);
3373 for (resourceIndex
= 0; ; resourceIndex
++) {
3374 if (resourceIndex
>= ARRAYLEN(resourceTable
)) {
3375 cliPrintLine("Invalid");
3379 if (strncasecmp(pch
, ownerNames
[resourceTable
[resourceIndex
].owner
], len
) == 0) {
3384 pch
= strtok_r(NULL
, " ", &saveptr
);
3387 if (resourceTable
[resourceIndex
].maxIndex
> 0 || index
> 0) {
3388 if (index
<= 0 || index
> MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
)) {
3389 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
));
3394 pch
= strtok_r(NULL
, " ", &saveptr
);
3397 ioTag_t
*tag
= getIoTag(resourceTable
[resourceIndex
], index
);
3399 if (strlen(pch
) > 0) {
3400 if (strToPin(pch
, tag
)) {
3401 if (*tag
== IO_TAG_NONE
) {
3403 cliPrintLine("Freed");
3405 cliPrintLine("Resource is freed");
3409 ioRec_t
*rec
= IO_Rec(IOGetByTag(*tag
));
3411 resourceCheck(resourceIndex
, index
, *tag
);
3413 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
3415 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
3418 cliShowParseError();
3425 cliShowParseError();
3428 static void printDma(void)
3433 cliPrintLine("DMA:");
3435 cliPrintLine("Currently active DMA:");
3438 for (int i
= 1; i
<= DMA_LAST_HANDLER
; i
++) {
3440 owner
= ownerNames
[dmaGetOwner(i
)];
3442 cliPrintf(DMA_OUTPUT_STRING
, DMA_DEVICE_NO(i
), DMA_DEVICE_INDEX(i
));
3443 uint8_t resourceIndex
= dmaGetResourceIndex(i
);
3444 if (resourceIndex
> 0) {
3445 cliPrintLinef(" %s %d", owner
, resourceIndex
);
3447 cliPrintLinef(" %s", owner
);
3452 static void cliDma(char* cmdLine
)
3457 #endif /* USE_RESOURCE_MGMT */
3459 static void backupConfigs(void)
3461 // make copies of configs to do differencing
3463 memcpy(pg
->copy
, pg
->address
, pg
->size
);
3466 configIsInCopy
= true;
3469 static void restoreConfigs(void)
3472 memcpy(pg
->address
, pg
->copy
, pg
->size
);
3475 configIsInCopy
= false;
3478 static void printConfig(char *cmdline
, bool doDiff
)
3480 uint8_t dumpMask
= DUMP_MASTER
;
3482 if ((options
= checkCommand(cmdline
, "master"))) {
3483 dumpMask
= DUMP_MASTER
; // only
3484 } else if ((options
= checkCommand(cmdline
, "profile"))) {
3485 dumpMask
= DUMP_PROFILE
; // only
3486 } else if ((options
= checkCommand(cmdline
, "rates"))) {
3487 dumpMask
= DUMP_RATES
; // only
3488 } else if ((options
= checkCommand(cmdline
, "all"))) {
3489 dumpMask
= DUMP_ALL
; // all profiles and rates
3495 dumpMask
= dumpMask
| DO_DIFF
;
3499 // reset all configs to defaults to do differencing
3502 #if defined(USE_TARGET_CONFIG)
3503 targetConfiguration();
3505 if (checkCommand(options
, "defaults")) {
3506 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
3509 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
3510 cliPrintHashLine("version");
3513 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
3514 cliPrintHashLine("reset configuration to default settings");
3515 cliPrint("defaults nosave");
3519 cliPrintHashLine("name");
3520 printName(dumpMask
, &pilotConfig_Copy
);
3522 #ifdef USE_RESOURCE_MGMT
3523 cliPrintHashLine("resources");
3524 printResource(dumpMask
);
3527 #ifndef USE_QUAD_MIXER_ONLY
3528 cliPrintHashLine("mixer");
3529 const bool equalsDefault
= mixerConfig_Copy
.mixerMode
== mixerConfig()->mixerMode
;
3530 const char *formatMixer
= "mixer %s";
3531 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig()->mixerMode
- 1]);
3532 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig_Copy
.mixerMode
- 1]);
3534 cliDumpPrintLinef(dumpMask
, customMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
3536 printMotorMix(dumpMask
, customMotorMixer_CopyArray
, customMotorMixer(0));
3539 cliPrintHashLine("servo");
3540 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0));
3542 cliPrintHashLine("servo mix");
3543 // print custom servo mixer if exists
3544 cliDumpPrintLinef(dumpMask
, customServoMixers(0)->rate
== 0, "smix reset\r\n");
3545 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0));
3549 cliPrintHashLine("feature");
3550 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig());
3553 cliPrintHashLine("beeper");
3554 printBeeper(dumpMask
, &beeperConfig_Copy
, beeperConfig());
3557 cliPrintHashLine("map");
3558 printMap(dumpMask
, &rxConfig_Copy
, rxConfig());
3560 cliPrintHashLine("serial");
3561 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig());
3563 #ifdef USE_LED_STRIP
3564 cliPrintHashLine("led");
3565 printLed(dumpMask
, ledStripConfig_Copy
.ledConfigs
, ledStripConfig()->ledConfigs
);
3567 cliPrintHashLine("color");
3568 printColor(dumpMask
, ledStripConfig_Copy
.colors
, ledStripConfig()->colors
);
3570 cliPrintHashLine("mode_color");
3571 printModeColor(dumpMask
, &ledStripConfig_Copy
, ledStripConfig());
3574 cliPrintHashLine("aux");
3575 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0));
3577 cliPrintHashLine("adjrange");
3578 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0));
3580 cliPrintHashLine("rxrange");
3581 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0));
3583 #ifdef USE_VTX_CONTROL
3584 cliPrintHashLine("vtx");
3585 printVtx(dumpMask
, &vtxConfig_Copy
, vtxConfig());
3588 cliPrintHashLine("rxfail");
3589 printRxFailsafe(dumpMask
, rxFailsafeChannelConfigs_CopyArray
, rxFailsafeChannelConfigs(0));
3591 cliPrintHashLine("master");
3592 dumpAllValues(MASTER_VALUE
, dumpMask
);
3594 if (dumpMask
& DUMP_ALL
) {
3595 const uint8_t pidProfileIndexSave
= systemConfig_Copy
.pidProfileIndex
;
3596 for (uint32_t pidProfileIndex
= 0; pidProfileIndex
< MAX_PROFILE_COUNT
; pidProfileIndex
++) {
3597 cliDumpPidProfile(pidProfileIndex
, dumpMask
);
3599 changePidProfile(pidProfileIndexSave
);
3600 cliPrintHashLine("restore original profile selection");
3603 const uint8_t controlRateProfileIndexSave
= systemConfig_Copy
.activeRateProfile
;
3604 for (uint32_t rateIndex
= 0; rateIndex
< CONTROL_RATE_PROFILE_COUNT
; rateIndex
++) {
3605 cliDumpRateProfile(rateIndex
, dumpMask
);
3607 changeControlRateProfile(controlRateProfileIndexSave
);
3608 cliPrintHashLine("restore original rateprofile selection");
3611 cliPrintHashLine("save configuration");
3614 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
3616 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
3620 if (dumpMask
& DUMP_PROFILE
) {
3621 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
3624 if (dumpMask
& DUMP_RATES
) {
3625 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
3627 // restore configs from copies
3631 static void cliDump(char *cmdline
)
3633 printConfig(cmdline
, false);
3636 static void cliDiff(char *cmdline
)
3638 printConfig(cmdline
, true);
3644 const char *description
;
3647 void (*func
)(char *cmdline
);
3651 #define CLI_COMMAND_DEF(name, description, args, method) \
3659 #define CLI_COMMAND_DEF(name, description, args, method) \
3666 static void cliHelp(char *cmdline
);
3668 // should be sorted a..z for bsearch()
3669 const clicmd_t cmdTable
[] = {
3670 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
3671 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux
),
3673 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3674 "\t<+|->[name]", cliBeeper
),
3676 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL
, cliBootloader
),
3677 #ifdef USE_LED_STRIP
3678 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
3680 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults
),
3681 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|all] {showdefaults}", cliDiff
),
3683 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg
),
3685 CLI_COMMAND_DEF("dump", "dump configuration",
3686 "[master|profile|rates|all] {defaults}", cliDump
),
3687 #ifdef USE_ESCSERIAL
3688 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough
),
3690 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
3691 CLI_COMMAND_DEF("feature", "configure features",
3693 "\t<+|->[name]", cliFeature
),
3695 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
3696 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
3697 #ifdef USE_FLASH_TOOLS
3698 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
3699 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
3702 #ifdef USE_RX_FRSKY_SPI
3703 CLI_COMMAND_DEF("frsky_bind", "initiate binding for FrSky SPI RX", NULL
, cliFrSkyBind
),
3705 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
3707 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
3709 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
3710 #ifdef USE_LED_STRIP
3711 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
3713 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
3714 #ifndef USE_QUAD_MIXER_ONLY
3715 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer
),
3717 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
3718 #ifdef USE_LED_STRIP
3719 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
3721 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
3722 CLI_COMMAND_DEF("name", "name of craft", NULL
, cliName
),
3724 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]", cliPlaySound
),
3726 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile
),
3727 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile
),
3728 #ifdef USE_RESOURCE_MGMT
3729 CLI_COMMAND_DEF("resource", "show/set resources", NULL
, cliResource
),
3730 CLI_COMMAND_DEF("dma", "list dma utilisation", NULL
, cliDma
),
3732 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL
, cliRxFailsafe
),
3733 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
3734 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
3736 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
3738 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
3739 #ifndef SKIP_SERIAL_PASSTHROUGH
3740 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough
),
3743 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
3745 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
3747 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
3749 "\tload <mixer>\r\n"
3750 "\treverse <servo> <source> r|n", cliServoMix
),
3752 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
3753 #ifndef SKIP_TASK_STATISTICS
3754 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
3756 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
3757 #ifdef USE_VTX_CONTROL
3758 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL
, cliVtx
),
3762 static void cliHelp(char *cmdline
)
3766 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
3767 cliPrint(cmdTable
[i
].name
);
3769 if (cmdTable
[i
].description
) {
3770 cliPrintf(" - %s", cmdTable
[i
].description
);
3772 if (cmdTable
[i
].args
) {
3773 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
3780 void cliProcess(void)
3786 // Be a little bit tricky. Flush the last inputs buffer, if any.
3787 bufWriterFlush(cliWriter
);
3789 while (serialRxBytesWaiting(cliPort
)) {
3790 uint8_t c
= serialRead(cliPort
);
3791 if (c
== '\t' || c
== '?') {
3792 // do tab completion
3793 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
3794 uint32_t i
= bufferIndex
;
3795 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
3796 if (bufferIndex
&& (strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
3802 if (pstart
) { /* Buffer matches one or more commands */
3803 for (; ; bufferIndex
++) {
3804 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
3806 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
3807 /* Unambiguous -- append a space */
3808 cliBuffer
[bufferIndex
++] = ' ';
3809 cliBuffer
[bufferIndex
] = '\0';
3812 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
3815 if (!bufferIndex
|| pstart
!= pend
) {
3816 /* Print list of ambiguous matches */
3817 cliPrint("\r\033[K");
3818 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
3819 cliPrint(cmd
->name
);
3823 i
= 0; /* Redraw prompt */
3825 for (; i
< bufferIndex
; i
++)
3826 cliWrite(cliBuffer
[i
]);
3827 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
3830 } else if (c
== 12) { // NewPage / CTRL-L
3832 cliPrint("\033[2J\033[1;1H");
3834 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
3838 // Strip comment starting with # from line
3839 char *p
= cliBuffer
;
3842 bufferIndex
= (uint32_t)(p
- cliBuffer
);
3845 // Strip trailing whitespace
3846 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
3850 // Process non-empty lines
3851 if (bufferIndex
> 0) {
3852 cliBuffer
[bufferIndex
] = 0; // null terminate
3854 const clicmd_t
*cmd
;
3856 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
3857 if ((options
= checkCommand(cliBuffer
, cmd
->name
))) {
3861 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
))
3864 cliPrint("Unknown command, try 'help'");
3868 memset(cliBuffer
, 0, sizeof(cliBuffer
));
3870 // 'exit' will reset this flag, so we don't need to print prompt again
3875 } else if (c
== 127) {
3878 cliBuffer
[--bufferIndex
] = 0;
3879 cliPrint("\010 \010");
3881 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
3882 if (!bufferIndex
&& c
== ' ')
3883 continue; // Ignore leading spaces
3884 cliBuffer
[bufferIndex
++] = c
;
3890 void cliEnter(serialPort_t
*serialPort
)
3893 cliPort
= serialPort
;
3894 setPrintfSerialPort(cliPort
);
3895 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
3897 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics
);
3900 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
3902 cliPrintLine("\r\nCLI");
3906 setArmingDisabled(ARMING_DISABLED_CLI
);
3909 void cliInit(const serialConfig_t
*serialConfig
)
3911 UNUSED(serialConfig
);