2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
30 #include "common/time.h"
32 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
33 // signal that we're in cli mode
36 extern uint8_t __config_start
; // configured via linker script when building binaries.
37 extern uint8_t __config_end
;
42 #include "blackbox/blackbox.h"
44 #include "build/build_config.h"
45 #include "build/debug.h"
46 #include "build/version.h"
50 #include "common/axis.h"
51 #include "common/color.h"
52 #include "common/maths.h"
53 #include "common/printf.h"
54 #include "common/typeconversion.h"
55 #include "common/utils.h"
57 #include "config/config_eeprom.h"
58 #include "config/feature.h"
60 #include "drivers/accgyro/accgyro.h"
61 #include "drivers/adc.h"
62 #include "drivers/buf_writer.h"
63 #include "drivers/bus_spi.h"
64 #include "drivers/compass/compass.h"
65 #include "drivers/display.h"
66 #include "drivers/dma.h"
67 #include "drivers/flash.h"
68 #include "drivers/io.h"
69 #include "drivers/io_impl.h"
70 #include "drivers/inverter.h"
71 #include "drivers/sdcard.h"
72 #include "drivers/sensor.h"
73 #include "drivers/serial.h"
74 #include "drivers/serial_escserial.h"
75 #include "drivers/rangefinder/rangefinder_hcsr04.h"
76 #include "drivers/sound_beeper.h"
77 #include "drivers/stack_check.h"
78 #include "drivers/system.h"
79 #include "drivers/transponder_ir.h"
80 #include "drivers/time.h"
81 #include "drivers/timer.h"
82 #include "drivers/light_led.h"
83 #include "drivers/camera_control.h"
84 #include "drivers/vtx_common.h"
85 #include "drivers/usb_msc.h"
87 #include "fc/config.h"
88 #include "fc/controlrate_profile.h"
89 #include "fc/fc_core.h"
91 #include "fc/rc_adjustments.h"
92 #include "fc/rc_controls.h"
93 #include "fc/runtime_config.h"
95 #include "flight/position.h"
96 #include "flight/failsafe.h"
97 #include "flight/imu.h"
98 #include "flight/mixer.h"
99 #include "flight/pid.h"
100 #include "flight/servos.h"
102 #include "interface/cli.h"
103 #include "interface/msp.h"
104 #include "interface/msp_box.h"
105 #include "interface/msp_protocol.h"
106 #include "interface/settings.h"
108 #include "io/asyncfatfs/asyncfatfs.h"
109 #include "io/beeper.h"
110 #include "io/flashfs.h"
111 #include "io/gimbal.h"
113 #include "io/ledstrip.h"
115 #include "io/serial.h"
116 #include "io/transponder_ir.h"
117 #include "io/vtx_control.h"
121 #include "pg/beeper.h"
122 #include "pg/beeper_dev.h"
123 #include "pg/bus_i2c.h"
124 #include "pg/bus_spi.h"
125 #include "pg/max7456.h"
126 #include "pg/pinio.h"
128 #include "pg/pg_ids.h"
129 #include "pg/rx_pwm.h"
130 #include "pg/timerio.h"
134 #include "rx/spektrum.h"
135 #include "rx/cc2500_frsky_common.h"
136 #include "rx/cc2500_frsky_x.h"
138 #include "scheduler/scheduler.h"
140 #include "sensors/acceleration.h"
141 #include "sensors/adcinternal.h"
142 #include "sensors/barometer.h"
143 #include "sensors/battery.h"
144 #include "sensors/boardalignment.h"
145 #include "sensors/compass.h"
146 #include "sensors/esc_sensor.h"
147 #include "sensors/gyro.h"
148 #include "sensors/sensors.h"
150 #include "telemetry/frsky_hub.h"
151 #include "telemetry/telemetry.h"
154 static serialPort_t
*cliPort
;
157 #define CLI_IN_BUFFER_SIZE 128
159 // Space required to set array parameters
160 #define CLI_IN_BUFFER_SIZE 256
162 #define CLI_OUT_BUFFER_SIZE 64
164 static bufWriter_t
*cliWriter
;
165 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + CLI_OUT_BUFFER_SIZE
];
167 static char cliBuffer
[CLI_IN_BUFFER_SIZE
];
168 static uint32_t bufferIndex
= 0;
170 static bool configIsInCopy
= false;
172 static const char* const emptyName
= "-";
173 static const char* const emptryString
= "";
175 #ifndef USE_QUAD_MIXER_ONLY
176 // sync this with mixerMode_e
177 static const char * const mixerNames
[] = {
178 "TRI", "QUADP", "QUADX", "BI",
179 "GIMBAL", "Y6", "HEX6",
180 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
181 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
182 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
183 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
187 // sync this with features_e
188 static const char * const featureNames
[] = {
189 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
190 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
191 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
192 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
193 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
194 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
197 // sync this with rxFailsafeChannelMode_e
198 static const char rxFailsafeModeCharacters
[] = "ahs";
200 static const rxFailsafeChannelMode_e rxFailsafeModesTable
[RX_FAILSAFE_TYPE_COUNT
][RX_FAILSAFE_MODE_COUNT
] = {
201 { RX_FAILSAFE_MODE_AUTO
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_INVALID
},
202 { RX_FAILSAFE_MODE_INVALID
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
}
205 #if defined(USE_SENSOR_NAMES)
206 // sync this with sensors_e
207 static const char * const sensorTypeNames
[] = {
208 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
211 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
213 static const char * const *sensorHardwareNames
[] = {
214 lookupTableGyroHardware
, lookupTableAccHardware
, lookupTableBaroHardware
, lookupTableMagHardware
, lookupTableRangefinderHardware
216 #endif // USE_SENSOR_NAMES
218 static void backupPgConfig(const pgRegistry_t
*pg
)
220 memcpy(pg
->copy
, pg
->address
, pg
->size
);
223 static void restorePgConfig(const pgRegistry_t
*pg
)
225 memcpy(pg
->address
, pg
->copy
, pg
->size
);
228 static void backupConfigs(void)
230 // make copies of configs to do differencing
235 configIsInCopy
= true;
238 static void restoreConfigs(void)
244 configIsInCopy
= false;
247 static void backupAndResetConfigs(void)
250 // reset all configs to defaults to do differencing
254 static void cliPrint(const char *str
)
257 bufWriterAppend(cliWriter
, *str
++);
259 bufWriterFlush(cliWriter
);
262 static void cliPrintLinefeed(void)
267 static void cliPrintLine(const char *str
)
274 #define cliPrintHashLine(str)
276 static void cliPrintHashLine(const char *str
)
283 static void cliPutp(void *p
, char ch
)
285 bufWriterAppend(p
, ch
);
289 DUMP_MASTER
= (1 << 0),
290 DUMP_PROFILE
= (1 << 1),
291 DUMP_RATES
= (1 << 2),
294 SHOW_DEFAULTS
= (1 << 5),
295 HIDE_UNUSED
= (1 << 6)
298 static void cliPrintfva(const char *format
, va_list va
)
300 tfp_format(cliWriter
, cliPutp
, format
, va
);
301 bufWriterFlush(cliWriter
);
304 static void cliPrintLinefva(const char *format
, va_list va
)
306 tfp_format(cliWriter
, cliPutp
, format
, va
);
307 bufWriterFlush(cliWriter
);
311 static bool cliDumpPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
313 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
315 va_start(va
, format
);
316 cliPrintLinefva(format
, va
);
324 static void cliWrite(uint8_t ch
)
326 bufWriterAppend(cliWriter
, ch
);
329 static bool cliDefaultPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
331 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
335 va_start(va
, format
);
336 cliPrintLinefva(format
, va
);
344 static void cliPrintf(const char *format
, ...)
347 va_start(va
, format
);
348 cliPrintfva(format
, va
);
353 static void cliPrintLinef(const char *format
, ...)
356 va_start(va
, format
);
357 cliPrintLinefva(format
, va
);
362 static void printValuePointer(const clivalue_t
*var
, const void *valuePointer
, bool full
)
364 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
365 for (int i
= 0; i
< var
->config
.array
.length
; i
++) {
366 switch (var
->type
& VALUE_TYPE_MASK
) {
370 cliPrintf("%d", ((uint8_t *)valuePointer
)[i
]);
375 cliPrintf("%d", ((int8_t *)valuePointer
)[i
]);
380 cliPrintf("%d", ((uint16_t *)valuePointer
)[i
]);
385 cliPrintf("%d", ((int16_t *)valuePointer
)[i
]);
389 if (i
< var
->config
.array
.length
- 1) {
396 switch (var
->type
& VALUE_TYPE_MASK
) {
398 value
= *(uint8_t *)valuePointer
;
402 value
= *(int8_t *)valuePointer
;
407 value
= *(int16_t *)valuePointer
;
410 value
= *(uint32_t *)valuePointer
;
414 switch (var
->type
& VALUE_MODE_MASK
) {
416 cliPrintf("%d", value
);
418 cliPrintf(" %d %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
422 cliPrint(lookupTables
[var
->config
.lookup
.tableIndex
].values
[value
]);
425 if (value
& 1 << var
->config
.bitpos
) {
435 static bool valuePtrEqualsDefault(const clivalue_t
*var
, const void *ptr
, const void *ptrDefault
)
438 int elementCount
= 1;
439 uint32_t mask
= 0xffffffff;
441 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
442 elementCount
= var
->config
.array
.length
;
444 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
445 mask
= 1 << var
->config
.bitpos
;
447 for (int i
= 0; i
< elementCount
; i
++) {
448 switch (var
->type
& VALUE_TYPE_MASK
) {
450 result
= result
&& (((uint8_t *)ptr
)[i
] & mask
) == (((uint8_t *)ptrDefault
)[i
] & mask
);
454 result
= result
&& ((int8_t *)ptr
)[i
] == ((int8_t *)ptrDefault
)[i
];
458 result
= result
&& (((int16_t *)ptr
)[i
] & mask
) == (((int16_t *)ptrDefault
)[i
] & mask
);
461 result
= result
&& ((int16_t *)ptr
)[i
] == ((int16_t *)ptrDefault
)[i
];
464 result
= result
&& (((uint32_t *)ptr
)[i
] & mask
) == (((uint32_t *)ptrDefault
)[i
] & mask
);
472 static uint16_t getValueOffset(const clivalue_t
*value
)
474 switch (value
->type
& VALUE_SECTION_MASK
) {
476 return value
->offset
;
478 return value
->offset
+ sizeof(pidProfile_t
) * getCurrentPidProfileIndex();
479 case PROFILE_RATE_VALUE
:
480 return value
->offset
+ sizeof(controlRateConfig_t
) * getCurrentControlRateProfileIndex();
485 void *cliGetValuePointer(const clivalue_t
*value
)
487 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
488 if (configIsInCopy
) {
489 return CONST_CAST(void *, rec
->copy
+ getValueOffset(value
));
491 return CONST_CAST(void *, rec
->address
+ getValueOffset(value
));
495 const void *cliGetDefaultPointer(const clivalue_t
*value
)
497 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
498 return rec
->address
+ getValueOffset(value
);
501 static void dumpPgValue(const clivalue_t
*value
, uint8_t dumpMask
)
503 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
506 cliPrintLinef("VALUE %s ERROR", value
->name
);
507 return; // if it's not found, the pgn shouldn't be in the value table!
511 const char *format
= "set %s = ";
512 const char *defaultFormat
= "#set %s = ";
513 const int valueOffset
= getValueOffset(value
);
514 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
516 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
517 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
518 cliPrintf(defaultFormat
, value
->name
);
519 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
522 cliPrintf(format
, value
->name
);
523 printValuePointer(value
, pg
->copy
+ valueOffset
, false);
528 static void dumpAllValues(uint16_t valueSection
, uint8_t dumpMask
)
530 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
531 const clivalue_t
*value
= &valueTable
[i
];
532 bufWriterFlush(cliWriter
);
533 if ((value
->type
& VALUE_SECTION_MASK
) == valueSection
) {
534 dumpPgValue(value
, dumpMask
);
539 static void cliPrintVar(const clivalue_t
*var
, bool full
)
541 const void *ptr
= cliGetValuePointer(var
);
543 printValuePointer(var
, ptr
, full
);
546 static void cliPrintVarRange(const clivalue_t
*var
)
548 switch (var
->type
& VALUE_MODE_MASK
) {
549 case (MODE_DIRECT
): {
550 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
553 case (MODE_LOOKUP
): {
554 const lookupTableEntry_t
*tableEntry
= &lookupTables
[var
->config
.lookup
.tableIndex
];
555 cliPrint("Allowed values: ");
556 bool firstEntry
= true;
557 for (unsigned i
= 0; i
< tableEntry
->valueCount
; i
++) {
558 if (tableEntry
->values
[i
]) {
562 cliPrintf("%s", tableEntry
->values
[i
]);
570 cliPrintLinef("Array length: %d", var
->config
.array
.length
);
573 case (MODE_BITSET
): {
574 cliPrintLinef("Allowed values: OFF, ON");
580 static void cliSetVar(const clivalue_t
*var
, const int16_t value
)
582 void *ptr
= cliGetValuePointer(var
);
586 if ((var
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
587 switch (var
->type
& VALUE_TYPE_MASK
) {
589 mask
= (1 << var
->config
.bitpos
) & 0xff;
591 workValue
= *(uint8_t *)ptr
| mask
;
593 workValue
= *(uint8_t *)ptr
& ~mask
;
595 *(uint8_t *)ptr
= workValue
;
599 mask
= (1 << var
->config
.bitpos
) & 0xffff;
601 workValue
= *(uint16_t *)ptr
| mask
;
603 workValue
= *(uint16_t *)ptr
& ~mask
;
605 *(uint16_t *)ptr
= workValue
;
609 mask
= 1 << var
->config
.bitpos
;
611 workValue
= *(uint32_t *)ptr
| mask
;
613 workValue
= *(uint32_t *)ptr
& ~mask
;
615 *(uint32_t *)ptr
= workValue
;
620 switch (var
->type
& VALUE_TYPE_MASK
) {
622 *(uint8_t *)ptr
= value
;
626 *(int8_t *)ptr
= value
;
631 *(int16_t *)ptr
= value
;
637 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
638 static void cliRepeat(char ch
, uint8_t len
)
640 for (int i
= 0; i
< len
; i
++) {
641 bufWriterAppend(cliWriter
, ch
);
647 static void cliPrompt(void)
652 static void cliShowParseError(void)
654 cliPrintLine("Parse error");
657 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
659 cliPrintLinef("%s not between %d and %d", name
, min
, max
);
662 static const char *nextArg(const char *currentArg
)
664 const char *ptr
= strchr(currentArg
, ' ');
665 while (ptr
&& *ptr
== ' ') {
672 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
674 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
678 val
= CHANNEL_VALUE_TO_STEP(val
);
679 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
681 range
->startStep
= val
;
683 range
->endStep
= val
;
685 (*validArgumentCount
)++;
693 // Check if a string's length is zero
694 static bool isEmpty(const char *string
)
696 return (string
== NULL
|| *string
== '\0') ? true : false;
699 static void printRxFailsafe(uint8_t dumpMask
, const rxFailsafeChannelConfig_t
*rxFailsafeChannelConfigs
, const rxFailsafeChannelConfig_t
*defaultRxFailsafeChannelConfigs
)
701 // print out rxConfig failsafe settings
702 for (uint32_t channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
703 const rxFailsafeChannelConfig_t
*channelFailsafeConfig
= &rxFailsafeChannelConfigs
[channel
];
704 const rxFailsafeChannelConfig_t
*defaultChannelFailsafeConfig
= &defaultRxFailsafeChannelConfigs
[channel
];
705 const bool equalsDefault
= channelFailsafeConfig
->mode
== defaultChannelFailsafeConfig
->mode
706 && channelFailsafeConfig
->step
== defaultChannelFailsafeConfig
->step
;
707 const bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
709 const char *format
= "rxfail %u %c %d";
710 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
712 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
],
713 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig
->step
)
715 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
717 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
],
718 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
721 const char *format
= "rxfail %u %c";
722 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
724 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
]
726 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
728 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
]
734 static void cliRxFailsafe(char *cmdline
)
739 if (isEmpty(cmdline
)) {
740 // print out rxConfig failsafe settings
741 for (channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
742 cliRxFailsafe(itoa(channel
, buf
, 10));
745 const char *ptr
= cmdline
;
746 channel
= atoi(ptr
++);
747 if ((channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
)) {
749 rxFailsafeChannelConfig_t
*channelFailsafeConfig
= rxFailsafeChannelConfigsMutable(channel
);
751 const rxFailsafeChannelType_e type
= (channel
< NON_AUX_CHANNEL_COUNT
) ? RX_FAILSAFE_TYPE_FLIGHT
: RX_FAILSAFE_TYPE_AUX
;
752 rxFailsafeChannelMode_e mode
= channelFailsafeConfig
->mode
;
753 bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
757 const char *p
= strchr(rxFailsafeModeCharacters
, *(ptr
));
759 const uint8_t requestedMode
= p
- rxFailsafeModeCharacters
;
760 mode
= rxFailsafeModesTable
[type
][requestedMode
];
762 mode
= RX_FAILSAFE_MODE_INVALID
;
764 if (mode
== RX_FAILSAFE_MODE_INVALID
) {
769 requireValue
= mode
== RX_FAILSAFE_MODE_SET
;
777 uint16_t value
= atoi(ptr
);
778 value
= CHANNEL_VALUE_TO_RXFAIL_STEP(value
);
779 if (value
> MAX_RXFAIL_RANGE_STEP
) {
780 cliPrintLine("Value out of range");
784 channelFailsafeConfig
->step
= value
;
785 } else if (requireValue
) {
789 channelFailsafeConfig
->mode
= mode
;
792 char modeCharacter
= rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
];
794 // double use of cliPrintf below
795 // 1. acknowledge interpretation on command,
796 // 2. query current setting on single item,
799 cliPrintLinef("rxfail %u %c %d",
802 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
805 cliPrintLinef("rxfail %u %c",
811 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT
- 1);
816 static void printAux(uint8_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
)
818 const char *format
= "aux %u %u %u %u %u %u";
819 // print out aux channel settings
820 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
821 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
822 bool equalsDefault
= false;
823 if (defaultModeActivationConditions
) {
824 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
825 equalsDefault
= mac
->modeId
== macDefault
->modeId
826 && mac
->auxChannelIndex
== macDefault
->auxChannelIndex
827 && mac
->range
.startStep
== macDefault
->range
.startStep
828 && mac
->range
.endStep
== macDefault
->range
.endStep
829 && mac
->modeLogic
== macDefault
->modeLogic
;
830 const box_t
*box
= findBoxByBoxId(macDefault
->modeId
);
832 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
835 macDefault
->auxChannelIndex
,
836 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
837 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
),
838 macDefault
->modeLogic
842 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
844 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
847 mac
->auxChannelIndex
,
848 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
849 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
856 static void cliAux(char *cmdline
)
861 if (isEmpty(cmdline
)) {
862 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
);
866 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
867 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
868 uint8_t validArgumentCount
= 0;
872 const box_t
*box
= findBoxByPermanentId(val
);
874 mac
->modeId
= box
->boxId
;
875 validArgumentCount
++;
881 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
882 mac
->auxChannelIndex
= val
;
883 validArgumentCount
++;
886 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
890 if (val
== MODELOGIC_OR
|| val
== MODELOGIC_AND
) {
891 mac
->modeLogic
= val
;
892 validArgumentCount
++;
895 if (validArgumentCount
== 4) { // for backwards compatibility
896 mac
->modeLogic
= MODELOGIC_OR
;
897 } else if (validArgumentCount
!= 5) {
898 memset(mac
, 0, sizeof(modeActivationCondition_t
));
900 cliPrintLinef( "aux %u %u %u %u %u %u",
903 mac
->auxChannelIndex
,
904 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
905 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
),
909 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
914 static void printSerial(uint8_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
)
916 const char *format
= "serial %d %d %ld %ld %ld %ld";
917 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
918 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
921 bool equalsDefault
= false;
922 if (serialConfigDefault
) {
923 equalsDefault
= serialConfig
->portConfigs
[i
].identifier
== serialConfigDefault
->portConfigs
[i
].identifier
924 && serialConfig
->portConfigs
[i
].functionMask
== serialConfigDefault
->portConfigs
[i
].functionMask
925 && serialConfig
->portConfigs
[i
].msp_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
926 && serialConfig
->portConfigs
[i
].gps_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
927 && serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
928 && serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
;
929 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
930 serialConfigDefault
->portConfigs
[i
].identifier
,
931 serialConfigDefault
->portConfigs
[i
].functionMask
,
932 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
933 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
934 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
935 baudRates
[serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
]
938 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
939 serialConfig
->portConfigs
[i
].identifier
,
940 serialConfig
->portConfigs
[i
].functionMask
,
941 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
942 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
943 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
944 baudRates
[serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
]
949 static void cliSerial(char *cmdline
)
951 const char *format
= "serial %d %d %ld %ld %ld %ld";
952 if (isEmpty(cmdline
)) {
953 printSerial(DUMP_MASTER
, serialConfig(), NULL
);
956 serialPortConfig_t portConfig
;
957 memset(&portConfig
, 0 , sizeof(portConfig
));
959 serialPortConfig_t
*currentConfig
;
961 uint8_t validArgumentCount
= 0;
963 const char *ptr
= cmdline
;
965 int val
= atoi(ptr
++);
966 currentConfig
= serialFindPortConfiguration(val
);
968 portConfig
.identifier
= val
;
969 validArgumentCount
++;
975 portConfig
.functionMask
= val
& 0xFFFF;
976 validArgumentCount
++;
979 for (int i
= 0; i
< 4; i
++) {
987 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
988 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
994 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_1000000
) {
997 portConfig
.msp_baudrateIndex
= baudRateIndex
;
1000 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
1003 portConfig
.gps_baudrateIndex
= baudRateIndex
;
1006 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
1009 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
1012 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_2470000
) {
1015 portConfig
.blackbox_baudrateIndex
= baudRateIndex
;
1019 validArgumentCount
++;
1022 if (validArgumentCount
< 6) {
1023 cliShowParseError();
1027 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
1029 cliDumpPrintLinef(0, false, format
,
1030 portConfig
.identifier
,
1031 portConfig
.functionMask
,
1032 baudRates
[portConfig
.msp_baudrateIndex
],
1033 baudRates
[portConfig
.gps_baudrateIndex
],
1034 baudRates
[portConfig
.telemetry_baudrateIndex
],
1035 baudRates
[portConfig
.blackbox_baudrateIndex
]
1040 #ifndef SKIP_SERIAL_PASSTHROUGH
1042 static void cbCtrlLine(void *context
, uint16_t ctrl
)
1044 int pinioDtr
= (int)(long)context
;
1046 pinioSet(pinioDtr
, !(ctrl
& CTRL_LINE_STATE_DTR
));
1048 #endif /* USE_PINIO */
1050 static void cliSerialPassthrough(char *cmdline
)
1052 if (isEmpty(cmdline
)) {
1053 cliShowParseError();
1059 bool enableBaudCb
= false;
1062 #endif /* USE_PINIO */
1065 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
1068 while (tok
!= NULL
) {
1077 if (strstr(tok
, "rx") || strstr(tok
, "RX"))
1079 if (strstr(tok
, "tx") || strstr(tok
, "TX"))
1084 pinioDtr
= atoi(tok
);
1086 #endif /* USE_PINIO */
1089 tok
= strtok_r(NULL
, " ", &saveptr
);
1093 enableBaudCb
= true;
1096 cliPrintf("Port %d ", id
);
1097 serialPort_t
*passThroughPort
;
1098 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
1099 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
1109 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
, NULL
,
1111 SERIAL_NOT_INVERTED
);
1112 if (!passThroughPort
) {
1113 cliPrintLine("could not be opened.");
1118 cliPrintf("opened, default baud = %d.\r\n", baud
);
1120 cliPrintf("opened, baud = %d.\r\n", baud
);
1123 passThroughPort
= passThroughPortUsage
->serialPort
;
1124 // If the user supplied a mode, override the port's mode, otherwise
1125 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1126 // Set the baud rate if specified
1128 cliPrintf("already open, setting baud = %d.\n\r", baud
);
1129 serialSetBaudRate(passThroughPort
, baud
);
1131 cliPrintf("already open, baud = %d.\n\r", passThroughPort
->baudRate
);
1134 if (mode
&& passThroughPort
->mode
!= mode
) {
1135 cliPrintf("Mode changed from %d to %d.\r\n",
1136 passThroughPort
->mode
, mode
);
1137 serialSetMode(passThroughPort
, mode
);
1140 // If this port has a rx callback associated we need to remove it now.
1141 // Otherwise no data will be pushed in the serial port buffer!
1142 if (passThroughPort
->rxCallback
) {
1143 passThroughPort
->rxCallback
= 0;
1147 // If no baud rate is specified allow to be set via USB
1149 cliPrintLine("Baud rate change over USB enabled.");
1150 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1151 // baud rate over USB without setting it using the serialpassthrough command
1152 serialSetBaudRateCb(cliPort
, serialSetBaudRate
, passThroughPort
);
1155 cliPrintLine("Forwarding, power cycle to exit.");
1158 // Register control line state callback
1160 serialSetCtrlLineStateCb(cliPort
, cbCtrlLine
, (void *)(intptr_t)(pinioDtr
- 1));
1162 #endif /* USE_PINIO */
1164 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
1168 static void printAdjustmentRange(uint8_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
)
1170 const char *format
= "adjrange %u %u %u %u %u %u %u %u %u";
1171 // print out adjustment ranges channel settings
1172 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
1173 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
1174 bool equalsDefault
= false;
1175 if (defaultAdjustmentRanges
) {
1176 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
1177 equalsDefault
= ar
->auxChannelIndex
== arDefault
->auxChannelIndex
1178 && ar
->range
.startStep
== arDefault
->range
.startStep
1179 && ar
->range
.endStep
== arDefault
->range
.endStep
1180 && ar
->adjustmentFunction
== arDefault
->adjustmentFunction
1181 && ar
->auxSwitchChannelIndex
== arDefault
->auxSwitchChannelIndex
1182 && ar
->adjustmentIndex
== arDefault
->adjustmentIndex
1183 && ar
->adjustmentCenter
== arDefault
->adjustmentCenter
1184 && ar
->adjustmentScale
== arDefault
->adjustmentScale
;
1185 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1187 arDefault
->adjustmentIndex
,
1188 arDefault
->auxChannelIndex
,
1189 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
1190 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
1191 arDefault
->adjustmentFunction
,
1192 arDefault
->auxSwitchChannelIndex
,
1193 arDefault
->adjustmentCenter
,
1194 arDefault
->adjustmentScale
1197 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1199 ar
->adjustmentIndex
,
1200 ar
->auxChannelIndex
,
1201 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1202 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1203 ar
->adjustmentFunction
,
1204 ar
->auxSwitchChannelIndex
,
1205 ar
->adjustmentCenter
,
1211 static void cliAdjustmentRange(char *cmdline
)
1213 const char *format
= "adjrange %u %u %u %u %u %u %u %u %u";
1217 if (isEmpty(cmdline
)) {
1218 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
);
1222 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
1223 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
1224 uint8_t validArgumentCount
= 0;
1229 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
1230 ar
->adjustmentIndex
= val
;
1231 validArgumentCount
++;
1237 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1238 ar
->auxChannelIndex
= val
;
1239 validArgumentCount
++;
1243 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
1248 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1249 ar
->adjustmentFunction
= val
;
1250 validArgumentCount
++;
1256 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1257 ar
->auxSwitchChannelIndex
= val
;
1258 validArgumentCount
++;
1262 if (validArgumentCount
!= 6) {
1263 memset(ar
, 0, sizeof(adjustmentRange_t
));
1264 cliShowParseError();
1268 // Optional arguments
1269 ar
->adjustmentCenter
= 0;
1270 ar
->adjustmentScale
= 0;
1275 ar
->adjustmentCenter
= val
;
1276 validArgumentCount
++;
1281 ar
->adjustmentScale
= val
;
1282 validArgumentCount
++;
1284 cliDumpPrintLinef(0, false, format
,
1286 ar
->adjustmentIndex
,
1287 ar
->auxChannelIndex
,
1288 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
1289 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
1290 ar
->adjustmentFunction
,
1291 ar
->auxSwitchChannelIndex
,
1292 ar
->adjustmentCenter
,
1297 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1302 #ifndef USE_QUAD_MIXER_ONLY
1303 static void printMotorMix(uint8_t dumpMask
, const motorMixer_t
*customMotorMixer
, const motorMixer_t
*defaultCustomMotorMixer
)
1305 const char *format
= "mmix %d %s %s %s %s";
1306 char buf0
[FTOA_BUFFER_LENGTH
];
1307 char buf1
[FTOA_BUFFER_LENGTH
];
1308 char buf2
[FTOA_BUFFER_LENGTH
];
1309 char buf3
[FTOA_BUFFER_LENGTH
];
1310 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1311 if (customMotorMixer
[i
].throttle
== 0.0f
)
1313 const float thr
= customMotorMixer
[i
].throttle
;
1314 const float roll
= customMotorMixer
[i
].roll
;
1315 const float pitch
= customMotorMixer
[i
].pitch
;
1316 const float yaw
= customMotorMixer
[i
].yaw
;
1317 bool equalsDefault
= false;
1318 if (defaultCustomMotorMixer
) {
1319 const float thrDefault
= defaultCustomMotorMixer
[i
].throttle
;
1320 const float rollDefault
= defaultCustomMotorMixer
[i
].roll
;
1321 const float pitchDefault
= defaultCustomMotorMixer
[i
].pitch
;
1322 const float yawDefault
= defaultCustomMotorMixer
[i
].yaw
;
1323 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1325 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1327 ftoa(thrDefault
, buf0
),
1328 ftoa(rollDefault
, buf1
),
1329 ftoa(pitchDefault
, buf2
),
1330 ftoa(yawDefault
, buf3
));
1332 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1340 #endif // USE_QUAD_MIXER_ONLY
1342 static void cliMotorMix(char *cmdline
)
1344 #ifdef USE_QUAD_MIXER_ONLY
1351 if (isEmpty(cmdline
)) {
1352 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
);
1353 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1354 // erase custom mixer
1355 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1356 customMotorMixerMutable(i
)->throttle
= 0.0f
;
1358 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1359 ptr
= nextArg(cmdline
);
1362 for (uint32_t i
= 0; ; i
++) {
1363 if (mixerNames
[i
] == NULL
) {
1364 cliPrintLine("Invalid name");
1367 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1368 mixerLoadMix(i
, customMotorMixerMutable(0));
1369 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1377 uint32_t i
= atoi(ptr
); // get motor number
1378 if (i
< MAX_SUPPORTED_MOTORS
) {
1381 customMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1386 customMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1391 customMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1396 customMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1400 cliShowParseError();
1402 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
);
1405 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
1411 static void printRxRange(uint8_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
)
1413 const char *format
= "rxrange %u %u %u";
1414 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1415 bool equalsDefault
= false;
1416 if (defaultChannelRangeConfigs
) {
1417 equalsDefault
= channelRangeConfigs
[i
].min
== defaultChannelRangeConfigs
[i
].min
1418 && channelRangeConfigs
[i
].max
== defaultChannelRangeConfigs
[i
].max
;
1419 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1421 defaultChannelRangeConfigs
[i
].min
,
1422 defaultChannelRangeConfigs
[i
].max
1425 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1427 channelRangeConfigs
[i
].min
,
1428 channelRangeConfigs
[i
].max
1433 static void cliRxRange(char *cmdline
)
1435 const char *format
= "rxrange %u %u %u";
1436 int i
, validArgumentCount
= 0;
1439 if (isEmpty(cmdline
)) {
1440 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
);
1441 } else if (strcasecmp(cmdline
, "reset") == 0) {
1442 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1446 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1447 int rangeMin
= PWM_PULSE_MIN
, rangeMax
= PWM_PULSE_MAX
;
1451 rangeMin
= atoi(ptr
);
1452 validArgumentCount
++;
1457 rangeMax
= atoi(ptr
);
1458 validArgumentCount
++;
1461 if (validArgumentCount
!= 2) {
1462 cliShowParseError();
1463 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1464 cliShowParseError();
1466 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1467 channelRangeConfig
->min
= rangeMin
;
1468 channelRangeConfig
->max
= rangeMax
;
1469 cliDumpPrintLinef(0, false, format
,
1471 channelRangeConfig
->min
,
1472 channelRangeConfig
->max
1477 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT
- 1);
1482 #ifdef USE_LED_STRIP
1483 static void printLed(uint8_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
)
1485 const char *format
= "led %u %s";
1486 char ledConfigBuffer
[20];
1487 char ledConfigDefaultBuffer
[20];
1488 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1489 ledConfig_t ledConfig
= ledConfigs
[i
];
1490 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1491 bool equalsDefault
= false;
1492 if (defaultLedConfigs
) {
1493 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1494 equalsDefault
= ledConfig
== ledConfigDefault
;
1495 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1496 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1498 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1502 static void cliLed(char *cmdline
)
1504 const char *format
= "led %u %s";
1505 char ledConfigBuffer
[20];
1509 if (isEmpty(cmdline
)) {
1510 printLed(DUMP_MASTER
, ledStripConfig()->ledConfigs
, NULL
);
1514 if (i
< LED_MAX_STRIP_LENGTH
) {
1515 ptr
= nextArg(cmdline
);
1516 if (parseLedStripConfig(i
, ptr
)) {
1517 generateLedConfig((ledConfig_t
*)&ledStripConfig()->ledConfigs
[i
], ledConfigBuffer
, sizeof(ledConfigBuffer
));
1518 cliDumpPrintLinef(0, false, format
, i
, ledConfigBuffer
);
1520 cliShowParseError();
1523 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH
- 1);
1528 static void printColor(uint8_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
)
1530 const char *format
= "color %u %d,%u,%u";
1531 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1532 const hsvColor_t
*color
= &colors
[i
];
1533 bool equalsDefault
= false;
1534 if (defaultColors
) {
1535 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1536 equalsDefault
= color
->h
== colorDefault
->h
1537 && color
->s
== colorDefault
->s
1538 && color
->v
== colorDefault
->v
;
1539 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1541 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1545 static void cliColor(char *cmdline
)
1547 const char *format
= "color %u %d,%u,%u";
1548 if (isEmpty(cmdline
)) {
1549 printColor(DUMP_MASTER
, ledStripConfig()->colors
, NULL
);
1551 const char *ptr
= cmdline
;
1552 const int i
= atoi(ptr
);
1553 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1554 ptr
= nextArg(cmdline
);
1555 if (parseColor(i
, ptr
)) {
1556 const hsvColor_t
*color
= &ledStripConfig()->colors
[i
];
1557 cliDumpPrintLinef(0, false, format
, i
, color
->h
, color
->s
, color
->v
);
1559 cliShowParseError();
1562 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1567 static void printModeColor(uint8_t dumpMask
, const ledStripConfig_t
*ledStripConfig
, const ledStripConfig_t
*defaultLedStripConfig
)
1569 const char *format
= "mode_color %u %u %u";
1570 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1571 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1572 int colorIndex
= ledStripConfig
->modeColors
[i
].color
[j
];
1573 bool equalsDefault
= false;
1574 if (defaultLedStripConfig
) {
1575 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1576 equalsDefault
= colorIndex
== colorIndexDefault
;
1577 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1579 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1583 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1584 const int colorIndex
= ledStripConfig
->specialColors
.color
[j
];
1585 bool equalsDefault
= false;
1586 if (defaultLedStripConfig
) {
1587 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1588 equalsDefault
= colorIndex
== colorIndexDefault
;
1589 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1591 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1594 const int ledStripAuxChannel
= ledStripConfig
->ledstrip_aux_channel
;
1595 bool equalsDefault
= false;
1596 if (defaultLedStripConfig
) {
1597 const int ledStripAuxChannelDefault
= defaultLedStripConfig
->ledstrip_aux_channel
;
1598 equalsDefault
= ledStripAuxChannel
== ledStripAuxChannelDefault
;
1599 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannelDefault
);
1601 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannel
);
1604 static void cliModeColor(char *cmdline
)
1606 if (isEmpty(cmdline
)) {
1607 printModeColor(DUMP_MASTER
, ledStripConfig(), NULL
);
1609 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1610 int args
[ARGS_COUNT
];
1613 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1614 while (ptr
&& argNo
< ARGS_COUNT
) {
1615 args
[argNo
++] = atoi(ptr
);
1616 ptr
= strtok_r(NULL
, " ", &saveptr
);
1619 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1620 cliShowParseError();
1624 int modeIdx
= args
[MODE
];
1625 int funIdx
= args
[FUNCTION
];
1626 int color
= args
[COLOR
];
1627 if (!setModeColor(modeIdx
, funIdx
, color
)) {
1628 cliShowParseError();
1631 // values are validated
1632 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1638 static void printServo(uint8_t dumpMask
, const servoParam_t
*servoParams
, const servoParam_t
*defaultServoParams
)
1640 // print out servo settings
1641 const char *format
= "servo %u %d %d %d %d %d";
1642 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1643 const servoParam_t
*servoConf
= &servoParams
[i
];
1644 bool equalsDefault
= false;
1645 if (defaultServoParams
) {
1646 const servoParam_t
*defaultServoConf
= &defaultServoParams
[i
];
1647 equalsDefault
= servoConf
->min
== defaultServoConf
->min
1648 && servoConf
->max
== defaultServoConf
->max
1649 && servoConf
->middle
== defaultServoConf
->middle
1650 && servoConf
->rate
== defaultServoConf
->rate
1651 && servoConf
->forwardFromChannel
== defaultServoConf
->forwardFromChannel
;
1652 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1654 defaultServoConf
->min
,
1655 defaultServoConf
->max
,
1656 defaultServoConf
->middle
,
1657 defaultServoConf
->rate
,
1658 defaultServoConf
->forwardFromChannel
1661 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1667 servoConf
->forwardFromChannel
1670 // print servo directions
1671 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1672 const char *format
= "smix reverse %d %d r";
1673 const servoParam_t
*servoConf
= &servoParams
[i
];
1674 const servoParam_t
*servoConfDefault
= &defaultServoParams
[i
];
1675 if (defaultServoParams
) {
1676 bool equalsDefault
= servoConf
->reversedSources
== servoConfDefault
->reversedSources
;
1677 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1678 equalsDefault
= ~(servoConf
->reversedSources
^ servoConfDefault
->reversedSources
) & (1 << channel
);
1679 if (servoConfDefault
->reversedSources
& (1 << channel
)) {
1680 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1682 if (servoConf
->reversedSources
& (1 << channel
)) {
1683 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1687 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1688 if (servoConf
->reversedSources
& (1 << channel
)) {
1689 cliDumpPrintLinef(dumpMask
, true, format
, i
, channel
);
1696 static void cliServo(char *cmdline
)
1698 const char *format
= "servo %u %d %d %d %d %d";
1699 enum { SERVO_ARGUMENT_COUNT
= 6 };
1700 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1702 servoParam_t
*servo
;
1707 if (isEmpty(cmdline
)) {
1708 printServo(DUMP_MASTER
, servoParams(0), NULL
);
1710 int validArgumentCount
= 0;
1714 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1716 // If command line doesn't fit the format, don't modify the config
1718 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1719 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1720 cliShowParseError();
1724 arguments
[validArgumentCount
++] = atoi(ptr
);
1728 } while (*ptr
>= '0' && *ptr
<= '9');
1729 } else if (*ptr
== ' ') {
1732 cliShowParseError();
1737 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
, FORWARD
};
1739 i
= arguments
[INDEX
];
1741 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1742 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1743 cliShowParseError();
1747 servo
= servoParamsMutable(i
);
1750 arguments
[MIN
] < PWM_PULSE_MIN
|| arguments
[MIN
] > PWM_PULSE_MAX
||
1751 arguments
[MAX
] < PWM_PULSE_MIN
|| arguments
[MAX
] > PWM_PULSE_MAX
||
1752 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
1753 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
1754 arguments
[RATE
] < -100 || arguments
[RATE
] > 100 ||
1755 arguments
[FORWARD
] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1757 cliShowParseError();
1761 servo
->min
= arguments
[MIN
];
1762 servo
->max
= arguments
[MAX
];
1763 servo
->middle
= arguments
[MIDDLE
];
1764 servo
->rate
= arguments
[RATE
];
1765 servo
->forwardFromChannel
= arguments
[FORWARD
];
1767 cliDumpPrintLinef(0, false, format
,
1773 servo
->forwardFromChannel
1781 static void printServoMix(uint8_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
)
1783 const char *format
= "smix %d %d %d %d %d %d %d %d";
1784 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1785 const servoMixer_t customServoMixer
= customServoMixers
[i
];
1786 if (customServoMixer
.rate
== 0) {
1790 bool equalsDefault
= false;
1791 if (defaultCustomServoMixers
) {
1792 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
1793 equalsDefault
= customServoMixer
.targetChannel
== customServoMixerDefault
.targetChannel
1794 && customServoMixer
.inputSource
== customServoMixerDefault
.inputSource
1795 && customServoMixer
.rate
== customServoMixerDefault
.rate
1796 && customServoMixer
.speed
== customServoMixerDefault
.speed
1797 && customServoMixer
.min
== customServoMixerDefault
.min
1798 && customServoMixer
.max
== customServoMixerDefault
.max
1799 && customServoMixer
.box
== customServoMixerDefault
.box
;
1801 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1803 customServoMixerDefault
.targetChannel
,
1804 customServoMixerDefault
.inputSource
,
1805 customServoMixerDefault
.rate
,
1806 customServoMixerDefault
.speed
,
1807 customServoMixerDefault
.min
,
1808 customServoMixerDefault
.max
,
1809 customServoMixerDefault
.box
1812 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1814 customServoMixer
.targetChannel
,
1815 customServoMixer
.inputSource
,
1816 customServoMixer
.rate
,
1817 customServoMixer
.speed
,
1818 customServoMixer
.min
,
1819 customServoMixer
.max
,
1820 customServoMixer
.box
1827 static void cliServoMix(char *cmdline
)
1829 int args
[8], check
= 0;
1830 int len
= strlen(cmdline
);
1833 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
);
1834 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1835 // erase custom mixer
1836 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1837 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1838 servoParamsMutable(i
)->reversedSources
= 0;
1840 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1841 const char *ptr
= nextArg(cmdline
);
1844 for (uint32_t i
= 0; ; i
++) {
1845 if (mixerNames
[i
] == NULL
) {
1846 cliPrintLine("Invalid name");
1849 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1850 servoMixerLoadMix(i
);
1851 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1857 } else if (strncasecmp(cmdline
, "reverse", 7) == 0) {
1858 enum {SERVO
= 0, INPUT
, REVERSE
, ARGS_COUNT
};
1859 char *ptr
= strchr(cmdline
, ' ');
1864 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
1865 cliPrintf("\ti%d", inputSource
);
1868 for (uint32_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
1869 cliPrintf("%d", servoIndex
);
1870 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
1871 cliPrintf("\t%s ", (servoParams(servoIndex
)->reversedSources
& (1 << inputSource
)) ? "r" : "n");
1878 ptr
= strtok_r(ptr
, " ", &saveptr
);
1879 while (ptr
!= NULL
&& check
< ARGS_COUNT
- 1) {
1880 args
[check
++] = atoi(ptr
);
1881 ptr
= strtok_r(NULL
, " ", &saveptr
);
1884 if (ptr
== NULL
|| check
!= ARGS_COUNT
- 1) {
1885 cliShowParseError();
1889 if (args
[SERVO
] >= 0 && args
[SERVO
] < MAX_SUPPORTED_SERVOS
1890 && args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
1891 && (*ptr
== 'r' || *ptr
== 'n')) {
1893 servoParamsMutable(args
[SERVO
])->reversedSources
|= 1 << args
[INPUT
];
1895 servoParamsMutable(args
[SERVO
])->reversedSources
&= ~(1 << args
[INPUT
]);
1897 cliShowParseError();
1899 cliServoMix("reverse");
1901 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, MIN
, MAX
, BOX
, ARGS_COUNT
};
1903 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1904 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1905 args
[check
++] = atoi(ptr
);
1906 ptr
= strtok_r(NULL
, " ", &saveptr
);
1909 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
1910 cliShowParseError();
1914 int32_t i
= args
[RULE
];
1915 if (i
>= 0 && i
< MAX_SERVO_RULES
&&
1916 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
1917 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
1918 args
[RATE
] >= -100 && args
[RATE
] <= 100 &&
1919 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
1920 args
[MIN
] >= 0 && args
[MIN
] <= 100 &&
1921 args
[MAX
] >= 0 && args
[MAX
] <= 100 && args
[MIN
] < args
[MAX
] &&
1922 args
[BOX
] >= 0 && args
[BOX
] <= MAX_SERVO_BOXES
) {
1923 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
1924 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
1925 customServoMixersMutable(i
)->rate
= args
[RATE
];
1926 customServoMixersMutable(i
)->speed
= args
[SPEED
];
1927 customServoMixersMutable(i
)->min
= args
[MIN
];
1928 customServoMixersMutable(i
)->max
= args
[MAX
];
1929 customServoMixersMutable(i
)->box
= args
[BOX
];
1932 cliShowParseError();
1940 static void cliWriteBytes(const uint8_t *buffer
, int count
)
1949 static void cliSdInfo(char *cmdline
)
1953 cliPrint("SD card: ");
1955 if (!sdcard_isInserted()) {
1956 cliPrintLine("None inserted");
1960 if (!sdcard_isInitialized()) {
1961 cliPrintLine("Startup failed");
1965 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
1967 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1968 metadata
->manufacturerID
,
1969 metadata
->numBlocks
/ 2, /* One block is half a kB */
1970 metadata
->productionMonth
,
1971 metadata
->productionYear
,
1972 metadata
->productRevisionMajor
,
1973 metadata
->productRevisionMinor
1976 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
1978 cliPrint("'\r\n" "Filesystem: ");
1980 switch (afatfs_getFilesystemState()) {
1981 case AFATFS_FILESYSTEM_STATE_READY
:
1984 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
1985 cliPrint("Initializing");
1987 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
1988 case AFATFS_FILESYSTEM_STATE_FATAL
:
1991 switch (afatfs_getLastError()) {
1992 case AFATFS_ERROR_BAD_MBR
:
1993 cliPrint(" - no FAT MBR partitions");
1995 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
1996 cliPrint(" - bad FAT header");
1998 case AFATFS_ERROR_GENERIC
:
1999 case AFATFS_ERROR_NONE
:
2000 ; // Nothing more detailed to print
2012 static void cliFlashInfo(char *cmdline
)
2014 const flashGeometry_t
*layout
= flashfsGetGeometry();
2018 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
2019 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
, flashfsGetOffset());
2023 static void cliFlashErase(char *cmdline
)
2029 cliPrintLine("Erasing, please wait ... ");
2031 cliPrintLine("Erasing,");
2034 bufWriterFlush(cliWriter
);
2035 flashfsEraseCompletely();
2037 while (!flashfsIsReady()) {
2045 bufWriterFlush(cliWriter
);
2049 beeper(BEEPER_BLACKBOX_ERASE
);
2051 cliPrintLine("Done.");
2054 #ifdef USE_FLASH_TOOLS
2056 static void cliFlashWrite(char *cmdline
)
2058 const uint32_t address
= atoi(cmdline
);
2059 const char *text
= strchr(cmdline
, ' ');
2062 cliShowParseError();
2064 flashfsSeekAbs(address
);
2065 flashfsWrite((uint8_t*)text
, strlen(text
), true);
2068 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
2072 static void cliFlashRead(char *cmdline
)
2074 uint32_t address
= atoi(cmdline
);
2076 const char *nextArg
= strchr(cmdline
, ' ');
2079 cliShowParseError();
2081 uint32_t length
= atoi(nextArg
);
2083 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
2086 while (length
> 0) {
2087 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
2089 for (int i
= 0; i
< bytesRead
; i
++) {
2090 cliWrite(buffer
[i
]);
2093 length
-= bytesRead
;
2094 address
+= bytesRead
;
2096 if (bytesRead
== 0) {
2097 //Assume we reached the end of the volume or something fatal happened
2108 #ifdef USE_VTX_CONTROL
2109 static void printVtx(uint8_t dumpMask
, const vtxConfig_t
*vtxConfig
, const vtxConfig_t
*vtxConfigDefault
)
2111 // print out vtx channel settings
2112 const char *format
= "vtx %u %u %u %u %u %u";
2113 bool equalsDefault
= false;
2114 for (uint32_t i
= 0; i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
; i
++) {
2115 const vtxChannelActivationCondition_t
*cac
= &vtxConfig
->vtxChannelActivationConditions
[i
];
2116 if (vtxConfigDefault
) {
2117 const vtxChannelActivationCondition_t
*cacDefault
= &vtxConfigDefault
->vtxChannelActivationConditions
[i
];
2118 equalsDefault
= cac
->auxChannelIndex
== cacDefault
->auxChannelIndex
2119 && cac
->band
== cacDefault
->band
2120 && cac
->channel
== cacDefault
->channel
2121 && cac
->range
.startStep
== cacDefault
->range
.startStep
2122 && cac
->range
.endStep
== cacDefault
->range
.endStep
;
2123 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
2125 cacDefault
->auxChannelIndex
,
2127 cacDefault
->channel
,
2128 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.startStep
),
2129 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.endStep
)
2132 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
2134 cac
->auxChannelIndex
,
2137 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2138 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2143 static void cliVtx(char *cmdline
)
2145 const char *format
= "vtx %u %u %u %u %u %u";
2149 if (isEmpty(cmdline
)) {
2150 printVtx(DUMP_MASTER
, vtxConfig(), NULL
);
2154 if (i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
) {
2155 vtxChannelActivationCondition_t
*cac
= &vtxConfigMutable()->vtxChannelActivationConditions
[i
];
2156 uint8_t validArgumentCount
= 0;
2160 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
2161 cac
->auxChannelIndex
= val
;
2162 validArgumentCount
++;
2168 // FIXME Use VTX API to get min/max
2169 if (val
>= VTX_SETTINGS_MIN_BAND
&& val
<= VTX_SETTINGS_MAX_BAND
) {
2171 validArgumentCount
++;
2177 // FIXME Use VTX API to get min/max
2178 if (val
>= VTX_SETTINGS_MIN_CHANNEL
&& val
<= VTX_SETTINGS_MAX_CHANNEL
) {
2180 validArgumentCount
++;
2183 ptr
= processChannelRangeArgs(ptr
, &cac
->range
, &validArgumentCount
);
2185 if (validArgumentCount
!= 5) {
2186 memset(cac
, 0, sizeof(vtxChannelActivationCondition_t
));
2187 cliShowParseError();
2189 cliDumpPrintLinef(0, false, format
,
2191 cac
->auxChannelIndex
,
2194 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
2195 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
2199 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
- 1);
2204 #endif // VTX_CONTROL
2206 static void printName(uint8_t dumpMask
, const pilotConfig_t
*pilotConfig
)
2208 const bool equalsDefault
= strlen(pilotConfig
->name
) == 0;
2209 cliDumpPrintLinef(dumpMask
, equalsDefault
, "name %s", equalsDefault
? emptyName
: pilotConfig
->name
);
2212 static void cliName(char *cmdline
)
2214 const unsigned int len
= strlen(cmdline
);
2216 memset(pilotConfigMutable()->name
, 0, ARRAYLEN(pilotConfig()->name
));
2217 if (strncmp(cmdline
, emptyName
, len
)) {
2218 strncpy(pilotConfigMutable()->name
, cmdline
, MIN(len
, MAX_NAME_LENGTH
));
2221 printName(DUMP_MASTER
, pilotConfig());
2224 static void printFeature(uint8_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
)
2226 const uint32_t mask
= featureConfig
->enabledFeatures
;
2227 const uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
2228 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // disabled features first
2229 if (strcmp(featureNames
[i
], emptryString
) != 0) { //Skip unused
2230 const char *format
= "feature -%s";
2231 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2232 cliDumpPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2235 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // enabled features
2236 if (strcmp(featureNames
[i
], emptryString
) != 0) { //Skip unused
2237 const char *format
= "feature %s";
2238 if (defaultMask
& (1 << i
)) {
2239 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
2241 if (mask
& (1 << i
)) {
2242 cliDumpPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
2248 static void cliFeature(char *cmdline
)
2250 uint32_t len
= strlen(cmdline
);
2251 uint32_t mask
= featureMask();
2254 cliPrint("Enabled: ");
2255 for (uint32_t i
= 0; ; i
++) {
2256 if (featureNames
[i
] == NULL
)
2258 if (mask
& (1 << i
))
2259 cliPrintf("%s ", featureNames
[i
]);
2262 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2263 cliPrint("Available:");
2264 for (uint32_t i
= 0; ; i
++) {
2265 if (featureNames
[i
] == NULL
)
2267 if (strcmp(featureNames
[i
], emptryString
) != 0) //Skip unused
2268 cliPrintf(" %s", featureNames
[i
]);
2273 bool remove
= false;
2274 if (cmdline
[0] == '-') {
2277 cmdline
++; // skip over -
2281 for (uint32_t i
= 0; ; i
++) {
2282 if (featureNames
[i
] == NULL
) {
2283 cliPrintLine("Invalid name");
2287 if (strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
2291 if (mask
& FEATURE_GPS
) {
2292 cliPrintLine("unavailable");
2296 #ifndef USE_RANGEFINDER
2297 if (mask
& FEATURE_RANGEFINDER
) {
2298 cliPrintLine("unavailable");
2304 cliPrint("Disabled");
2307 cliPrint("Enabled");
2309 cliPrintLinef(" %s", featureNames
[i
]);
2317 static void printBeeper(uint8_t dumpMask
, const beeperConfig_t
*beeperConfig
, const beeperConfig_t
*beeperConfigDefault
)
2319 const uint8_t beeperCount
= beeperTableEntryCount();
2320 const uint32_t mask
= beeperConfig
->beeper_off_flags
;
2321 const uint32_t defaultMask
= beeperConfigDefault
->beeper_off_flags
;
2322 for (int32_t i
= 0; i
< beeperCount
- 2; i
++) {
2323 const char *formatOff
= "beeper -%s";
2324 const char *formatOn
= "beeper %s";
2325 const uint32_t beeperModeMask
= beeperModeMaskForTableIndex(i
);
2326 cliDefaultPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & beeperModeMask
, mask
& beeperModeMask
? formatOn
: formatOff
, beeperNameForTableIndex(i
));
2327 cliDumpPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & beeperModeMask
, mask
& beeperModeMask
? formatOff
: formatOn
, beeperNameForTableIndex(i
));
2331 static void cliBeeper(char *cmdline
)
2333 uint32_t len
= strlen(cmdline
);
2334 uint8_t beeperCount
= beeperTableEntryCount();
2335 uint32_t mask
= getBeeperOffMask();
2338 cliPrintf("Disabled:");
2339 for (int32_t i
= 0; ; i
++) {
2340 if (i
== beeperCount
- 2) {
2346 if (mask
& beeperModeMaskForTableIndex(i
))
2347 cliPrintf(" %s", beeperNameForTableIndex(i
));
2350 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2351 cliPrint("Available:");
2352 for (uint32_t i
= 0; i
< beeperCount
; i
++)
2353 cliPrintf(" %s", beeperNameForTableIndex(i
));
2357 bool remove
= false;
2358 if (cmdline
[0] == '-') {
2359 remove
= true; // this is for beeper OFF condition
2364 for (uint32_t i
= 0; ; i
++) {
2365 if (i
== beeperCount
) {
2366 cliPrintLine("Invalid name");
2369 if (strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0) {
2370 if (remove
) { // beeper off
2371 if (i
== BEEPER_ALL
-1)
2372 beeperOffSetAll(beeperCount
-2);
2374 if (i
== BEEPER_PREFERENCE
-1)
2375 setBeeperOffMask(getPreferredBeeperOffMask());
2377 beeperOffSet(beeperModeMaskForTableIndex(i
));
2379 cliPrint("Disabled");
2382 if (i
== BEEPER_ALL
-1)
2383 beeperOffClearAll();
2385 if (i
== BEEPER_PREFERENCE
-1)
2386 setPreferredBeeperOffMask(getBeeperOffMask());
2388 beeperOffClear(beeperModeMaskForTableIndex(i
));
2390 cliPrint("Enabled");
2392 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
2400 void cliFrSkyBind(char *cmdline
){
2402 switch (rxConfig()->rx_spi_protocol
) {
2403 #ifdef USE_RX_FRSKY_SPI
2404 case RX_SPI_FRSKY_D
:
2405 case RX_SPI_FRSKY_X
:
2408 cliPrint("Binding...");
2413 cliPrint("Not supported.");
2419 static void printMap(uint8_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
)
2421 bool equalsDefault
= true;
2423 char bufDefault
[16];
2425 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2426 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2427 if (defaultRxConfig
) {
2428 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2429 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
2434 const char *formatMap
= "map %s";
2435 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
2436 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
2440 static void cliMap(char *cmdline
)
2443 char buf
[RX_MAPPABLE_CHANNEL_COUNT
+ 1];
2445 uint32_t len
= strlen(cmdline
);
2446 if (len
== RX_MAPPABLE_CHANNEL_COUNT
) {
2448 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2449 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
2453 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2454 buf
[i
] = toupper((unsigned char)cmdline
[i
]);
2456 if (strchr(rcChannelLetters
, buf
[i
]) && !strchr(buf
+ i
+ 1, buf
[i
]))
2459 cliShowParseError();
2462 parseRcChannels(buf
, rxConfigMutable());
2463 } else if (len
> 0) {
2464 cliShowParseError();
2468 for (i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
2469 buf
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
2473 cliPrintLinef("map %s", buf
);
2476 static char *skipSpace(char *buffer
)
2478 while (*(buffer
) == ' ') {
2485 static char *checkCommand(char *cmdLine
, const char *command
)
2487 if (!strncasecmp(cmdLine
, command
, strlen(command
)) // command names match
2488 && (isspace((unsigned)cmdLine
[strlen(command
)]) || cmdLine
[strlen(command
)] == 0)) {
2489 return skipSpace(cmdLine
+ strlen(command
) + 1);
2495 static void cliRebootEx(bool bootLoader
)
2497 cliPrint("\r\nRebooting");
2498 bufWriterFlush(cliWriter
);
2499 waitForSerialPortToFinishTransmitting(cliPort
);
2502 systemResetToBootloader();
2508 static void cliReboot(void)
2513 static void cliBootloader(char *cmdLine
)
2517 cliPrintHashLine("restarting in bootloader mode");
2521 static void cliExit(char *cmdline
)
2525 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2526 bufWriterFlush(cliWriter
);
2531 // incase a motor was left running during motortest, clear it here
2532 mixerResetDisarmedMotors();
2539 static void cliGpsPassthrough(char *cmdline
)
2543 gpsEnablePassthrough(cliPort
);
2547 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
2548 static void cliPrintGyroRegisters(uint8_t whichSensor
)
2550 tfp_printf("# WHO_AM_I 0x%X\r\n", gyroReadRegister(whichSensor
, MPU_RA_WHO_AM_I
));
2551 tfp_printf("# CONFIG 0x%X\r\n", gyroReadRegister(whichSensor
, MPU_RA_CONFIG
));
2552 tfp_printf("# GYRO_CONFIG 0x%X\r\n", gyroReadRegister(whichSensor
, MPU_RA_GYRO_CONFIG
));
2555 static void cliDumpGyroRegisters(char *cmdline
)
2557 #ifdef USE_DUAL_GYRO
2558 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_1
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
2559 tfp_printf("\r\n# Gyro 1\r\n");
2560 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
2562 if ((gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_2
) || (gyroConfig()->gyro_to_use
== GYRO_CONFIG_USE_GYRO_BOTH
)) {
2563 tfp_printf("\r\n# Gyro 2\r\n");
2564 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2
);
2567 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1
);
2568 #endif // USE_DUAL_GYRO
2574 static int parseOutputIndex(char *pch
, bool allowAllEscs
) {
2575 int outputIndex
= atoi(pch
);
2576 if ((outputIndex
>= 0) && (outputIndex
< getMotorCount())) {
2577 tfp_printf("Using output %d.\r\n", outputIndex
);
2578 } else if (allowAllEscs
&& outputIndex
== ALL_MOTORS
) {
2579 tfp_printf("Using all outputs.\r\n");
2581 tfp_printf("Invalid output number. Range: 0 %d.\r\n", getMotorCount() - 1);
2589 #if defined(USE_DSHOT)
2590 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2592 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
2593 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
2594 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
2602 #define ESC_INFO_VERSION_POSITION 12
2604 void printEscInfo(const uint8_t *escInfoBuffer
, uint8_t bytesRead
)
2606 bool escInfoReceived
= false;
2607 if (bytesRead
> ESC_INFO_VERSION_POSITION
) {
2608 uint8_t escInfoVersion
;
2609 uint8_t frameLength
;
2610 if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 254) {
2611 escInfoVersion
= ESC_INFO_BLHELI32
;
2612 frameLength
= ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
;
2613 } else if (escInfoBuffer
[ESC_INFO_VERSION_POSITION
] == 255) {
2614 escInfoVersion
= ESC_INFO_KISS_V2
;
2615 frameLength
= ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE
;
2617 escInfoVersion
= ESC_INFO_KISS_V1
;
2618 frameLength
= ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE
;
2621 if (bytesRead
== frameLength
) {
2622 escInfoReceived
= true;
2624 if (calculateCrc8(escInfoBuffer
, frameLength
- 1) == escInfoBuffer
[frameLength
- 1]) {
2625 uint8_t firmwareVersion
= 0;
2626 uint8_t firmwareSubVersion
= 0;
2627 uint8_t escType
= 0;
2628 switch (escInfoVersion
) {
2629 case ESC_INFO_KISS_V1
:
2630 firmwareVersion
= escInfoBuffer
[12];
2631 firmwareSubVersion
= (escInfoBuffer
[13] & 0x1f) + 97;
2632 escType
= (escInfoBuffer
[13] & 0xe0) >> 5;
2635 case ESC_INFO_KISS_V2
:
2636 firmwareVersion
= escInfoBuffer
[13];
2637 firmwareSubVersion
= escInfoBuffer
[14];
2638 escType
= escInfoBuffer
[15];
2641 case ESC_INFO_BLHELI32
:
2642 firmwareVersion
= escInfoBuffer
[13];
2643 firmwareSubVersion
= escInfoBuffer
[14];
2644 escType
= escInfoBuffer
[15];
2649 cliPrint("ESC Type: ");
2650 switch (escInfoVersion
) {
2651 case ESC_INFO_KISS_V1
:
2652 case ESC_INFO_KISS_V2
:
2655 cliPrintLine("KISS8A");
2659 cliPrintLine("KISS16A");
2663 cliPrintLine("KISS24A");
2667 cliPrintLine("KISS Ultralite");
2671 cliPrintLine("unknown");
2677 case ESC_INFO_BLHELI32
:
2679 char *escType
= (char *)(escInfoBuffer
+ 31);
2681 cliPrintLine(escType
);
2687 cliPrint("MCU Serial No: 0x");
2688 for (int i
= 0; i
< 12; i
++) {
2689 if (i
&& (i
% 3 == 0)) {
2692 cliPrintf("%02x", escInfoBuffer
[i
]);
2696 switch (escInfoVersion
) {
2697 case ESC_INFO_KISS_V1
:
2698 case ESC_INFO_KISS_V2
:
2699 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion
/ 100, firmwareVersion
% 100, (char)firmwareSubVersion
);
2702 case ESC_INFO_BLHELI32
:
2703 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion
, firmwareSubVersion
);
2707 if (escInfoVersion
== ESC_INFO_KISS_V2
|| escInfoVersion
== ESC_INFO_BLHELI32
) {
2708 cliPrintLinef("Rotation Direction: %s", escInfoBuffer
[16] ? "reversed" : "normal");
2709 cliPrintLinef("3D: %s", escInfoBuffer
[17] ? "on" : "off");
2710 if (escInfoVersion
== ESC_INFO_BLHELI32
) {
2711 uint8_t setting
= escInfoBuffer
[18];
2712 cliPrint("Low voltage Limit: ");
2715 cliPrintLine("off");
2719 cliPrintLine("unsupported");
2723 cliPrintLinef("%d.%01d", setting
/ 10, setting
% 10);
2728 setting
= escInfoBuffer
[19];
2729 cliPrint("Current Limit: ");
2732 cliPrintLine("off");
2736 cliPrintLine("unsupported");
2740 cliPrintLinef("%d", setting
);
2745 for (int i
= 0; i
< 4; i
++) {
2746 setting
= escInfoBuffer
[i
+ 20];
2747 cliPrintLinef("LED %d: %s", i
, setting
? (setting
== 255) ? "unsupported" : "on" : "off");
2752 cliPrintLine("Checksum Error.");
2757 if (!escInfoReceived
) {
2758 cliPrintLine("No Info.");
2762 static void executeEscInfoCommand(uint8_t escIndex
)
2764 cliPrintLinef("Info for ESC %d:", escIndex
);
2766 uint8_t escInfoBuffer
[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
];
2768 startEscDataRead(escInfoBuffer
, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE
);
2770 pwmWriteDshotCommand(escIndex
, getMotorCount(), DSHOT_CMD_ESC_INFO
);
2774 printEscInfo(escInfoBuffer
, getNumberEscBytesRead());
2776 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
2778 static void cliDshotProg(char *cmdline
)
2780 if (isEmpty(cmdline
) || motorConfig()->dev
.motorPwmProtocol
< PWM_TYPE_DSHOT150
) {
2781 cliShowParseError();
2787 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2790 bool firstCommand
= true;
2791 while (pch
!= NULL
) {
2794 escIndex
= parseOutputIndex(pch
, true);
2795 if (escIndex
== -1) {
2802 int command
= atoi(pch
);
2803 if (command
>= 0 && command
< DSHOT_MIN_THROTTLE
) {
2807 if (command
== DSHOT_CMD_ESC_INFO
) {
2808 delay(5); // Wait for potential ESC telemetry transmission to finish
2813 firstCommand
= false;
2816 if (command
!= DSHOT_CMD_ESC_INFO
) {
2817 pwmWriteDshotCommand(escIndex
, getMotorCount(), command
);
2819 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2820 if (feature(FEATURE_ESC_SENSOR
)) {
2821 if (escIndex
!= ALL_MOTORS
) {
2822 executeEscInfoCommand(escIndex
);
2824 for (uint8_t i
= 0; i
< getMotorCount(); i
++) {
2825 executeEscInfoCommand(i
);
2831 cliPrintLine("Not supported.");
2835 cliPrintLinef("Command Sent: %d", command
);
2838 delay(20); // wait for sound output to finish
2841 cliPrintLinef("Invalid command. Range: 1 - %d.", DSHOT_MIN_THROTTLE
- 1);
2849 pch
= strtok_r(NULL
, " ", &saveptr
);
2856 #ifdef USE_ESCSERIAL
2857 static void cliEscPassthrough(char *cmdline
)
2859 if (isEmpty(cmdline
)) {
2860 cliShowParseError();
2866 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2870 while (pch
!= NULL
) {
2873 if (strncasecmp(pch
, "sk", strlen(pch
)) == 0) {
2874 mode
= PROTOCOL_SIMONK
;
2875 } else if (strncasecmp(pch
, "bl", strlen(pch
)) == 0) {
2876 mode
= PROTOCOL_BLHELI
;
2877 } else if (strncasecmp(pch
, "ki", strlen(pch
)) == 0) {
2878 mode
= PROTOCOL_KISS
;
2879 } else if (strncasecmp(pch
, "cc", strlen(pch
)) == 0) {
2880 mode
= PROTOCOL_KISSALL
;
2882 cliShowParseError();
2888 escIndex
= parseOutputIndex(pch
, mode
== PROTOCOL_KISS
);
2889 if (escIndex
== -1) {
2895 cliShowParseError();
2903 pch
= strtok_r(NULL
, " ", &saveptr
);
2906 escEnablePassthrough(cliPort
, escIndex
, mode
);
2910 #ifndef USE_QUAD_MIXER_ONLY
2911 static void cliMixer(char *cmdline
)
2915 len
= strlen(cmdline
);
2918 cliPrintLinef("Mixer: %s", mixerNames
[mixerConfig()->mixerMode
- 1]);
2920 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2921 cliPrint("Available:");
2922 for (uint32_t i
= 0; ; i
++) {
2923 if (mixerNames
[i
] == NULL
)
2925 cliPrintf(" %s", mixerNames
[i
]);
2931 for (uint32_t i
= 0; ; i
++) {
2932 if (mixerNames
[i
] == NULL
) {
2933 cliPrintLine("Invalid name");
2936 if (strncasecmp(cmdline
, mixerNames
[i
], len
) == 0) {
2937 mixerConfigMutable()->mixerMode
= i
+ 1;
2946 static void cliMotor(char *cmdline
)
2948 if (isEmpty(cmdline
)) {
2949 cliShowParseError();
2958 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2960 while (pch
!= NULL
) {
2963 motorIndex
= parseOutputIndex(pch
, true);
2964 if (motorIndex
== -1) {
2970 motorValue
= atoi(pch
);
2975 pch
= strtok_r(NULL
, " ", &saveptr
);
2979 if (motorValue
< PWM_RANGE_MIN
|| motorValue
> PWM_RANGE_MAX
) {
2980 cliShowArgumentRangeError("value", 1000, 2000);
2982 uint32_t motorOutputValue
= convertExternalToMotor(motorValue
);
2984 if (motorIndex
!= ALL_MOTORS
) {
2985 motor_disarmed
[motorIndex
] = motorOutputValue
;
2987 cliPrintLinef("motor %d: %d", motorIndex
, motorOutputValue
);
2989 for (int i
= 0; i
< getMotorCount(); i
++) {
2990 motor_disarmed
[i
] = motorOutputValue
;
2993 cliPrintLinef("all motors: %d", motorOutputValue
);
2997 cliShowParseError();
3002 static void cliPlaySound(char *cmdline
)
3006 static int lastSoundIdx
= -1;
3008 if (isEmpty(cmdline
)) {
3009 i
= lastSoundIdx
+ 1; //next sound index
3010 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3011 while (true) { //no name for index; try next one
3012 if (++i
>= beeperTableEntryCount())
3013 i
= 0; //if end then wrap around to first entry
3014 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
3015 break; //if name OK then play sound below
3016 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
3017 cliPrintLine("Error playing sound");
3022 } else { //index value was given
3024 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
3025 cliPrintLinef("No sound for index %d", i
);
3031 cliPrintLinef("Playing sound %d: %s", i
, name
);
3032 beeper(beeperModeForTableIndex(i
));
3036 static void cliProfile(char *cmdline
)
3038 if (isEmpty(cmdline
)) {
3039 cliPrintLinef("profile %d", getCurrentPidProfileIndex());
3042 const int i
= atoi(cmdline
);
3043 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
3044 systemConfigMutable()->pidProfileIndex
= i
;
3050 static void cliRateProfile(char *cmdline
)
3052 if (isEmpty(cmdline
)) {
3053 cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
3056 const int i
= atoi(cmdline
);
3057 if (i
>= 0 && i
< CONTROL_RATE_PROFILE_COUNT
) {
3058 changeControlRateProfile(i
);
3064 static void cliDumpPidProfile(uint8_t pidProfileIndex
, uint8_t dumpMask
)
3066 if (pidProfileIndex
>= MAX_PROFILE_COUNT
) {
3070 changePidProfile(pidProfileIndex
);
3071 cliPrintHashLine("profile");
3074 dumpAllValues(PROFILE_VALUE
, dumpMask
);
3077 static void cliDumpRateProfile(uint8_t rateProfileIndex
, uint8_t dumpMask
)
3079 if (rateProfileIndex
>= CONTROL_RATE_PROFILE_COUNT
) {
3083 changeControlRateProfile(rateProfileIndex
);
3084 cliPrintHashLine("rateprofile");
3087 dumpAllValues(PROFILE_RATE_VALUE
, dumpMask
);
3090 static void cliSave(char *cmdline
)
3094 cliPrintHashLine("saving");
3099 static void cliDefaults(char *cmdline
)
3103 if (isEmpty(cmdline
)) {
3105 } else if (strncasecmp(cmdline
, "nosave", 6) == 0) {
3106 saveConfigs
= false;
3111 cliPrintHashLine("resetting to defaults");
3118 void cliPrintVarDefault(const clivalue_t
*value
)
3120 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
3122 const char *defaultFormat
= "Default value: ";
3123 const int valueOffset
= getValueOffset(value
);
3124 const bool equalsDefault
= valuePtrEqualsDefault(value
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
3125 if (!equalsDefault
) {
3126 cliPrintf(defaultFormat
, value
->name
);
3127 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
3133 STATIC_UNIT_TESTED
void cliGet(char *cmdline
)
3135 const clivalue_t
*val
;
3136 int matchedCommands
= 0;
3138 backupAndResetConfigs();
3140 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
3141 if (strcasestr(valueTable
[i
].name
, cmdline
)) {
3142 val
= &valueTable
[i
];
3143 if (matchedCommands
> 0) {
3146 cliPrintf("%s = ", valueTable
[i
].name
);
3147 cliPrintVar(val
, 0);
3149 cliPrintVarRange(val
);
3150 cliPrintVarDefault(val
);
3157 if (matchedCommands
) {
3161 cliPrintLine("Invalid name");
3164 static uint8_t getWordLength(char *bufBegin
, char *bufEnd
)
3166 while (*(bufEnd
- 1) == ' ') {
3170 return bufEnd
- bufBegin
;
3173 STATIC_UNIT_TESTED
void cliSet(char *cmdline
)
3175 const uint32_t len
= strlen(cmdline
);
3178 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
3179 cliPrintLine("Current settings: ");
3181 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
3182 const clivalue_t
*val
= &valueTable
[i
];
3183 cliPrintf("%s = ", valueTable
[i
].name
);
3184 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3187 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
3190 uint8_t variableNameLength
= getWordLength(cmdline
, eqptr
);
3192 // skip the '=' and any ' ' characters
3194 eqptr
= skipSpace(eqptr
);
3196 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
3197 const clivalue_t
*val
= &valueTable
[i
];
3199 // ensure exact match when setting to prevent setting variables with shorter names
3200 if (strncasecmp(cmdline
, val
->name
, strlen(val
->name
)) == 0 && variableNameLength
== strlen(val
->name
)) {
3202 bool valueChanged
= false;
3204 switch (val
->type
& VALUE_MODE_MASK
) {
3206 int16_t value
= atoi(eqptr
);
3208 if (value
>= val
->config
.minmax
.min
&& value
<= val
->config
.minmax
.max
) {
3209 cliSetVar(val
, value
);
3210 valueChanged
= true;
3218 if ((val
->type
& VALUE_MODE_MASK
) == MODE_BITSET
) {
3219 tableIndex
= TABLE_OFF_ON
;
3221 tableIndex
= val
->config
.lookup
.tableIndex
;
3223 const lookupTableEntry_t
*tableEntry
= &lookupTables
[tableIndex
];
3224 bool matched
= false;
3225 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
3226 matched
= tableEntry
->values
[tableValueIndex
] && strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
3229 value
= tableValueIndex
;
3231 cliSetVar(val
, value
);
3232 valueChanged
= true;
3240 const uint8_t arrayLength
= val
->config
.array
.length
;
3241 char *valPtr
= eqptr
;
3244 while (i
< arrayLength
&& valPtr
!= NULL
) {
3246 valPtr
= skipSpace(valPtr
);
3248 // process substring starting at valPtr
3249 // note: no need to copy substrings for atoi()
3250 // it stops at the first character that cannot be converted...
3251 switch (val
->type
& VALUE_TYPE_MASK
) {
3255 // fetch data pointer
3256 uint8_t *data
= (uint8_t *)cliGetValuePointer(val
) + i
;
3258 *data
= (uint8_t)atoi((const char*) valPtr
);
3264 // fetch data pointer
3265 int8_t *data
= (int8_t *)cliGetValuePointer(val
) + i
;
3267 *data
= (int8_t)atoi((const char*) valPtr
);
3273 // fetch data pointer
3274 uint16_t *data
= (uint16_t *)cliGetValuePointer(val
) + i
;
3276 *data
= (uint16_t)atoi((const char*) valPtr
);
3282 // fetch data pointer
3283 int16_t *data
= (int16_t *)cliGetValuePointer(val
) + i
;
3285 *data
= (int16_t)atoi((const char*) valPtr
);
3291 // find next comma (or end of string)
3292 valPtr
= strchr(valPtr
, ',') + 1;
3299 valueChanged
= true;
3306 cliPrintf("%s set to ", val
->name
);
3307 cliPrintVar(val
, 0);
3309 cliPrintLine("Invalid value");
3310 cliPrintVarRange(val
);
3316 cliPrintLine("Invalid name");
3318 // no equals, check for matching variables.
3323 static void cliStatus(char *cmdline
)
3327 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3330 char buf
[FORMATTED_DATE_TIME_BUFSIZE
];
3332 if (rtcGetDateTime(&dt
)) {
3333 dateTimeFormatLocal(buf
, &dt
);
3334 cliPrintLinef("Current Time: %s", buf
);
3338 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
3340 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock
/ 1000000));
3342 #ifdef USE_ADC_INTERNAL
3343 uint16_t vrefintMv
= getVrefMv();
3344 int16_t coretemp
= getCoreTemperatureCelsius();
3345 cliPrintf(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv
/ 1000, (vrefintMv
% 1000) / 10, coretemp
);
3348 #if defined(USE_SENSOR_NAMES)
3349 const uint32_t detectedSensorsMask
= sensorsMask();
3350 for (uint32_t i
= 0; ; i
++) {
3351 if (sensorTypeNames
[i
] == NULL
) {
3354 const uint32_t mask
= (1 << i
);
3355 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
3356 const uint8_t sensorHardwareIndex
= detectedSensors
[i
];
3357 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
3358 cliPrintf(", %s=%s", sensorTypeNames
[i
], sensorHardware
);
3359 if (mask
== SENSOR_ACC
&& acc
.dev
.revisionCode
) {
3360 cliPrintf(".%c", acc
.dev
.revisionCode
);
3364 #endif /* USE_SENSOR_NAMES */
3372 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
3374 const uint16_t i2cErrorCounter
= 0;
3378 cliPrintf("Stack used: %d, ", stackUsedSize());
3380 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
3381 #ifdef EEPROM_IN_RAM
3382 #define CONFIG_SIZE EEPROM_SIZE
3384 #define CONFIG_SIZE (&__config_end - &__config_start)
3386 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter
, getEEPROMConfigSize(), CONFIG_SIZE
);
3388 const int gyroRate
= getTaskDeltaTime(TASK_GYROPID
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_GYROPID
)));
3389 const int rxRate
= currentRxRefreshRate
== 0 ? 0 : (int)(1000000.0f
/ ((float)currentRxRefreshRate
));
3390 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
3391 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
3392 constrain(averageSystemLoadPercent
, 0, 100), getTaskDeltaTime(TASK_GYROPID
), gyroRate
, rxRate
, systemRate
);
3393 cliPrint("Arming disable flags:");
3394 armingDisableFlags_e flags
= getArmingDisableFlags();
3396 const int bitpos
= ffs(flags
) - 1;
3397 flags
&= ~(1 << bitpos
);
3398 cliPrintf(" %s", armingDisableFlagNames
[bitpos
]);
3403 #ifndef SKIP_TASK_STATISTICS
3404 static void cliTasks(char *cmdline
)
3408 int averageLoadSum
= 0;
3411 if (systemConfig()->task_statistics
) {
3412 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
3414 cliPrintLine("Task list");
3417 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
3418 cfTaskInfo_t taskInfo
;
3419 getTaskInfo(taskId
, &taskInfo
);
3420 if (taskInfo
.isEnabled
) {
3422 int subTaskFrequency
= 0;
3423 if (taskId
== TASK_GYROPID
) {
3424 subTaskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3425 taskFrequency
= subTaskFrequency
/ pidConfig()->pid_process_denom
;
3426 if (pidConfig()->pid_process_denom
> 1) {
3427 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
3429 taskFrequency
= subTaskFrequency
;
3430 cliPrintf("%02d - (%11s/%3s) ", taskId
, taskInfo
.subTaskName
, taskInfo
.taskName
);
3433 taskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
3434 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
3436 const int maxLoad
= taskInfo
.maxExecutionTime
== 0 ? 0 :(taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
3437 const int averageLoad
= taskInfo
.averageExecutionTime
== 0 ? 0 : (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
3438 if (taskId
!= TASK_SERIAL
) {
3439 maxLoadSum
+= maxLoad
;
3440 averageLoadSum
+= averageLoad
;
3442 if (systemConfig()->task_statistics
) {
3443 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
3444 taskFrequency
, taskInfo
.maxExecutionTime
, taskInfo
.averageExecutionTime
,
3445 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, taskInfo
.totalExecutionTime
/ 1000);
3447 cliPrintLinef("%6d", taskFrequency
);
3449 if (taskId
== TASK_GYROPID
&& pidConfig()->pid_process_denom
> 1) {
3450 cliPrintLinef(" - (%15s) %6d", taskInfo
.subTaskName
, subTaskFrequency
);
3454 if (systemConfig()->task_statistics
) {
3455 cfCheckFuncInfo_t checkFuncInfo
;
3456 getCheckFuncInfo(&checkFuncInfo
);
3457 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo
.maxExecutionTime
, checkFuncInfo
.averageExecutionTime
, checkFuncInfo
.totalExecutionTime
/ 1000);
3458 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
3463 static void cliVersion(char *cmdline
)
3467 cliPrintLinef("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
3470 systemConfig()->boardIdentifier
,
3475 MSP_API_VERSION_STRING
3479 #if defined(USE_RESOURCE_MGMT)
3481 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
3484 const uint8_t owner
;
3488 const uint8_t maxIndex
;
3489 } cliResourceValue_t
;
3491 // Handy macros for keeping the table tidy.
3492 // DEFS : Single entry
3493 // DEFA : Array of uint8_t (stride = 1)
3494 // DEFW : Wider stride case; array of structs.
3496 #define DEFS(owner, pgn, type, member) \
3497 { owner, pgn, 0, offsetof(type, member), 0 }
3499 #define DEFA(owner, pgn, type, member, max) \
3500 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
3502 #define DEFW(owner, pgn, type, member, max) \
3503 { owner, pgn, sizeof(type), offsetof(type, member), max }
3505 const cliResourceValue_t resourceTable
[] = {
3507 DEFS( OWNER_BEEPER
, PG_BEEPER_DEV_CONFIG
, beeperDevConfig_t
, ioTag
) ,
3509 DEFA( OWNER_MOTOR
, PG_MOTOR_CONFIG
, motorConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_MOTORS
),
3511 DEFA( OWNER_SERVO
, PG_SERVO_CONFIG
, servoConfig_t
, dev
.ioTags
[0], MAX_SUPPORTED_SERVOS
),
3513 #if defined(USE_PWM) || defined(USE_PPM)
3514 DEFS( OWNER_PPMINPUT
, PG_PPM_CONFIG
, ppmConfig_t
, ioTag
),
3515 DEFA( OWNER_PWMINPUT
, PG_PWM_CONFIG
, pwmConfig_t
, ioTags
[0], PWM_INPUT_PORT_COUNT
),
3517 #ifdef USE_RANGEFINDER_HCSR04
3518 DEFS( OWNER_SONAR_TRIGGER
, PG_SONAR_CONFIG
, sonarConfig_t
, triggerTag
),
3519 DEFS( OWNER_SONAR_ECHO
, PG_SONAR_CONFIG
, sonarConfig_t
, echoTag
),
3521 #ifdef USE_LED_STRIP
3522 DEFS( OWNER_LED_STRIP
, PG_LED_STRIP_CONFIG
, ledStripConfig_t
, ioTag
),
3524 DEFA( OWNER_SERIAL_TX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagTx
[0], SERIAL_PORT_MAX_INDEX
),
3525 DEFA( OWNER_SERIAL_RX
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagRx
[0], SERIAL_PORT_MAX_INDEX
),
3527 DEFA( OWNER_INVERTER
, PG_SERIAL_PIN_CONFIG
, serialPinConfig_t
, ioTagInverter
[0], SERIAL_PORT_MAX_INDEX
),
3530 DEFW( OWNER_I2C_SCL
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagScl
, I2CDEV_COUNT
),
3531 DEFW( OWNER_I2C_SDA
, PG_I2C_CONFIG
, i2cConfig_t
, ioTagSda
, I2CDEV_COUNT
),
3533 DEFA( OWNER_LED
, PG_STATUS_LED_CONFIG
, statusLedConfig_t
, ioTags
[0], STATUS_LED_NUMBER
),
3534 #ifdef USE_SPEKTRUM_BIND
3535 DEFS( OWNER_RX_BIND
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_pin_override_ioTag
),
3536 DEFS( OWNER_RX_BIND_PLUG
, PG_RX_CONFIG
, rxConfig_t
, spektrum_bind_plug_ioTag
),
3538 #ifdef USE_TRANSPONDER
3539 DEFS( OWNER_TRANSPONDER
, PG_TRANSPONDER_CONFIG
, transponderConfig_t
, ioTag
),
3542 DEFW( OWNER_SPI_SCK
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagSck
, SPIDEV_COUNT
),
3543 DEFW( OWNER_SPI_MISO
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMiso
, SPIDEV_COUNT
),
3544 DEFW( OWNER_SPI_MOSI
, PG_SPI_PIN_CONFIG
, spiPinConfig_t
, ioTagMosi
, SPIDEV_COUNT
),
3546 #ifdef USE_ESCSERIAL
3547 DEFS( OWNER_ESCSERIAL
, PG_ESCSERIAL_CONFIG
, escSerialConfig_t
, ioTag
),
3549 #ifdef USE_CAMERA_CONTROL
3550 DEFS( OWNER_CAMERA_CONTROL
, PG_CAMERA_CONTROL_CONFIG
, cameraControlConfig_t
, ioTag
),
3553 DEFS( OWNER_ADC_BATT
, PG_ADC_CONFIG
, adcConfig_t
, vbat
.ioTag
),
3554 DEFS( OWNER_ADC_RSSI
, PG_ADC_CONFIG
, adcConfig_t
, rssi
.ioTag
),
3555 DEFS( OWNER_ADC_CURR
, PG_ADC_CONFIG
, adcConfig_t
, current
.ioTag
),
3556 DEFS( OWNER_ADC_EXT
, PG_ADC_CONFIG
, adcConfig_t
, external1
.ioTag
),
3559 DEFS( OWNER_BARO_CS
, PG_BAROMETER_CONFIG
, barometerConfig_t
, baro_spi_csn
),
3562 DEFS( OWNER_COMPASS_CS
, PG_COMPASS_CONFIG
, compassConfig_t
, mag_spi_csn
),
3565 DEFS( OWNER_SDCARD_CS
, PG_SDCARD_CONFIG
, sdcardConfig_t
, chipSelectTag
),
3566 DEFS( OWNER_SDCARD_DETECT
, PG_SDCARD_CONFIG
, sdcardConfig_t
, cardDetectTag
),
3569 DEFA( OWNER_PINIO
, PG_PINIO_CONFIG
, pinioConfig_t
, ioTag
, PINIO_COUNT
),
3571 #if defined(USE_USB_MSC)
3572 DEFS( OWNER_USB_MSC_PIN
, PG_USB_CONFIG
, usbDev_t
, mscButtonPin
),
3575 DEFS( OWNER_FLASH_CS
, PG_FLASH_CONFIG
, flashConfig_t
, csTag
),
3578 DEFS( OWNER_OSD_CS
, PG_MAX7456_CONFIG
, max7456Config_t
, csTag
),
3586 static ioTag_t
*getIoTag(const cliResourceValue_t value
, uint8_t index
)
3588 const pgRegistry_t
* rec
= pgFind(value
.pgn
);
3589 return CONST_CAST(ioTag_t
*, rec
->address
+ value
.stride
* index
+ value
.offset
);
3592 static void printResource(uint8_t dumpMask
)
3594 for (unsigned int i
= 0; i
< ARRAYLEN(resourceTable
); i
++) {
3595 const char* owner
= ownerNames
[resourceTable
[i
].owner
];
3596 const pgRegistry_t
* pg
= pgFind(resourceTable
[i
].pgn
);
3597 const void *currentConfig
;
3598 const void *defaultConfig
;
3599 if (configIsInCopy
) {
3600 currentConfig
= pg
->copy
;
3601 defaultConfig
= pg
->address
;
3603 currentConfig
= pg
->address
;
3604 defaultConfig
= NULL
;
3607 for (int index
= 0; index
< MAX_RESOURCE_INDEX(resourceTable
[i
].maxIndex
); index
++) {
3608 const ioTag_t ioTag
= *((const uint8_t *)currentConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
3609 const ioTag_t ioTagDefault
= *((const uint8_t *)defaultConfig
+ resourceTable
[i
].stride
* index
+ resourceTable
[i
].offset
);
3611 bool equalsDefault
= ioTag
== ioTagDefault
;
3612 const char *format
= "resource %s %d %c%02d";
3613 const char *formatUnassigned
= "resource %s %d NONE";
3614 if (!ioTagDefault
) {
3615 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
3617 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTagDefault
) + 'A', IO_GPIOPinIdxByTag(ioTagDefault
));
3620 if (!(dumpMask
& HIDE_UNUSED
)) {
3621 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
3624 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
));
3630 static void printResourceOwner(uint8_t owner
, uint8_t index
)
3632 cliPrintf("%s", ownerNames
[resourceTable
[owner
].owner
]);
3634 if (resourceTable
[owner
].maxIndex
> 0) {
3635 cliPrintf(" %d", RESOURCE_INDEX(index
));
3639 static void resourceCheck(uint8_t resourceIndex
, uint8_t index
, ioTag_t newTag
)
3645 const char * format
= "\r\nNOTE: %c%02d already assigned to ";
3646 for (int r
= 0; r
< (int)ARRAYLEN(resourceTable
); r
++) {
3647 for (int i
= 0; i
< MAX_RESOURCE_INDEX(resourceTable
[r
].maxIndex
); i
++) {
3648 ioTag_t
*tag
= getIoTag(resourceTable
[r
], i
);
3649 if (*tag
== newTag
) {
3650 bool cleared
= false;
3651 if (r
== resourceIndex
) {
3659 cliPrintf(format
, DEFIO_TAG_GPIOID(newTag
) + 'A', DEFIO_TAG_PIN(newTag
));
3661 printResourceOwner(r
, i
);
3665 printResourceOwner(r
, i
);
3666 cliPrintf(" disabled");
3675 static bool strToPin(char *pch
, ioTag_t
*tag
)
3677 if (strcasecmp(pch
, "NONE") == 0) {
3682 unsigned port
= (*pch
>= 'a') ? *pch
- 'a' : *pch
- 'A';
3688 *tag
= DEFIO_TAG_MAKE(port
, pin
);
3696 static void cliResource(char *cmdline
)
3698 int len
= strlen(cmdline
);
3701 printResource(DUMP_MASTER
| HIDE_UNUSED
);
3704 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3708 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
3711 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
3713 owner
= ownerNames
[ioRecs
[i
].owner
];
3715 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
);
3716 if (ioRecs
[i
].index
> 0) {
3717 cliPrintf(" %d", ioRecs
[i
].index
);
3723 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
3729 uint8_t resourceIndex
= 0;
3734 pch
= strtok_r(cmdline
, " ", &saveptr
);
3735 for (resourceIndex
= 0; ; resourceIndex
++) {
3736 if (resourceIndex
>= ARRAYLEN(resourceTable
)) {
3737 cliPrintLine("Invalid");
3741 if (strncasecmp(pch
, ownerNames
[resourceTable
[resourceIndex
].owner
], len
) == 0) {
3746 pch
= strtok_r(NULL
, " ", &saveptr
);
3749 if (resourceTable
[resourceIndex
].maxIndex
> 0 || index
> 0) {
3750 if (index
<= 0 || index
> MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
)) {
3751 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
));
3756 pch
= strtok_r(NULL
, " ", &saveptr
);
3759 ioTag_t
*tag
= getIoTag(resourceTable
[resourceIndex
], index
);
3761 if (strlen(pch
) > 0) {
3762 if (strToPin(pch
, tag
)) {
3763 if (*tag
== IO_TAG_NONE
) {
3765 cliPrintLine("Freed");
3767 cliPrintLine("Resource is freed");
3771 ioRec_t
*rec
= IO_Rec(IOGetByTag(*tag
));
3773 resourceCheck(resourceIndex
, index
, *tag
);
3775 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
3777 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec
) + 'A', IO_GPIOPinIdx(rec
));
3780 cliShowParseError();
3787 cliShowParseError();
3790 static void printDma(void)
3795 cliPrintLine("DMA:");
3797 cliPrintLine("Currently active DMA:");
3800 for (int i
= 1; i
<= DMA_LAST_HANDLER
; i
++) {
3802 owner
= ownerNames
[dmaGetOwner(i
)];
3804 cliPrintf(DMA_OUTPUT_STRING
, DMA_DEVICE_NO(i
), DMA_DEVICE_INDEX(i
));
3805 uint8_t resourceIndex
= dmaGetResourceIndex(i
);
3806 if (resourceIndex
> 0) {
3807 cliPrintLinef(" %s %d", owner
, resourceIndex
);
3809 cliPrintLinef(" %s", owner
);
3814 static void cliDma(char* cmdLine
)
3819 #endif /* USE_RESOURCE_MGMT */
3821 #ifdef USE_TIMER_MGMT
3823 static void printTimer(uint8_t dumpMask
)
3825 cliPrintLine("# examples: ");
3826 const char *format
= "timer %c%02d %d";
3828 cliPrintLinef(format
, 'A', 1, 1);
3831 cliPrintLinef(format
, 'A', 1, 0);
3833 for (unsigned int i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
3835 const ioTag_t ioTag
= timerIOConfig(i
)->ioTag
;
3836 const uint8_t timerIndex
= timerIOConfig(i
)->index
;
3842 if (timerIndex
!= 0 && !(dumpMask
& HIDE_UNUSED
)) {
3843 cliDumpPrintLinef(dumpMask
, false, format
,
3844 IO_GPIOPortIdxByTag(ioTag
) + 'A',
3845 IO_GPIOPinIdxByTag(ioTag
),
3852 static void cliTimer(char *cmdline
)
3854 int len
= strlen(cmdline
);
3857 printTimer(DUMP_MASTER
| HIDE_UNUSED
);
3859 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
3860 printTimer(DUMP_MASTER
);
3866 int timerIOIndex
= -1;
3869 pch
= strtok_r(cmdline
, " ", &saveptr
);
3870 if (!pch
|| !(strToPin(pch
, &ioTag
) && IOGetByTag(ioTag
))) {
3874 /* find existing entry, or go for next available */
3875 for (unsigned i
= 0; i
< MAX_TIMER_PINMAP_COUNT
; i
++) {
3876 if (timerIOConfig(i
)->ioTag
== ioTag
) {
3881 /* first available empty slot */
3882 if (timerIOIndex
< 0 && timerIOConfig(i
)->ioTag
== IO_TAG_NONE
) {
3887 if (timerIOIndex
< 0) {
3888 cliPrintLine("Error: out of index");
3892 uint8_t timerIndex
= 0;
3893 pch
= strtok_r(NULL
, " ", &saveptr
);
3895 if (strcasecmp(pch
, "list") == 0) {
3896 /* output the list of available options */
3898 for (unsigned i
= 0; i
< USABLE_TIMER_CHANNEL_COUNT
; i
++) {
3899 if (timerHardware
[i
].tag
== ioTag
) {
3900 cliPrintLinef("# %d. TIM%d CH%d",
3902 timerGetTIMNumber(timerHardware
[i
].tim
),
3903 CC_INDEX_FROM_CHANNEL(timerHardware
[i
].channel
)
3909 } else if (strcasecmp(pch
, "none") == 0) {
3912 timerIndex
= atoi(pch
);
3919 timerIOConfigMutable(timerIOIndex
)->ioTag
= timerIndex
== 0 ? IO_TAG_NONE
: ioTag
;
3920 timerIOConfigMutable(timerIOIndex
)->index
= timerIndex
;
3922 cliPrintLine("Success");
3926 cliShowParseError();
3930 static void printConfig(char *cmdline
, bool doDiff
)
3932 uint8_t dumpMask
= DUMP_MASTER
;
3934 if ((options
= checkCommand(cmdline
, "master"))) {
3935 dumpMask
= DUMP_MASTER
; // only
3936 } else if ((options
= checkCommand(cmdline
, "profile"))) {
3937 dumpMask
= DUMP_PROFILE
; // only
3938 } else if ((options
= checkCommand(cmdline
, "rates"))) {
3939 dumpMask
= DUMP_RATES
; // only
3940 } else if ((options
= checkCommand(cmdline
, "all"))) {
3941 dumpMask
= DUMP_ALL
; // all profiles and rates
3947 dumpMask
= dumpMask
| DO_DIFF
;
3950 backupAndResetConfigs();
3951 if (checkCommand(options
, "defaults")) {
3952 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
3955 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
3956 cliPrintHashLine("version");
3959 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
3960 cliPrintHashLine("reset configuration to default settings");
3961 cliPrint("defaults nosave");
3965 cliPrintHashLine("name");
3966 printName(dumpMask
, &pilotConfig_Copy
);
3968 #ifdef USE_RESOURCE_MGMT
3969 cliPrintHashLine("resources");
3970 printResource(dumpMask
);
3973 #ifndef USE_QUAD_MIXER_ONLY
3974 cliPrintHashLine("mixer");
3975 const bool equalsDefault
= mixerConfig_Copy
.mixerMode
== mixerConfig()->mixerMode
;
3976 const char *formatMixer
= "mixer %s";
3977 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig()->mixerMode
- 1]);
3978 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig_Copy
.mixerMode
- 1]);
3980 cliDumpPrintLinef(dumpMask
, customMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
3982 printMotorMix(dumpMask
, customMotorMixer_CopyArray
, customMotorMixer(0));
3985 cliPrintHashLine("servo");
3986 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0));
3988 cliPrintHashLine("servo mix");
3989 // print custom servo mixer if exists
3990 cliDumpPrintLinef(dumpMask
, customServoMixers(0)->rate
== 0, "smix reset\r\n");
3991 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0));
3995 cliPrintHashLine("feature");
3996 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig());
3999 cliPrintHashLine("beeper");
4000 printBeeper(dumpMask
, &beeperConfig_Copy
, beeperConfig());
4003 cliPrintHashLine("map");
4004 printMap(dumpMask
, &rxConfig_Copy
, rxConfig());
4006 cliPrintHashLine("serial");
4007 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig());
4009 #ifdef USE_LED_STRIP
4010 cliPrintHashLine("led");
4011 printLed(dumpMask
, ledStripConfig_Copy
.ledConfigs
, ledStripConfig()->ledConfigs
);
4013 cliPrintHashLine("color");
4014 printColor(dumpMask
, ledStripConfig_Copy
.colors
, ledStripConfig()->colors
);
4016 cliPrintHashLine("mode_color");
4017 printModeColor(dumpMask
, &ledStripConfig_Copy
, ledStripConfig());
4020 cliPrintHashLine("aux");
4021 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0));
4023 cliPrintHashLine("adjrange");
4024 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0));
4026 cliPrintHashLine("rxrange");
4027 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0));
4029 #ifdef USE_VTX_CONTROL
4030 cliPrintHashLine("vtx");
4031 printVtx(dumpMask
, &vtxConfig_Copy
, vtxConfig());
4034 cliPrintHashLine("rxfail");
4035 printRxFailsafe(dumpMask
, rxFailsafeChannelConfigs_CopyArray
, rxFailsafeChannelConfigs(0));
4037 cliPrintHashLine("master");
4038 dumpAllValues(MASTER_VALUE
, dumpMask
);
4040 if (dumpMask
& DUMP_ALL
) {
4041 const uint8_t pidProfileIndexSave
= systemConfig_Copy
.pidProfileIndex
;
4042 for (uint32_t pidProfileIndex
= 0; pidProfileIndex
< MAX_PROFILE_COUNT
; pidProfileIndex
++) {
4043 cliDumpPidProfile(pidProfileIndex
, dumpMask
);
4045 changePidProfile(pidProfileIndexSave
);
4046 cliPrintHashLine("restore original profile selection");
4049 const uint8_t controlRateProfileIndexSave
= systemConfig_Copy
.activeRateProfile
;
4050 for (uint32_t rateIndex
= 0; rateIndex
< CONTROL_RATE_PROFILE_COUNT
; rateIndex
++) {
4051 cliDumpRateProfile(rateIndex
, dumpMask
);
4053 changeControlRateProfile(controlRateProfileIndexSave
);
4054 cliPrintHashLine("restore original rateprofile selection");
4057 cliPrintHashLine("save configuration");
4060 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
4062 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
4066 if (dumpMask
& DUMP_PROFILE
) {
4067 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
4070 if (dumpMask
& DUMP_RATES
) {
4071 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
4073 // restore configs from copies
4077 static void cliDump(char *cmdline
)
4079 printConfig(cmdline
, false);
4082 static void cliDiff(char *cmdline
)
4084 printConfig(cmdline
, true);
4088 static void cliMsc(char *cmdline
)
4094 || sdcard_isFunctional()
4097 || flashfsGetSize() > 0
4100 cliPrintHashLine("restarting in mass storage mode");
4101 cliPrint("\r\nRebooting");
4102 bufWriterFlush(cliWriter
);
4104 waitForSerialPortToFinishTransmitting(cliPort
);
4111 *((__IO
uint32_t*) BKPSRAM_BASE
+ 16) = MSC_MAGIC
;
4112 #elif defined(STM32F4)
4113 *((uint32_t *)0x2001FFF0) = MSC_MAGIC
;
4119 cliPrint("\r\nStorage not present or failed to initialize!");
4120 bufWriterFlush(cliWriter
);
4128 const char *description
;
4131 void (*func
)(char *cmdline
);
4135 #define CLI_COMMAND_DEF(name, description, args, method) \
4143 #define CLI_COMMAND_DEF(name, description, args, method) \
4150 static void cliHelp(char *cmdline
);
4152 // should be sorted a..z for bsearch()
4153 const clicmd_t cmdTable
[] = {
4154 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
4155 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux
),
4157 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
4158 "\t<+|->[name]", cliBeeper
),
4160 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL
, cliBootloader
),
4161 #ifdef USE_LED_STRIP
4162 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
4164 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults
),
4165 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|all] {defaults}", cliDiff
),
4166 #ifdef USE_RESOURCE_MGMT
4167 CLI_COMMAND_DEF("dma", "list dma utilisation", NULL
, cliDma
),
4170 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg
),
4172 CLI_COMMAND_DEF("dump", "dump configuration",
4173 "[master|profile|rates|all] {defaults}", cliDump
),
4174 #ifdef USE_ESCSERIAL
4175 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough
),
4177 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
4178 CLI_COMMAND_DEF("feature", "configure features",
4180 "\t<+|->[name]", cliFeature
),
4182 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
4183 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
4184 #ifdef USE_FLASH_TOOLS
4185 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
4186 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
4189 #ifdef USE_RX_FRSKY_SPI
4190 CLI_COMMAND_DEF("frsky_bind", "initiate binding for FrSky SPI RX", NULL
, cliFrSkyBind
),
4192 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
4194 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
4196 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
4197 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL
, cliDumpGyroRegisters
),
4199 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
4200 #ifdef USE_LED_STRIP
4201 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
4203 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
4204 #ifndef USE_QUAD_MIXER_ONLY
4205 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer
),
4207 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
4208 #ifdef USE_LED_STRIP
4209 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
4211 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
4213 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL
, cliMsc
),
4215 CLI_COMMAND_DEF("name", "name of craft", NULL
, cliName
),
4217 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]", cliPlaySound
),
4219 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile
),
4220 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile
),
4221 #ifdef USE_RESOURCE_MGMT
4222 CLI_COMMAND_DEF("resource", "show/set resources", NULL
, cliResource
),
4224 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL
, cliRxFailsafe
),
4225 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
4226 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
4228 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
4230 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
4231 #ifndef SKIP_SERIAL_PASSTHROUGH
4232 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [DTR PINIO]: passthrough to serial", cliSerialPassthrough
),
4235 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
4237 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
4239 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
4241 "\tload <mixer>\r\n"
4242 "\treverse <servo> <source> r|n", cliServoMix
),
4244 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
4245 #ifndef SKIP_TASK_STATISTICS
4246 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
4248 #ifdef USE_TIMER_MGMT
4249 CLI_COMMAND_DEF("timer", "show timer configuration", NULL
, cliTimer
),
4251 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
4252 #ifdef USE_VTX_CONTROL
4253 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL
, cliVtx
),
4257 static void cliHelp(char *cmdline
)
4261 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
4262 cliPrint(cmdTable
[i
].name
);
4264 if (cmdTable
[i
].description
) {
4265 cliPrintf(" - %s", cmdTable
[i
].description
);
4267 if (cmdTable
[i
].args
) {
4268 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
4275 void cliProcess(void)
4281 // Be a little bit tricky. Flush the last inputs buffer, if any.
4282 bufWriterFlush(cliWriter
);
4284 while (serialRxBytesWaiting(cliPort
)) {
4285 uint8_t c
= serialRead(cliPort
);
4286 if (c
== '\t' || c
== '?') {
4287 // do tab completion
4288 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
4289 uint32_t i
= bufferIndex
;
4290 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4291 if (bufferIndex
&& (strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
4297 if (pstart
) { /* Buffer matches one or more commands */
4298 for (; ; bufferIndex
++) {
4299 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
4301 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
4302 /* Unambiguous -- append a space */
4303 cliBuffer
[bufferIndex
++] = ' ';
4304 cliBuffer
[bufferIndex
] = '\0';
4307 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
4310 if (!bufferIndex
|| pstart
!= pend
) {
4311 /* Print list of ambiguous matches */
4312 cliPrint("\r\033[K");
4313 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
4314 cliPrint(cmd
->name
);
4318 i
= 0; /* Redraw prompt */
4320 for (; i
< bufferIndex
; i
++)
4321 cliWrite(cliBuffer
[i
]);
4322 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
4325 } else if (c
== 12) { // NewPage / CTRL-L
4327 cliPrint("\033[2J\033[1;1H");
4329 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
4333 // Strip comment starting with # from line
4334 char *p
= cliBuffer
;
4337 bufferIndex
= (uint32_t)(p
- cliBuffer
);
4340 // Strip trailing whitespace
4341 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
4345 // Process non-empty lines
4346 if (bufferIndex
> 0) {
4347 cliBuffer
[bufferIndex
] = 0; // null terminate
4349 const clicmd_t
*cmd
;
4351 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
4352 if ((options
= checkCommand(cliBuffer
, cmd
->name
))) {
4356 if (cmd
< cmdTable
+ ARRAYLEN(cmdTable
))
4359 cliPrint("Unknown command, try 'help'");
4363 memset(cliBuffer
, 0, sizeof(cliBuffer
));
4365 // 'exit' will reset this flag, so we don't need to print prompt again
4370 } else if (c
== 127) {
4373 cliBuffer
[--bufferIndex
] = 0;
4374 cliPrint("\010 \010");
4376 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
4377 if (!bufferIndex
&& c
== ' ')
4378 continue; // Ignore leading spaces
4379 cliBuffer
[bufferIndex
++] = c
;
4385 void cliEnter(serialPort_t
*serialPort
)
4388 cliPort
= serialPort
;
4389 setPrintfSerialPort(cliPort
);
4390 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
4392 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics
);
4395 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4397 cliPrintLine("\r\nCLI");
4401 setArmingDisabled(ARMING_DISABLED_CLI
);
4404 void cliInit(const serialConfig_t
*serialConfig
)
4406 UNUSED(serialConfig
);