Merge pull request #5809 from jirif/rx_rate_calculation
[betaflight.git] / src / main / interface / cli.c
blobcd3986ae5974882af430f120dd764ae792f3f268
1 /*
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)
8 * any later version.
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/>.
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <math.h>
27 #include <ctype.h>
29 #include "platform.h"
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
34 uint8_t cliMode = 0;
35 #ifndef EEPROM_IN_RAM
36 extern uint8_t __config_start; // configured via linker script when building binaries.
37 extern uint8_t __config_end;
38 #endif
40 #ifdef USE_CLI
42 #include "blackbox/blackbox.h"
44 #include "build/build_config.h"
45 #include "build/debug.h"
46 #include "build/version.h"
48 #include "cms/cms.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"
90 #include "fc/fc_rc.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/navigation.h"
100 #include "flight/pid.h"
101 #include "flight/servos.h"
103 #include "interface/cli.h"
104 #include "interface/msp.h"
105 #include "interface/msp_box.h"
106 #include "interface/msp_protocol.h"
107 #include "interface/settings.h"
109 #include "io/asyncfatfs/asyncfatfs.h"
110 #include "io/beeper.h"
111 #include "io/flashfs.h"
112 #include "io/gimbal.h"
113 #include "io/gps.h"
114 #include "io/ledstrip.h"
115 #include "io/osd.h"
116 #include "io/serial.h"
117 #include "io/transponder_ir.h"
118 #include "io/vtx_control.h"
119 #include "io/vtx.h"
121 #include "pg/adc.h"
122 #include "pg/beeper.h"
123 #include "pg/beeper_dev.h"
124 #include "pg/bus_i2c.h"
125 #include "pg/bus_spi.h"
126 #include "pg/max7456.h"
127 #include "pg/pinio.h"
128 #include "pg/pg.h"
129 #include "pg/pg_ids.h"
130 #include "pg/rx_pwm.h"
131 #include "pg/usb.h"
133 #include "rx/rx.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;
156 #ifdef STM32F1
157 #define CLI_IN_BUFFER_SIZE 128
158 #else
159 // Space required to set array parameters
160 #define CLI_IN_BUFFER_SIZE 256
161 #endif
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
185 #endif
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 cliPrint(const char *str)
220 while (*str) {
221 bufWriterAppend(cliWriter, *str++);
223 bufWriterFlush(cliWriter);
226 static void cliPrintLinefeed(void)
228 cliPrint("\r\n");
231 static void cliPrintLine(const char *str)
233 cliPrint(str);
234 cliPrintLinefeed();
237 #ifdef MINIMAL_CLI
238 #define cliPrintHashLine(str)
239 #else
240 static void cliPrintHashLine(const char *str)
242 cliPrint("\r\n# ");
243 cliPrintLine(str);
245 #endif
247 static void cliPutp(void *p, char ch)
249 bufWriterAppend(p, ch);
252 typedef enum {
253 DUMP_MASTER = (1 << 0),
254 DUMP_PROFILE = (1 << 1),
255 DUMP_RATES = (1 << 2),
256 DUMP_ALL = (1 << 3),
257 DO_DIFF = (1 << 4),
258 SHOW_DEFAULTS = (1 << 5),
259 HIDE_UNUSED = (1 << 6)
260 } dumpFlags_e;
262 static void cliPrintfva(const char *format, va_list va)
264 tfp_format(cliWriter, cliPutp, format, va);
265 bufWriterFlush(cliWriter);
268 static void cliPrintLinefva(const char *format, va_list va)
270 tfp_format(cliWriter, cliPutp, format, va);
271 bufWriterFlush(cliWriter);
272 cliPrintLinefeed();
275 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
277 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
278 va_list va;
279 va_start(va, format);
280 cliPrintLinefva(format, va);
281 va_end(va);
282 return true;
283 } else {
284 return false;
288 static void cliWrite(uint8_t ch)
290 bufWriterAppend(cliWriter, ch);
293 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
295 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
296 cliWrite('#');
298 va_list va;
299 va_start(va, format);
300 cliPrintLinefva(format, va);
301 va_end(va);
302 return true;
303 } else {
304 return false;
308 static void cliPrintf(const char *format, ...)
310 va_list va;
311 va_start(va, format);
312 cliPrintfva(format, va);
313 va_end(va);
317 static void cliPrintLinef(const char *format, ...)
319 va_list va;
320 va_start(va, format);
321 cliPrintLinefva(format, va);
322 va_end(va);
326 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
328 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
329 for (int i = 0; i < var->config.array.length; i++) {
330 switch (var->type & VALUE_TYPE_MASK) {
331 default:
332 case VAR_UINT8:
333 // uint8_t array
334 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
335 break;
337 case VAR_INT8:
338 // int8_t array
339 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
340 break;
342 case VAR_UINT16:
343 // uin16_t array
344 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
345 break;
347 case VAR_INT16:
348 // int16_t array
349 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
350 break;
353 if (i < var->config.array.length - 1) {
354 cliPrint(",");
357 } else {
358 int value = 0;
360 switch (var->type & VALUE_TYPE_MASK) {
361 case VAR_UINT8:
362 value = *(uint8_t *)valuePointer;
363 break;
365 case VAR_INT8:
366 value = *(int8_t *)valuePointer;
367 break;
369 case VAR_UINT16:
370 case VAR_INT16:
371 value = *(int16_t *)valuePointer;
372 break;
375 switch (var->type & VALUE_MODE_MASK) {
376 case MODE_DIRECT:
377 cliPrintf("%d", value);
378 if (full) {
379 cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
381 break;
382 case MODE_LOOKUP:
383 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
384 break;
390 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
392 bool result = true;
393 int elementCount = 1;
395 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
396 elementCount = var->config.array.length;
398 for (int i = 0; i < elementCount; i++) {
399 switch (var->type & VALUE_TYPE_MASK) {
400 case VAR_UINT8:
401 result = result && ((uint8_t *)ptr)[i] == ((uint8_t *)ptrDefault)[i];
402 break;
404 case VAR_INT8:
405 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
406 break;
408 case VAR_UINT16:
409 case VAR_INT16:
410 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
411 break;
415 return result;
418 static uint16_t getValueOffset(const clivalue_t *value)
420 switch (value->type & VALUE_SECTION_MASK) {
421 case MASTER_VALUE:
422 return value->offset;
423 case PROFILE_VALUE:
424 return value->offset + sizeof(pidProfile_t) * getCurrentPidProfileIndex();
425 case PROFILE_RATE_VALUE:
426 return value->offset + sizeof(controlRateConfig_t) * getCurrentControlRateProfileIndex();
428 return 0;
431 void *cliGetValuePointer(const clivalue_t *value)
433 const pgRegistry_t* rec = pgFind(value->pgn);
434 return CONST_CAST(void *, rec->address + getValueOffset(value));
437 const void *cliGetDefaultPointer(const clivalue_t *value)
439 const pgRegistry_t* rec = pgFind(value->pgn);
440 return rec->address + getValueOffset(value);
443 static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
445 const pgRegistry_t *pg = pgFind(value->pgn);
446 #ifdef DEBUG
447 if (!pg) {
448 cliPrintLinef("VALUE %s ERROR", value->name);
449 return; // if it's not found, the pgn shouldn't be in the value table!
451 #endif
453 const char *format = "set %s = ";
454 const char *defaultFormat = "#set %s = ";
455 const int valueOffset = getValueOffset(value);
456 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
458 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
459 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
460 cliPrintf(defaultFormat, value->name);
461 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
462 cliPrintLinefeed();
464 cliPrintf(format, value->name);
465 printValuePointer(value, pg->copy + valueOffset, false);
466 cliPrintLinefeed();
470 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
472 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
473 const clivalue_t *value = &valueTable[i];
474 bufWriterFlush(cliWriter);
475 if ((value->type & VALUE_SECTION_MASK) == valueSection) {
476 dumpPgValue(value, dumpMask);
481 static void cliPrintVar(const clivalue_t *var, bool full)
483 const void *ptr = cliGetValuePointer(var);
485 printValuePointer(var, ptr, full);
488 static void cliPrintVarRange(const clivalue_t *var)
490 switch (var->type & VALUE_MODE_MASK) {
491 case (MODE_DIRECT): {
492 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
494 break;
495 case (MODE_LOOKUP): {
496 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
497 cliPrint("Allowed values: ");
498 bool firstEntry = true;
499 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
500 if (tableEntry->values[i]) {
501 if (!firstEntry) {
502 cliPrint(", ");
504 cliPrintf("%s", tableEntry->values[i]);
505 firstEntry = false;
508 cliPrintLinefeed();
510 break;
511 case (MODE_ARRAY): {
512 cliPrintLinef("Array length: %d", var->config.array.length);
515 break;
519 static void cliSetVar(const clivalue_t *var, const int16_t value)
521 void *ptr = cliGetValuePointer(var);
523 switch (var->type & VALUE_TYPE_MASK) {
524 case VAR_UINT8:
525 *(uint8_t *)ptr = value;
526 break;
528 case VAR_INT8:
529 *(int8_t *)ptr = value;
530 break;
532 case VAR_UINT16:
533 case VAR_INT16:
534 *(int16_t *)ptr = value;
535 break;
539 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
540 static void cliRepeat(char ch, uint8_t len)
542 for (int i = 0; i < len; i++) {
543 bufWriterAppend(cliWriter, ch);
545 cliPrintLinefeed();
547 #endif
549 static void cliPrompt(void)
551 cliPrint("\r\n# ");
554 static void cliShowParseError(void)
556 cliPrintLine("Parse error");
559 static void cliShowArgumentRangeError(char *name, int min, int max)
561 cliPrintLinef("%s not between %d and %d", name, min, max);
564 static const char *nextArg(const char *currentArg)
566 const char *ptr = strchr(currentArg, ' ');
567 while (ptr && *ptr == ' ') {
568 ptr++;
571 return ptr;
574 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
576 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
577 ptr = nextArg(ptr);
578 if (ptr) {
579 int val = atoi(ptr);
580 val = CHANNEL_VALUE_TO_STEP(val);
581 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
582 if (argIndex == 0) {
583 range->startStep = val;
584 } else {
585 range->endStep = val;
587 (*validArgumentCount)++;
592 return ptr;
595 // Check if a string's length is zero
596 static bool isEmpty(const char *string)
598 return (string == NULL || *string == '\0') ? true : false;
601 static void printRxFailsafe(uint8_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs)
603 // print out rxConfig failsafe settings
604 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
605 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
606 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
607 const bool equalsDefault = channelFailsafeConfig->mode == defaultChannelFailsafeConfig->mode
608 && channelFailsafeConfig->step == defaultChannelFailsafeConfig->step;
609 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
610 if (requireValue) {
611 const char *format = "rxfail %u %c %d";
612 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
613 channel,
614 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
615 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
617 cliDumpPrintLinef(dumpMask, equalsDefault, format,
618 channel,
619 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
620 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
622 } else {
623 const char *format = "rxfail %u %c";
624 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
625 channel,
626 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
628 cliDumpPrintLinef(dumpMask, equalsDefault, format,
629 channel,
630 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
636 static void cliRxFailsafe(char *cmdline)
638 uint8_t channel;
639 char buf[3];
641 if (isEmpty(cmdline)) {
642 // print out rxConfig failsafe settings
643 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
644 cliRxFailsafe(itoa(channel, buf, 10));
646 } else {
647 const char *ptr = cmdline;
648 channel = atoi(ptr++);
649 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
651 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
653 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
654 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
655 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
657 ptr = nextArg(ptr);
658 if (ptr) {
659 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
660 if (p) {
661 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
662 mode = rxFailsafeModesTable[type][requestedMode];
663 } else {
664 mode = RX_FAILSAFE_MODE_INVALID;
666 if (mode == RX_FAILSAFE_MODE_INVALID) {
667 cliShowParseError();
668 return;
671 requireValue = mode == RX_FAILSAFE_MODE_SET;
673 ptr = nextArg(ptr);
674 if (ptr) {
675 if (!requireValue) {
676 cliShowParseError();
677 return;
679 uint16_t value = atoi(ptr);
680 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
681 if (value > MAX_RXFAIL_RANGE_STEP) {
682 cliPrintLine("Value out of range");
683 return;
686 channelFailsafeConfig->step = value;
687 } else if (requireValue) {
688 cliShowParseError();
689 return;
691 channelFailsafeConfig->mode = mode;
694 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
696 // double use of cliPrintf below
697 // 1. acknowledge interpretation on command,
698 // 2. query current setting on single item,
700 if (requireValue) {
701 cliPrintLinef("rxfail %u %c %d",
702 channel,
703 modeCharacter,
704 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
706 } else {
707 cliPrintLinef("rxfail %u %c",
708 channel,
709 modeCharacter
712 } else {
713 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
718 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
720 const char *format = "aux %u %u %u %u %u %u";
721 // print out aux channel settings
722 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
723 const modeActivationCondition_t *mac = &modeActivationConditions[i];
724 bool equalsDefault = false;
725 if (defaultModeActivationConditions) {
726 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
727 equalsDefault = mac->modeId == macDefault->modeId
728 && mac->auxChannelIndex == macDefault->auxChannelIndex
729 && mac->range.startStep == macDefault->range.startStep
730 && mac->range.endStep == macDefault->range.endStep
731 && mac->modeLogic == macDefault->modeLogic;
732 const box_t *box = findBoxByBoxId(macDefault->modeId);
733 if (box) {
734 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
736 box->permanentId,
737 macDefault->auxChannelIndex,
738 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
739 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
740 macDefault->modeLogic
744 const box_t *box = findBoxByBoxId(mac->modeId);
745 if (box) {
746 cliDumpPrintLinef(dumpMask, equalsDefault, format,
748 box->permanentId,
749 mac->auxChannelIndex,
750 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
751 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
752 mac->modeLogic
758 static void cliAux(char *cmdline)
760 int i, val = 0;
761 const char *ptr;
763 if (isEmpty(cmdline)) {
764 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
765 } else {
766 ptr = cmdline;
767 i = atoi(ptr++);
768 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
769 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
770 uint8_t validArgumentCount = 0;
771 ptr = nextArg(ptr);
772 if (ptr) {
773 val = atoi(ptr);
774 const box_t *box = findBoxByPermanentId(val);
775 if (box) {
776 mac->modeId = box->boxId;
777 validArgumentCount++;
780 ptr = nextArg(ptr);
781 if (ptr) {
782 val = atoi(ptr);
783 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
784 mac->auxChannelIndex = val;
785 validArgumentCount++;
788 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
789 ptr = nextArg(ptr);
790 if (ptr) {
791 val = atoi(ptr);
792 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
793 mac->modeLogic = val;
794 validArgumentCount++;
797 if (validArgumentCount == 4) { // for backwards compatibility
798 mac->modeLogic = MODELOGIC_OR;
799 } else if (validArgumentCount != 5) {
800 memset(mac, 0, sizeof(modeActivationCondition_t));
802 cliPrintLinef( "aux %u %u %u %u %u %u",
804 mac->modeId,
805 mac->auxChannelIndex,
806 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
807 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
808 mac->modeLogic
810 } else {
811 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
816 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
818 const char *format = "serial %d %d %ld %ld %ld %ld";
819 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
820 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
821 continue;
823 bool equalsDefault = false;
824 if (serialConfigDefault) {
825 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
826 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
827 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
828 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
829 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
830 && serialConfig->portConfigs[i].blackbox_baudrateIndex == serialConfigDefault->portConfigs[i].blackbox_baudrateIndex;
831 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
832 serialConfigDefault->portConfigs[i].identifier,
833 serialConfigDefault->portConfigs[i].functionMask,
834 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
835 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
836 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
837 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
840 cliDumpPrintLinef(dumpMask, equalsDefault, format,
841 serialConfig->portConfigs[i].identifier,
842 serialConfig->portConfigs[i].functionMask,
843 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
844 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
845 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
846 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
851 static void cliSerial(char *cmdline)
853 if (isEmpty(cmdline)) {
854 printSerial(DUMP_MASTER, serialConfig(), NULL);
855 return;
857 serialPortConfig_t portConfig;
858 memset(&portConfig, 0 , sizeof(portConfig));
860 serialPortConfig_t *currentConfig;
862 uint8_t validArgumentCount = 0;
864 const char *ptr = cmdline;
866 int val = atoi(ptr++);
867 currentConfig = serialFindPortConfiguration(val);
868 if (currentConfig) {
869 portConfig.identifier = val;
870 validArgumentCount++;
873 ptr = nextArg(ptr);
874 if (ptr) {
875 val = atoi(ptr);
876 portConfig.functionMask = val & 0xFFFF;
877 validArgumentCount++;
880 for (int i = 0; i < 4; i ++) {
881 ptr = nextArg(ptr);
882 if (!ptr) {
883 break;
886 val = atoi(ptr);
888 uint8_t baudRateIndex = lookupBaudRateIndex(val);
889 if (baudRates[baudRateIndex] != (uint32_t) val) {
890 break;
893 switch (i) {
894 case 0:
895 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
896 continue;
898 portConfig.msp_baudrateIndex = baudRateIndex;
899 break;
900 case 1:
901 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
902 continue;
904 portConfig.gps_baudrateIndex = baudRateIndex;
905 break;
906 case 2:
907 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
908 continue;
910 portConfig.telemetry_baudrateIndex = baudRateIndex;
911 break;
912 case 3:
913 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
914 continue;
916 portConfig.blackbox_baudrateIndex = baudRateIndex;
917 break;
920 validArgumentCount++;
923 if (validArgumentCount < 6) {
924 cliShowParseError();
925 return;
928 memcpy(currentConfig, &portConfig, sizeof(portConfig));
931 #ifndef SKIP_SERIAL_PASSTHROUGH
932 #ifdef USE_PINIO
933 static void cbCtrlLine(void *context, uint16_t ctrl)
935 int pinioDtr = (int)(long)context;
937 pinioSet(pinioDtr, !(ctrl & CTRL_LINE_STATE_DTR));
939 #endif /* USE_PINIO */
941 static void cliSerialPassthrough(char *cmdline)
943 if (isEmpty(cmdline)) {
944 cliShowParseError();
945 return;
948 int id = -1;
949 uint32_t baud = 0;
950 bool enableBaudCb = false;
951 #ifdef USE_PINIO
952 int pinioDtr = 0;
953 #endif /* USE_PINIO */
954 unsigned mode = 0;
955 char *saveptr;
956 char* tok = strtok_r(cmdline, " ", &saveptr);
957 int index = 0;
959 while (tok != NULL) {
960 switch (index) {
961 case 0:
962 id = atoi(tok);
963 break;
964 case 1:
965 baud = atoi(tok);
966 break;
967 case 2:
968 if (strstr(tok, "rx") || strstr(tok, "RX"))
969 mode |= MODE_RX;
970 if (strstr(tok, "tx") || strstr(tok, "TX"))
971 mode |= MODE_TX;
972 break;
973 #ifdef USE_PINIO
974 case 3:
975 pinioDtr = atoi(tok);
976 break;
977 #endif /* USE_PINIO */
979 index++;
980 tok = strtok_r(NULL, " ", &saveptr);
983 if (baud == 0) {
984 enableBaudCb = true;
987 cliPrintf("Port %d ", id);
988 serialPort_t *passThroughPort;
989 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
990 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
991 if (enableBaudCb) {
992 // Set default baud
993 baud = 57600;
996 if (!mode) {
997 mode = MODE_RXTX;
1000 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
1001 baud, mode,
1002 SERIAL_NOT_INVERTED);
1003 if (!passThroughPort) {
1004 cliPrintLine("could not be opened.");
1005 return;
1008 if (enableBaudCb) {
1009 cliPrintf("opened, default baud = %d.\r\n", baud);
1010 } else {
1011 cliPrintf("opened, baud = %d.\r\n", baud);
1013 } else {
1014 passThroughPort = passThroughPortUsage->serialPort;
1015 // If the user supplied a mode, override the port's mode, otherwise
1016 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1017 // Set the baud rate if specified
1018 if (baud) {
1019 cliPrintf("already open, setting baud = %d.\n\r", baud);
1020 serialSetBaudRate(passThroughPort, baud);
1021 } else {
1022 cliPrintf("already open, baud = %d.\n\r", passThroughPort->baudRate);
1025 if (mode && passThroughPort->mode != mode) {
1026 cliPrintf("Mode changed from %d to %d.\r\n",
1027 passThroughPort->mode, mode);
1028 serialSetMode(passThroughPort, mode);
1031 // If this port has a rx callback associated we need to remove it now.
1032 // Otherwise no data will be pushed in the serial port buffer!
1033 if (passThroughPort->rxCallback) {
1034 passThroughPort->rxCallback = 0;
1038 // If no baud rate is specified allow to be set via USB
1039 if (enableBaudCb) {
1040 cliPrintLine("Baud rate change over USB enabled.");
1041 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1042 // baud rate over USB without setting it using the serialpassthrough command
1043 serialSetBaudRateCb(cliPort, serialSetBaudRate, passThroughPort);
1046 cliPrintLine("Forwarding, power cycle to exit.");
1048 #ifdef USE_PINIO
1049 // Register control line state callback
1050 if (pinioDtr) {
1051 serialSetCtrlLineStateCb(cliPort, cbCtrlLine, (void *)(intptr_t)(pinioDtr - 1));
1053 #endif /* USE_PINIO */
1055 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
1057 #endif
1059 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
1061 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1062 // print out adjustment ranges channel settings
1063 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1064 const adjustmentRange_t *ar = &adjustmentRanges[i];
1065 bool equalsDefault = false;
1066 if (defaultAdjustmentRanges) {
1067 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1068 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
1069 && ar->range.startStep == arDefault->range.startStep
1070 && ar->range.endStep == arDefault->range.endStep
1071 && ar->adjustmentFunction == arDefault->adjustmentFunction
1072 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
1073 && ar->adjustmentIndex == arDefault->adjustmentIndex
1074 && ar->adjustmentCenter == arDefault->adjustmentCenter
1075 && ar->adjustmentScale == arDefault->adjustmentScale;
1076 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1078 arDefault->adjustmentIndex,
1079 arDefault->auxChannelIndex,
1080 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1081 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1082 arDefault->adjustmentFunction,
1083 arDefault->auxSwitchChannelIndex,
1084 arDefault->adjustmentCenter,
1085 arDefault->adjustmentScale
1088 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1090 ar->adjustmentIndex,
1091 ar->auxChannelIndex,
1092 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1093 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1094 ar->adjustmentFunction,
1095 ar->auxSwitchChannelIndex,
1096 ar->adjustmentCenter,
1097 ar->adjustmentScale
1102 static void cliAdjustmentRange(char *cmdline)
1104 int i, val = 0;
1105 const char *ptr;
1107 if (isEmpty(cmdline)) {
1108 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
1109 } else {
1110 ptr = cmdline;
1111 i = atoi(ptr++);
1112 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1113 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1114 uint8_t validArgumentCount = 0;
1116 ptr = nextArg(ptr);
1117 if (ptr) {
1118 val = atoi(ptr);
1119 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1120 ar->adjustmentIndex = val;
1121 validArgumentCount++;
1124 ptr = nextArg(ptr);
1125 if (ptr) {
1126 val = atoi(ptr);
1127 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1128 ar->auxChannelIndex = val;
1129 validArgumentCount++;
1133 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1135 ptr = nextArg(ptr);
1136 if (ptr) {
1137 val = atoi(ptr);
1138 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1139 ar->adjustmentFunction = val;
1140 validArgumentCount++;
1143 ptr = nextArg(ptr);
1144 if (ptr) {
1145 val = atoi(ptr);
1146 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1147 ar->auxSwitchChannelIndex = val;
1148 validArgumentCount++;
1152 if (validArgumentCount != 6) {
1153 memset(ar, 0, sizeof(adjustmentRange_t));
1154 cliShowParseError();
1157 // Optional arguments
1158 ar->adjustmentCenter = 0;
1159 ar->adjustmentScale = 0;
1161 ptr = nextArg(ptr);
1162 if (ptr) {
1163 val = atoi(ptr);
1164 ar->adjustmentCenter = val;
1165 validArgumentCount++;
1167 ptr = nextArg(ptr);
1168 if (ptr) {
1169 val = atoi(ptr);
1170 ar->adjustmentScale = val;
1171 validArgumentCount++;
1173 } else {
1174 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1179 #ifndef USE_QUAD_MIXER_ONLY
1180 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer)
1182 const char *format = "mmix %d %s %s %s %s";
1183 char buf0[FTOA_BUFFER_LENGTH];
1184 char buf1[FTOA_BUFFER_LENGTH];
1185 char buf2[FTOA_BUFFER_LENGTH];
1186 char buf3[FTOA_BUFFER_LENGTH];
1187 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1188 if (customMotorMixer[i].throttle == 0.0f)
1189 break;
1190 const float thr = customMotorMixer[i].throttle;
1191 const float roll = customMotorMixer[i].roll;
1192 const float pitch = customMotorMixer[i].pitch;
1193 const float yaw = customMotorMixer[i].yaw;
1194 bool equalsDefault = false;
1195 if (defaultCustomMotorMixer) {
1196 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1197 const float rollDefault = defaultCustomMotorMixer[i].roll;
1198 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1199 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1200 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1202 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1204 ftoa(thrDefault, buf0),
1205 ftoa(rollDefault, buf1),
1206 ftoa(pitchDefault, buf2),
1207 ftoa(yawDefault, buf3));
1209 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1211 ftoa(thr, buf0),
1212 ftoa(roll, buf1),
1213 ftoa(pitch, buf2),
1214 ftoa(yaw, buf3));
1217 #endif // USE_QUAD_MIXER_ONLY
1219 static void cliMotorMix(char *cmdline)
1221 #ifdef USE_QUAD_MIXER_ONLY
1222 UNUSED(cmdline);
1223 #else
1224 int check = 0;
1225 uint8_t len;
1226 const char *ptr;
1228 if (isEmpty(cmdline)) {
1229 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1230 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1231 // erase custom mixer
1232 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1233 customMotorMixerMutable(i)->throttle = 0.0f;
1235 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1236 ptr = nextArg(cmdline);
1237 if (ptr) {
1238 len = strlen(ptr);
1239 for (uint32_t i = 0; ; i++) {
1240 if (mixerNames[i] == NULL) {
1241 cliPrintLine("Invalid name");
1242 break;
1244 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1245 mixerLoadMix(i, customMotorMixerMutable(0));
1246 cliPrintLinef("Loaded %s", mixerNames[i]);
1247 cliMotorMix("");
1248 break;
1252 } else {
1253 ptr = cmdline;
1254 uint32_t i = atoi(ptr); // get motor number
1255 if (i < MAX_SUPPORTED_MOTORS) {
1256 ptr = nextArg(ptr);
1257 if (ptr) {
1258 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1259 check++;
1261 ptr = nextArg(ptr);
1262 if (ptr) {
1263 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1264 check++;
1266 ptr = nextArg(ptr);
1267 if (ptr) {
1268 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1269 check++;
1271 ptr = nextArg(ptr);
1272 if (ptr) {
1273 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1274 check++;
1276 if (check != 4) {
1277 cliShowParseError();
1278 } else {
1279 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1281 } else {
1282 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1285 #endif
1288 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1290 const char *format = "rxrange %u %u %u";
1291 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1292 bool equalsDefault = false;
1293 if (defaultChannelRangeConfigs) {
1294 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1295 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1296 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1298 defaultChannelRangeConfigs[i].min,
1299 defaultChannelRangeConfigs[i].max
1302 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1304 channelRangeConfigs[i].min,
1305 channelRangeConfigs[i].max
1310 static void cliRxRange(char *cmdline)
1312 int i, validArgumentCount = 0;
1313 const char *ptr;
1315 if (isEmpty(cmdline)) {
1316 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1317 } else if (strcasecmp(cmdline, "reset") == 0) {
1318 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1319 } else {
1320 ptr = cmdline;
1321 i = atoi(ptr);
1322 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1323 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1325 ptr = nextArg(ptr);
1326 if (ptr) {
1327 rangeMin = atoi(ptr);
1328 validArgumentCount++;
1331 ptr = nextArg(ptr);
1332 if (ptr) {
1333 rangeMax = atoi(ptr);
1334 validArgumentCount++;
1337 if (validArgumentCount != 2) {
1338 cliShowParseError();
1339 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1340 cliShowParseError();
1341 } else {
1342 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1343 channelRangeConfig->min = rangeMin;
1344 channelRangeConfig->max = rangeMax;
1346 } else {
1347 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1352 #ifdef USE_LED_STRIP
1353 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1355 const char *format = "led %u %s";
1356 char ledConfigBuffer[20];
1357 char ledConfigDefaultBuffer[20];
1358 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1359 ledConfig_t ledConfig = ledConfigs[i];
1360 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1361 bool equalsDefault = false;
1362 if (defaultLedConfigs) {
1363 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1364 equalsDefault = ledConfig == ledConfigDefault;
1365 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1366 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1368 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1372 static void cliLed(char *cmdline)
1374 int i;
1375 const char *ptr;
1377 if (isEmpty(cmdline)) {
1378 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1379 } else {
1380 ptr = cmdline;
1381 i = atoi(ptr);
1382 if (i < LED_MAX_STRIP_LENGTH) {
1383 ptr = nextArg(cmdline);
1384 if (!parseLedStripConfig(i, ptr)) {
1385 cliShowParseError();
1387 } else {
1388 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1393 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1395 const char *format = "color %u %d,%u,%u";
1396 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1397 const hsvColor_t *color = &colors[i];
1398 bool equalsDefault = false;
1399 if (defaultColors) {
1400 const hsvColor_t *colorDefault = &defaultColors[i];
1401 equalsDefault = color->h == colorDefault->h
1402 && color->s == colorDefault->s
1403 && color->v == colorDefault->v;
1404 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1406 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1410 static void cliColor(char *cmdline)
1412 if (isEmpty(cmdline)) {
1413 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1414 } else {
1415 const char *ptr = cmdline;
1416 const int i = atoi(ptr);
1417 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1418 ptr = nextArg(cmdline);
1419 if (!parseColor(i, ptr)) {
1420 cliShowParseError();
1422 } else {
1423 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1428 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1430 const char *format = "mode_color %u %u %u";
1431 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1432 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1433 int colorIndex = ledStripConfig->modeColors[i].color[j];
1434 bool equalsDefault = false;
1435 if (defaultLedStripConfig) {
1436 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1437 equalsDefault = colorIndex == colorIndexDefault;
1438 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1440 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1444 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1445 const int colorIndex = ledStripConfig->specialColors.color[j];
1446 bool equalsDefault = false;
1447 if (defaultLedStripConfig) {
1448 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1449 equalsDefault = colorIndex == colorIndexDefault;
1450 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1452 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1455 const int ledStripAuxChannel = ledStripConfig->ledstrip_aux_channel;
1456 bool equalsDefault = false;
1457 if (defaultLedStripConfig) {
1458 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1459 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1460 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1462 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1465 static void cliModeColor(char *cmdline)
1467 if (isEmpty(cmdline)) {
1468 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1469 } else {
1470 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1471 int args[ARGS_COUNT];
1472 int argNo = 0;
1473 char *saveptr;
1474 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1475 while (ptr && argNo < ARGS_COUNT) {
1476 args[argNo++] = atoi(ptr);
1477 ptr = strtok_r(NULL, " ", &saveptr);
1480 if (ptr != NULL || argNo != ARGS_COUNT) {
1481 cliShowParseError();
1482 return;
1485 int modeIdx = args[MODE];
1486 int funIdx = args[FUNCTION];
1487 int color = args[COLOR];
1488 if (!setModeColor(modeIdx, funIdx, color)) {
1489 cliShowParseError();
1490 return;
1492 // values are validated
1493 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1496 #endif
1498 #ifdef USE_SERVOS
1499 static void printServo(uint8_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams)
1501 // print out servo settings
1502 const char *format = "servo %u %d %d %d %d %d";
1503 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1504 const servoParam_t *servoConf = &servoParams[i];
1505 bool equalsDefault = false;
1506 if (defaultServoParams) {
1507 const servoParam_t *defaultServoConf = &defaultServoParams[i];
1508 equalsDefault = servoConf->min == defaultServoConf->min
1509 && servoConf->max == defaultServoConf->max
1510 && servoConf->middle == defaultServoConf->middle
1511 && servoConf->rate == defaultServoConf->rate
1512 && servoConf->forwardFromChannel == defaultServoConf->forwardFromChannel;
1513 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1515 defaultServoConf->min,
1516 defaultServoConf->max,
1517 defaultServoConf->middle,
1518 defaultServoConf->rate,
1519 defaultServoConf->forwardFromChannel
1522 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1524 servoConf->min,
1525 servoConf->max,
1526 servoConf->middle,
1527 servoConf->rate,
1528 servoConf->forwardFromChannel
1531 // print servo directions
1532 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1533 const char *format = "smix reverse %d %d r";
1534 const servoParam_t *servoConf = &servoParams[i];
1535 const servoParam_t *servoConfDefault = &defaultServoParams[i];
1536 if (defaultServoParams) {
1537 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
1538 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1539 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
1540 if (servoConfDefault->reversedSources & (1 << channel)) {
1541 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
1543 if (servoConf->reversedSources & (1 << channel)) {
1544 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
1547 } else {
1548 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1549 if (servoConf->reversedSources & (1 << channel)) {
1550 cliDumpPrintLinef(dumpMask, true, format, i , channel);
1557 static void cliServo(char *cmdline)
1559 enum { SERVO_ARGUMENT_COUNT = 6 };
1560 int16_t arguments[SERVO_ARGUMENT_COUNT];
1562 servoParam_t *servo;
1564 int i;
1565 char *ptr;
1567 if (isEmpty(cmdline)) {
1568 printServo(DUMP_MASTER, servoParams(0), NULL);
1569 } else {
1570 int validArgumentCount = 0;
1572 ptr = cmdline;
1574 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1576 // If command line doesn't fit the format, don't modify the config
1577 while (*ptr) {
1578 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1579 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1580 cliShowParseError();
1581 return;
1584 arguments[validArgumentCount++] = atoi(ptr);
1586 do {
1587 ptr++;
1588 } while (*ptr >= '0' && *ptr <= '9');
1589 } else if (*ptr == ' ') {
1590 ptr++;
1591 } else {
1592 cliShowParseError();
1593 return;
1597 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
1599 i = arguments[INDEX];
1601 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1602 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1603 cliShowParseError();
1604 return;
1607 servo = servoParamsMutable(i);
1609 if (
1610 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
1611 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
1612 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1613 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1614 arguments[RATE] < -100 || arguments[RATE] > 100 ||
1615 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1617 cliShowParseError();
1618 return;
1621 servo->min = arguments[MIN];
1622 servo->max = arguments[MAX];
1623 servo->middle = arguments[MIDDLE];
1624 servo->rate = arguments[RATE];
1625 servo->forwardFromChannel = arguments[FORWARD];
1628 #endif
1630 #ifdef USE_SERVOS
1631 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1633 const char *format = "smix %d %d %d %d %d %d %d %d";
1634 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1635 const servoMixer_t customServoMixer = customServoMixers[i];
1636 if (customServoMixer.rate == 0) {
1637 break;
1640 bool equalsDefault = false;
1641 if (defaultCustomServoMixers) {
1642 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1643 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1644 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1645 && customServoMixer.rate == customServoMixerDefault.rate
1646 && customServoMixer.speed == customServoMixerDefault.speed
1647 && customServoMixer.min == customServoMixerDefault.min
1648 && customServoMixer.max == customServoMixerDefault.max
1649 && customServoMixer.box == customServoMixerDefault.box;
1651 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1653 customServoMixerDefault.targetChannel,
1654 customServoMixerDefault.inputSource,
1655 customServoMixerDefault.rate,
1656 customServoMixerDefault.speed,
1657 customServoMixerDefault.min,
1658 customServoMixerDefault.max,
1659 customServoMixerDefault.box
1662 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1664 customServoMixer.targetChannel,
1665 customServoMixer.inputSource,
1666 customServoMixer.rate,
1667 customServoMixer.speed,
1668 customServoMixer.min,
1669 customServoMixer.max,
1670 customServoMixer.box
1674 cliPrintLinefeed();
1677 static void cliServoMix(char *cmdline)
1679 int args[8], check = 0;
1680 int len = strlen(cmdline);
1682 if (len == 0) {
1683 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
1684 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1685 // erase custom mixer
1686 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1687 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1688 servoParamsMutable(i)->reversedSources = 0;
1690 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1691 const char *ptr = nextArg(cmdline);
1692 if (ptr) {
1693 len = strlen(ptr);
1694 for (uint32_t i = 0; ; i++) {
1695 if (mixerNames[i] == NULL) {
1696 cliPrintLine("Invalid name");
1697 break;
1699 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1700 servoMixerLoadMix(i);
1701 cliPrintLinef("Loaded %s", mixerNames[i]);
1702 cliServoMix("");
1703 break;
1707 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
1708 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
1709 char *ptr = strchr(cmdline, ' ');
1711 len = strlen(ptr);
1712 if (len == 0) {
1713 cliPrintf("s");
1714 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1715 cliPrintf("\ti%d", inputSource);
1716 cliPrintLinefeed();
1718 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
1719 cliPrintf("%d", servoIndex);
1720 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1721 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
1722 cliPrintLinefeed();
1724 return;
1727 char *saveptr;
1728 ptr = strtok_r(ptr, " ", &saveptr);
1729 while (ptr != NULL && check < ARGS_COUNT - 1) {
1730 args[check++] = atoi(ptr);
1731 ptr = strtok_r(NULL, " ", &saveptr);
1734 if (ptr == NULL || check != ARGS_COUNT - 1) {
1735 cliShowParseError();
1736 return;
1739 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
1740 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
1741 && (*ptr == 'r' || *ptr == 'n')) {
1742 if (*ptr == 'r')
1743 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
1744 else
1745 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
1746 } else
1747 cliShowParseError();
1749 cliServoMix("reverse");
1750 } else {
1751 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
1752 char *saveptr;
1753 char *ptr = strtok_r(cmdline, " ", &saveptr);
1754 while (ptr != NULL && check < ARGS_COUNT) {
1755 args[check++] = atoi(ptr);
1756 ptr = strtok_r(NULL, " ", &saveptr);
1759 if (ptr != NULL || check != ARGS_COUNT) {
1760 cliShowParseError();
1761 return;
1764 int32_t i = args[RULE];
1765 if (i >= 0 && i < MAX_SERVO_RULES &&
1766 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1767 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1768 args[RATE] >= -100 && args[RATE] <= 100 &&
1769 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1770 args[MIN] >= 0 && args[MIN] <= 100 &&
1771 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
1772 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
1773 customServoMixersMutable(i)->targetChannel = args[TARGET];
1774 customServoMixersMutable(i)->inputSource = args[INPUT];
1775 customServoMixersMutable(i)->rate = args[RATE];
1776 customServoMixersMutable(i)->speed = args[SPEED];
1777 customServoMixersMutable(i)->min = args[MIN];
1778 customServoMixersMutable(i)->max = args[MAX];
1779 customServoMixersMutable(i)->box = args[BOX];
1780 cliServoMix("");
1781 } else {
1782 cliShowParseError();
1786 #endif
1788 #ifdef USE_SDCARD
1790 static void cliWriteBytes(const uint8_t *buffer, int count)
1792 while (count > 0) {
1793 cliWrite(*buffer);
1794 buffer++;
1795 count--;
1799 static void cliSdInfo(char *cmdline)
1801 UNUSED(cmdline);
1803 cliPrint("SD card: ");
1805 if (!sdcard_isInserted()) {
1806 cliPrintLine("None inserted");
1807 return;
1810 if (!sdcard_isInitialized()) {
1811 cliPrintLine("Startup failed");
1812 return;
1815 const sdcardMetadata_t *metadata = sdcard_getMetadata();
1817 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1818 metadata->manufacturerID,
1819 metadata->numBlocks / 2, /* One block is half a kB */
1820 metadata->productionMonth,
1821 metadata->productionYear,
1822 metadata->productRevisionMajor,
1823 metadata->productRevisionMinor
1826 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
1828 cliPrint("'\r\n" "Filesystem: ");
1830 switch (afatfs_getFilesystemState()) {
1831 case AFATFS_FILESYSTEM_STATE_READY:
1832 cliPrint("Ready");
1833 break;
1834 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
1835 cliPrint("Initializing");
1836 break;
1837 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
1838 case AFATFS_FILESYSTEM_STATE_FATAL:
1839 cliPrint("Fatal");
1841 switch (afatfs_getLastError()) {
1842 case AFATFS_ERROR_BAD_MBR:
1843 cliPrint(" - no FAT MBR partitions");
1844 break;
1845 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
1846 cliPrint(" - bad FAT header");
1847 break;
1848 case AFATFS_ERROR_GENERIC:
1849 case AFATFS_ERROR_NONE:
1850 ; // Nothing more detailed to print
1851 break;
1853 break;
1855 cliPrintLinefeed();
1858 #endif
1860 #ifdef USE_FLASHFS
1862 static void cliFlashInfo(char *cmdline)
1864 const flashGeometry_t *layout = flashfsGetGeometry();
1866 UNUSED(cmdline);
1868 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
1869 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
1873 static void cliFlashErase(char *cmdline)
1875 UNUSED(cmdline);
1877 #ifndef MINIMAL_CLI
1878 uint32_t i = 0;
1879 cliPrintLine("Erasing, please wait ... ");
1880 #else
1881 cliPrintLine("Erasing,");
1882 #endif
1884 bufWriterFlush(cliWriter);
1885 flashfsEraseCompletely();
1887 while (!flashfsIsReady()) {
1888 #ifndef MINIMAL_CLI
1889 cliPrintf(".");
1890 if (i++ > 120) {
1891 i=0;
1892 cliPrintLinefeed();
1895 bufWriterFlush(cliWriter);
1896 #endif
1897 delay(100);
1899 beeper(BEEPER_BLACKBOX_ERASE);
1900 cliPrintLinefeed();
1901 cliPrintLine("Done.");
1904 #ifdef USE_FLASH_TOOLS
1906 static void cliFlashWrite(char *cmdline)
1908 const uint32_t address = atoi(cmdline);
1909 const char *text = strchr(cmdline, ' ');
1911 if (!text) {
1912 cliShowParseError();
1913 } else {
1914 flashfsSeekAbs(address);
1915 flashfsWrite((uint8_t*)text, strlen(text), true);
1916 flashfsFlushSync();
1918 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
1922 static void cliFlashRead(char *cmdline)
1924 uint32_t address = atoi(cmdline);
1926 const char *nextArg = strchr(cmdline, ' ');
1928 if (!nextArg) {
1929 cliShowParseError();
1930 } else {
1931 uint32_t length = atoi(nextArg);
1933 cliPrintLinef("Reading %u bytes at %u:", length, address);
1935 uint8_t buffer[32];
1936 while (length > 0) {
1937 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
1939 for (int i = 0; i < bytesRead; i++) {
1940 cliWrite(buffer[i]);
1943 length -= bytesRead;
1944 address += bytesRead;
1946 if (bytesRead == 0) {
1947 //Assume we reached the end of the volume or something fatal happened
1948 break;
1951 cliPrintLinefeed();
1955 #endif
1956 #endif
1958 #ifdef USE_VTX_CONTROL
1959 static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault)
1961 // print out vtx channel settings
1962 const char *format = "vtx %u %u %u %u %u %u";
1963 bool equalsDefault = false;
1964 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
1965 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
1966 if (vtxConfigDefault) {
1967 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
1968 equalsDefault = cac->auxChannelIndex == cacDefault->auxChannelIndex
1969 && cac->band == cacDefault->band
1970 && cac->channel == cacDefault->channel
1971 && cac->range.startStep == cacDefault->range.startStep
1972 && cac->range.endStep == cacDefault->range.endStep;
1973 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1975 cacDefault->auxChannelIndex,
1976 cacDefault->band,
1977 cacDefault->channel,
1978 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
1979 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
1982 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1984 cac->auxChannelIndex,
1985 cac->band,
1986 cac->channel,
1987 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
1988 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
1993 static void cliVtx(char *cmdline)
1995 int i, val = 0;
1996 const char *ptr;
1998 if (isEmpty(cmdline)) {
1999 printVtx(DUMP_MASTER, vtxConfig(), NULL);
2000 } else {
2001 ptr = cmdline;
2002 i = atoi(ptr++);
2003 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2004 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2005 uint8_t validArgumentCount = 0;
2006 ptr = nextArg(ptr);
2007 if (ptr) {
2008 val = atoi(ptr);
2009 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2010 cac->auxChannelIndex = val;
2011 validArgumentCount++;
2014 ptr = nextArg(ptr);
2015 if (ptr) {
2016 val = atoi(ptr);
2017 // FIXME Use VTX API to get min/max
2018 if (val >= VTX_SETTINGS_MIN_BAND && val <= VTX_SETTINGS_MAX_BAND) {
2019 cac->band = val;
2020 validArgumentCount++;
2023 ptr = nextArg(ptr);
2024 if (ptr) {
2025 val = atoi(ptr);
2026 // FIXME Use VTX API to get min/max
2027 if (val >= VTX_SETTINGS_MIN_CHANNEL && val <= VTX_SETTINGS_MAX_CHANNEL) {
2028 cac->channel = val;
2029 validArgumentCount++;
2032 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2034 if (validArgumentCount != 5) {
2035 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2037 } else {
2038 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2043 #endif // VTX_CONTROL
2045 static void printName(uint8_t dumpMask, const pilotConfig_t *pilotConfig)
2047 const bool equalsDefault = strlen(pilotConfig->name) == 0;
2048 cliDumpPrintLinef(dumpMask, equalsDefault, "name %s", equalsDefault ? emptyName : pilotConfig->name);
2051 static void cliName(char *cmdline)
2053 const unsigned int len = strlen(cmdline);
2054 if (len > 0) {
2055 memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
2056 if (strncmp(cmdline, emptyName, len)) {
2057 strncpy(pilotConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
2060 printName(DUMP_MASTER, pilotConfig());
2063 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2065 const uint32_t mask = featureConfig->enabledFeatures;
2066 const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2067 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
2068 if (strcmp(featureNames[i], emptryString) != 0) { //Skip unused
2069 const char *format = "feature -%s";
2070 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2071 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2074 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
2075 if (strcmp(featureNames[i], emptryString) != 0) { //Skip unused
2076 const char *format = "feature %s";
2077 if (defaultMask & (1 << i)) {
2078 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2080 if (mask & (1 << i)) {
2081 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2087 static void cliFeature(char *cmdline)
2089 uint32_t len = strlen(cmdline);
2090 uint32_t mask = featureMask();
2092 if (len == 0) {
2093 cliPrint("Enabled: ");
2094 for (uint32_t i = 0; ; i++) {
2095 if (featureNames[i] == NULL)
2096 break;
2097 if (mask & (1 << i))
2098 cliPrintf("%s ", featureNames[i]);
2100 cliPrintLinefeed();
2101 } else if (strncasecmp(cmdline, "list", len) == 0) {
2102 cliPrint("Available:");
2103 for (uint32_t i = 0; ; i++) {
2104 if (featureNames[i] == NULL)
2105 break;
2106 if (strcmp(featureNames[i], emptryString) != 0) //Skip unused
2107 cliPrintf(" %s", featureNames[i]);
2109 cliPrintLinefeed();
2110 return;
2111 } else {
2112 bool remove = false;
2113 if (cmdline[0] == '-') {
2114 // remove feature
2115 remove = true;
2116 cmdline++; // skip over -
2117 len--;
2120 for (uint32_t i = 0; ; i++) {
2121 if (featureNames[i] == NULL) {
2122 cliPrintLine("Invalid name");
2123 break;
2126 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
2128 mask = 1 << i;
2129 #ifndef USE_GPS
2130 if (mask & FEATURE_GPS) {
2131 cliPrintLine("unavailable");
2132 break;
2134 #endif
2135 #ifndef USE_RANGEFINDER
2136 if (mask & FEATURE_RANGEFINDER) {
2137 cliPrintLine("unavailable");
2138 break;
2140 #endif
2141 if (remove) {
2142 featureClear(mask);
2143 cliPrint("Disabled");
2144 } else {
2145 featureSet(mask);
2146 cliPrint("Enabled");
2148 cliPrintLinef(" %s", featureNames[i]);
2149 break;
2155 #ifdef USE_BEEPER
2156 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
2158 const uint8_t beeperCount = beeperTableEntryCount();
2159 const uint32_t mask = beeperConfig->beeper_off_flags;
2160 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
2161 for (int32_t i = 0; i < beeperCount - 2; i++) {
2162 const char *formatOff = "beeper -%s";
2163 const char *formatOn = "beeper %s";
2164 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
2165 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & beeperModeMask, mask & beeperModeMask ? formatOn : formatOff, beeperNameForTableIndex(i));
2166 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & beeperModeMask, mask & beeperModeMask ? formatOff : formatOn, beeperNameForTableIndex(i));
2170 static void cliBeeper(char *cmdline)
2172 uint32_t len = strlen(cmdline);
2173 uint8_t beeperCount = beeperTableEntryCount();
2174 uint32_t mask = getBeeperOffMask();
2176 if (len == 0) {
2177 cliPrintf("Disabled:");
2178 for (int32_t i = 0; ; i++) {
2179 if (i == beeperCount - 2) {
2180 if (mask == 0)
2181 cliPrint(" none");
2182 break;
2185 if (mask & beeperModeMaskForTableIndex(i))
2186 cliPrintf(" %s", beeperNameForTableIndex(i));
2188 cliPrintLinefeed();
2189 } else if (strncasecmp(cmdline, "list", len) == 0) {
2190 cliPrint("Available:");
2191 for (uint32_t i = 0; i < beeperCount; i++)
2192 cliPrintf(" %s", beeperNameForTableIndex(i));
2193 cliPrintLinefeed();
2194 return;
2195 } else {
2196 bool remove = false;
2197 if (cmdline[0] == '-') {
2198 remove = true; // this is for beeper OFF condition
2199 cmdline++;
2200 len--;
2203 for (uint32_t i = 0; ; i++) {
2204 if (i == beeperCount) {
2205 cliPrintLine("Invalid name");
2206 break;
2208 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
2209 if (remove) { // beeper off
2210 if (i == BEEPER_ALL-1)
2211 beeperOffSetAll(beeperCount-2);
2212 else
2213 if (i == BEEPER_PREFERENCE-1)
2214 setBeeperOffMask(getPreferredBeeperOffMask());
2215 else {
2216 beeperOffSet(beeperModeMaskForTableIndex(i));
2218 cliPrint("Disabled");
2220 else { // beeper on
2221 if (i == BEEPER_ALL-1)
2222 beeperOffClearAll();
2223 else
2224 if (i == BEEPER_PREFERENCE-1)
2225 setPreferredBeeperOffMask(getBeeperOffMask());
2226 else {
2227 beeperOffClear(beeperModeMaskForTableIndex(i));
2229 cliPrint("Enabled");
2231 cliPrintLinef(" %s", beeperNameForTableIndex(i));
2232 break;
2237 #endif
2239 void cliFrSkyBind(char *cmdline){
2240 UNUSED(cmdline);
2241 switch (rxConfig()->rx_spi_protocol) {
2242 #ifdef USE_RX_FRSKY_SPI
2243 case RX_SPI_FRSKY_D:
2244 case RX_SPI_FRSKY_X:
2245 frSkySpiBind();
2247 cliPrint("Binding...");
2249 break;
2250 #endif
2251 default:
2252 cliPrint("Not supported.");
2254 break;
2258 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
2260 bool equalsDefault = true;
2261 char buf[16];
2262 char bufDefault[16];
2263 uint32_t i;
2264 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2265 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
2266 if (defaultRxConfig) {
2267 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
2268 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
2271 buf[i] = '\0';
2273 const char *formatMap = "map %s";
2274 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
2275 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
2279 static void cliMap(char *cmdline)
2281 uint32_t i;
2282 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
2284 uint32_t len = strlen(cmdline);
2285 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
2287 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2288 buf[i] = toupper((unsigned char)cmdline[i]);
2290 buf[i] = '\0';
2292 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2293 buf[i] = toupper((unsigned char)cmdline[i]);
2295 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
2296 continue;
2298 cliShowParseError();
2299 return;
2301 parseRcChannels(buf, rxConfigMutable());
2302 } else if (len > 0) {
2303 cliShowParseError();
2304 return;
2307 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2308 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
2311 buf[i] = '\0';
2312 cliPrintLinef("map %s", buf);
2315 static char *checkCommand(char *cmdLine, const char *command)
2317 if (!strncasecmp(cmdLine, command, strlen(command)) // command names match
2318 && (isspace((unsigned)cmdLine[strlen(command)]) || cmdLine[strlen(command)] == 0)) {
2319 return cmdLine + strlen(command) + 1;
2320 } else {
2321 return 0;
2325 static void cliRebootEx(bool bootLoader)
2327 cliPrint("\r\nRebooting");
2328 bufWriterFlush(cliWriter);
2329 waitForSerialPortToFinishTransmitting(cliPort);
2330 stopPwmAllMotors();
2331 if (bootLoader) {
2332 systemResetToBootloader();
2333 return;
2335 systemReset();
2338 static void cliReboot(void)
2340 cliRebootEx(false);
2343 static void cliBootloader(char *cmdLine)
2345 UNUSED(cmdLine);
2347 cliPrintHashLine("restarting in bootloader mode");
2348 cliRebootEx(true);
2351 static void cliExit(char *cmdline)
2353 UNUSED(cmdline);
2355 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2356 bufWriterFlush(cliWriter);
2358 *cliBuffer = '\0';
2359 bufferIndex = 0;
2360 cliMode = 0;
2361 // incase a motor was left running during motortest, clear it here
2362 mixerResetDisarmedMotors();
2363 cliReboot();
2365 cliWriter = NULL;
2368 #ifdef USE_GPS
2369 static void cliGpsPassthrough(char *cmdline)
2371 UNUSED(cmdline);
2373 gpsEnablePassthrough(cliPort);
2375 #endif
2377 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
2378 static void cliPrintGyroRegisters(uint8_t whichSensor)
2380 tfp_printf("# WHO_AM_I 0x%X\r\n", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
2381 tfp_printf("# CONFIG 0x%X\r\n", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
2382 tfp_printf("# GYRO_CONFIG 0x%X\r\n", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
2385 static void cliDumpGyroRegisters(char *cmdline)
2387 #ifdef USE_DUAL_GYRO
2388 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
2389 tfp_printf("\r\n# Gyro 1\r\n");
2390 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
2392 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
2393 tfp_printf("\r\n# Gyro 2\r\n");
2394 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
2396 #else
2397 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
2398 #endif // USE_DUAL_GYRO
2399 UNUSED(cmdline);
2401 #endif
2404 static int parseOutputIndex(char *pch, bool allowAllEscs) {
2405 int outputIndex = atoi(pch);
2406 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
2407 tfp_printf("Using output %d.\r\n", outputIndex);
2408 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
2409 tfp_printf("Using all outputs.\r\n");
2410 } else {
2411 tfp_printf("Invalid output number. Range: 0 %d.\r\n", getMotorCount() - 1);
2413 return -1;
2416 return outputIndex;
2419 #if defined(USE_DSHOT)
2420 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2422 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
2423 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
2424 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
2426 enum {
2427 ESC_INFO_KISS_V1,
2428 ESC_INFO_KISS_V2,
2429 ESC_INFO_BLHELI32
2432 #define ESC_INFO_VERSION_POSITION 12
2434 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
2436 bool escInfoReceived = false;
2437 if (bytesRead > ESC_INFO_VERSION_POSITION) {
2438 uint8_t escInfoVersion;
2439 uint8_t frameLength;
2440 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
2441 escInfoVersion = ESC_INFO_BLHELI32;
2442 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
2443 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
2444 escInfoVersion = ESC_INFO_KISS_V2;
2445 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
2446 } else {
2447 escInfoVersion = ESC_INFO_KISS_V1;
2448 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
2451 if (bytesRead == frameLength) {
2452 escInfoReceived = true;
2454 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
2455 uint8_t firmwareVersion = 0;
2456 uint8_t firmwareSubVersion = 0;
2457 uint8_t escType = 0;
2458 switch (escInfoVersion) {
2459 case ESC_INFO_KISS_V1:
2460 firmwareVersion = escInfoBuffer[12];
2461 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
2462 escType = (escInfoBuffer[13] & 0xe0) >> 5;
2464 break;
2465 case ESC_INFO_KISS_V2:
2466 firmwareVersion = escInfoBuffer[13];
2467 firmwareSubVersion = escInfoBuffer[14];
2468 escType = escInfoBuffer[15];
2470 break;
2471 case ESC_INFO_BLHELI32:
2472 firmwareVersion = escInfoBuffer[13];
2473 firmwareSubVersion = escInfoBuffer[14];
2474 escType = escInfoBuffer[15];
2476 break;
2479 cliPrint("ESC Type: ");
2480 switch (escInfoVersion) {
2481 case ESC_INFO_KISS_V1:
2482 case ESC_INFO_KISS_V2:
2483 switch (escType) {
2484 case 1:
2485 cliPrintLine("KISS8A");
2487 break;
2488 case 2:
2489 cliPrintLine("KISS16A");
2491 break;
2492 case 3:
2493 cliPrintLine("KISS24A");
2495 break;
2496 case 5:
2497 cliPrintLine("KISS Ultralite");
2499 break;
2500 default:
2501 cliPrintLine("unknown");
2503 break;
2506 break;
2507 case ESC_INFO_BLHELI32:
2509 char *escType = (char *)(escInfoBuffer + 31);
2510 escType[32] = 0;
2511 cliPrintLine(escType);
2514 break;
2517 cliPrint("MCU Serial No: 0x");
2518 for (int i = 0; i < 12; i++) {
2519 if (i && (i % 3 == 0)) {
2520 cliPrint("-");
2522 cliPrintf("%02x", escInfoBuffer[i]);
2524 cliPrintLinefeed();
2526 switch (escInfoVersion) {
2527 case ESC_INFO_KISS_V1:
2528 case ESC_INFO_KISS_V2:
2529 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
2531 break;
2532 case ESC_INFO_BLHELI32:
2533 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
2535 break;
2537 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
2538 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
2539 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
2540 if (escInfoVersion == ESC_INFO_BLHELI32) {
2541 uint8_t setting = escInfoBuffer[18];
2542 cliPrint("Low voltage Limit: ");
2543 switch (setting) {
2544 case 0:
2545 cliPrintLine("off");
2547 break;
2548 case 255:
2549 cliPrintLine("unsupported");
2551 break;
2552 default:
2553 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
2555 break;
2558 setting = escInfoBuffer[19];
2559 cliPrint("Current Limit: ");
2560 switch (setting) {
2561 case 0:
2562 cliPrintLine("off");
2564 break;
2565 case 255:
2566 cliPrintLine("unsupported");
2568 break;
2569 default:
2570 cliPrintLinef("%d", setting);
2572 break;
2575 for (int i = 0; i < 4; i++) {
2576 setting = escInfoBuffer[i + 20];
2577 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
2581 } else {
2582 cliPrintLine("Checksum Error.");
2587 if (!escInfoReceived) {
2588 cliPrintLine("No Info.");
2592 static void executeEscInfoCommand(uint8_t escIndex)
2594 cliPrintLinef("Info for ESC %d:", escIndex);
2596 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
2598 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
2600 pwmWriteDshotCommand(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO);
2602 delay(10);
2604 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
2606 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
2608 static void cliDshotProg(char *cmdline)
2610 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
2611 cliShowParseError();
2613 return;
2616 char *saveptr;
2617 char *pch = strtok_r(cmdline, " ", &saveptr);
2618 int pos = 0;
2619 int escIndex = 0;
2620 bool firstCommand = true;
2621 while (pch != NULL) {
2622 switch (pos) {
2623 case 0:
2624 escIndex = parseOutputIndex(pch, true);
2625 if (escIndex == -1) {
2626 return;
2629 break;
2630 default:
2632 int command = atoi(pch);
2633 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
2634 if (firstCommand) {
2635 pwmDisableMotors();
2637 if (command == DSHOT_CMD_ESC_INFO) {
2638 delay(5); // Wait for potential ESC telemetry transmission to finish
2639 } else {
2640 delay(1);
2643 firstCommand = false;
2646 if (command != DSHOT_CMD_ESC_INFO) {
2647 pwmWriteDshotCommand(escIndex, getMotorCount(), command);
2648 } else {
2649 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2650 if (feature(FEATURE_ESC_SENSOR)) {
2651 if (escIndex != ALL_MOTORS) {
2652 executeEscInfoCommand(escIndex);
2653 } else {
2654 for (uint8_t i = 0; i < getMotorCount(); i++) {
2655 executeEscInfoCommand(i);
2658 } else
2659 #endif
2661 cliPrintLine("Not supported.");
2665 cliPrintLinef("Command Sent: %d", command);
2667 if (command <= 5) {
2668 delay(20); // wait for sound output to finish
2670 } else {
2671 cliPrintLinef("Invalid command. Range: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
2675 break;
2678 pos++;
2679 pch = strtok_r(NULL, " ", &saveptr);
2682 pwmEnableMotors();
2684 #endif // USE_DSHOT
2686 #ifdef USE_ESCSERIAL
2687 static void cliEscPassthrough(char *cmdline)
2689 if (isEmpty(cmdline)) {
2690 cliShowParseError();
2692 return;
2695 char *saveptr;
2696 char *pch = strtok_r(cmdline, " ", &saveptr);
2697 int pos = 0;
2698 uint8_t mode = 0;
2699 int escIndex = 0;
2700 while (pch != NULL) {
2701 switch (pos) {
2702 case 0:
2703 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
2704 mode = PROTOCOL_SIMONK;
2705 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
2706 mode = PROTOCOL_BLHELI;
2707 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
2708 mode = PROTOCOL_KISS;
2709 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
2710 mode = PROTOCOL_KISSALL;
2711 } else {
2712 cliShowParseError();
2714 return;
2716 break;
2717 case 1:
2718 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
2719 if (escIndex == -1) {
2720 return;
2723 break;
2724 default:
2725 cliShowParseError();
2727 return;
2729 break;
2732 pos++;
2733 pch = strtok_r(NULL, " ", &saveptr);
2736 escEnablePassthrough(cliPort, escIndex, mode);
2738 #endif
2740 #ifndef USE_QUAD_MIXER_ONLY
2741 static void cliMixer(char *cmdline)
2743 int len;
2745 len = strlen(cmdline);
2747 if (len == 0) {
2748 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
2749 return;
2750 } else if (strncasecmp(cmdline, "list", len) == 0) {
2751 cliPrint("Available:");
2752 for (uint32_t i = 0; ; i++) {
2753 if (mixerNames[i] == NULL)
2754 break;
2755 cliPrintf(" %s", mixerNames[i]);
2757 cliPrintLinefeed();
2758 return;
2761 for (uint32_t i = 0; ; i++) {
2762 if (mixerNames[i] == NULL) {
2763 cliPrintLine("Invalid name");
2764 return;
2766 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
2767 mixerConfigMutable()->mixerMode = i + 1;
2768 break;
2772 cliMixer("");
2774 #endif
2776 static void cliMotor(char *cmdline)
2778 if (isEmpty(cmdline)) {
2779 cliShowParseError();
2781 return;
2784 int motorIndex = 0;
2785 int motorValue = 0;
2787 char *saveptr;
2788 char *pch = strtok_r(cmdline, " ", &saveptr);
2789 int index = 0;
2790 while (pch != NULL) {
2791 switch (index) {
2792 case 0:
2793 motorIndex = parseOutputIndex(pch, true);
2794 if (motorIndex == -1) {
2795 return;
2798 break;
2799 case 1:
2800 motorValue = atoi(pch);
2802 break;
2804 index++;
2805 pch = strtok_r(NULL, " ", &saveptr);
2808 if (index == 2) {
2809 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
2810 cliShowArgumentRangeError("value", 1000, 2000);
2811 } else {
2812 uint32_t motorOutputValue = convertExternalToMotor(motorValue);
2814 if (motorIndex != ALL_MOTORS) {
2815 motor_disarmed[motorIndex] = motorOutputValue;
2817 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
2818 } else {
2819 for (int i = 0; i < getMotorCount(); i++) {
2820 motor_disarmed[i] = motorOutputValue;
2823 cliPrintLinef("all motors: %d", motorOutputValue);
2826 } else {
2827 cliShowParseError();
2831 #ifndef MINIMAL_CLI
2832 static void cliPlaySound(char *cmdline)
2834 int i;
2835 const char *name;
2836 static int lastSoundIdx = -1;
2838 if (isEmpty(cmdline)) {
2839 i = lastSoundIdx + 1; //next sound index
2840 if ((name=beeperNameForTableIndex(i)) == NULL) {
2841 while (true) { //no name for index; try next one
2842 if (++i >= beeperTableEntryCount())
2843 i = 0; //if end then wrap around to first entry
2844 if ((name=beeperNameForTableIndex(i)) != NULL)
2845 break; //if name OK then play sound below
2846 if (i == lastSoundIdx + 1) { //prevent infinite loop
2847 cliPrintLine("Error playing sound");
2848 return;
2852 } else { //index value was given
2853 i = atoi(cmdline);
2854 if ((name=beeperNameForTableIndex(i)) == NULL) {
2855 cliPrintLinef("No sound for index %d", i);
2856 return;
2859 lastSoundIdx = i;
2860 beeperSilence();
2861 cliPrintLinef("Playing sound %d: %s", i, name);
2862 beeper(beeperModeForTableIndex(i));
2864 #endif
2866 static void cliProfile(char *cmdline)
2868 if (isEmpty(cmdline)) {
2869 cliPrintLinef("profile %d", getCurrentPidProfileIndex());
2870 return;
2871 } else {
2872 const int i = atoi(cmdline);
2873 if (i >= 0 && i < MAX_PROFILE_COUNT) {
2874 systemConfigMutable()->pidProfileIndex = i;
2875 cliProfile("");
2880 static void cliRateProfile(char *cmdline)
2882 if (isEmpty(cmdline)) {
2883 cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
2884 return;
2885 } else {
2886 const int i = atoi(cmdline);
2887 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
2888 changeControlRateProfile(i);
2889 cliRateProfile("");
2894 static void cliDumpPidProfile(uint8_t pidProfileIndex, uint8_t dumpMask)
2896 if (pidProfileIndex >= MAX_PROFILE_COUNT) {
2897 // Faulty values
2898 return;
2900 changePidProfile(pidProfileIndex);
2901 cliPrintHashLine("profile");
2902 cliProfile("");
2903 cliPrintLinefeed();
2904 dumpAllValues(PROFILE_VALUE, dumpMask);
2907 static void cliDumpRateProfile(uint8_t rateProfileIndex, uint8_t dumpMask)
2909 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
2910 // Faulty values
2911 return;
2913 changeControlRateProfile(rateProfileIndex);
2914 cliPrintHashLine("rateprofile");
2915 cliRateProfile("");
2916 cliPrintLinefeed();
2917 dumpAllValues(PROFILE_RATE_VALUE, dumpMask);
2920 static void cliSave(char *cmdline)
2922 UNUSED(cmdline);
2924 cliPrintHashLine("saving");
2925 writeEEPROM();
2926 cliReboot();
2929 static void cliDefaults(char *cmdline)
2931 bool saveConfigs;
2933 if (isEmpty(cmdline)) {
2934 saveConfigs = true;
2935 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
2936 saveConfigs = false;
2937 } else {
2938 return;
2941 cliPrintHashLine("resetting to defaults");
2942 resetConfigs();
2943 if (saveConfigs) {
2944 cliSave(NULL);
2948 STATIC_UNIT_TESTED void cliGet(char *cmdline)
2950 const clivalue_t *val;
2951 int matchedCommands = 0;
2953 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
2954 if (strcasestr(valueTable[i].name, cmdline)) {
2955 val = &valueTable[i];
2956 cliPrintf("%s = ", valueTable[i].name);
2957 cliPrintVar(val, 0);
2958 cliPrintLinefeed();
2959 cliPrintVarRange(val);
2960 cliPrintLinefeed();
2962 matchedCommands++;
2967 if (matchedCommands) {
2968 return;
2971 cliPrintLine("Invalid name");
2974 static char *skipSpace(char *buffer)
2976 while (*(buffer) == ' ') {
2977 buffer++;
2980 return buffer;
2983 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
2985 while (*(bufEnd - 1) == ' ') {
2986 bufEnd--;
2989 return bufEnd - bufBegin;
2992 STATIC_UNIT_TESTED void cliSet(char *cmdline)
2994 const uint32_t len = strlen(cmdline);
2995 char *eqptr;
2997 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
2998 cliPrintLine("Current settings: ");
3000 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3001 const clivalue_t *val = &valueTable[i];
3002 cliPrintf("%s = ", valueTable[i].name);
3003 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3004 cliPrintLinefeed();
3006 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
3007 // has equals
3009 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
3011 // skip the '=' and any ' ' characters
3012 eqptr++;
3013 eqptr = skipSpace(eqptr);
3015 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3016 const clivalue_t *val = &valueTable[i];
3018 // ensure exact match when setting to prevent setting variables with shorter names
3019 if (strncasecmp(cmdline, val->name, strlen(val->name)) == 0 && variableNameLength == strlen(val->name)) {
3021 bool valueChanged = false;
3022 int16_t value = 0;
3023 switch (val->type & VALUE_MODE_MASK) {
3024 case MODE_DIRECT: {
3025 int16_t value = atoi(eqptr);
3027 if (value >= val->config.minmax.min && value <= val->config.minmax.max) {
3028 cliSetVar(val, value);
3029 valueChanged = true;
3033 break;
3034 case MODE_LOOKUP: {
3035 const lookupTableEntry_t *tableEntry = &lookupTables[val->config.lookup.tableIndex];
3036 bool matched = false;
3037 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
3038 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
3040 if (matched) {
3041 value = tableValueIndex;
3043 cliSetVar(val, value);
3044 valueChanged = true;
3049 break;
3050 case MODE_ARRAY: {
3051 const uint8_t arrayLength = val->config.array.length;
3052 char *valPtr = eqptr;
3054 int i = 0;
3055 while (i < arrayLength && valPtr != NULL) {
3056 // skip spaces
3057 valPtr = skipSpace(valPtr);
3059 // process substring starting at valPtr
3060 // note: no need to copy substrings for atoi()
3061 // it stops at the first character that cannot be converted...
3062 switch (val->type & VALUE_TYPE_MASK) {
3063 default:
3064 case VAR_UINT8:
3066 // fetch data pointer
3067 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
3068 // store value
3069 *data = (uint8_t)atoi((const char*) valPtr);
3072 break;
3073 case VAR_INT8:
3075 // fetch data pointer
3076 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
3077 // store value
3078 *data = (int8_t)atoi((const char*) valPtr);
3081 break;
3082 case VAR_UINT16:
3084 // fetch data pointer
3085 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
3086 // store value
3087 *data = (uint16_t)atoi((const char*) valPtr);
3090 break;
3091 case VAR_INT16:
3093 // fetch data pointer
3094 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
3095 // store value
3096 *data = (int16_t)atoi((const char*) valPtr);
3099 break;
3102 // find next comma (or end of string)
3103 valPtr = strchr(valPtr, ',') + 1;
3105 i++;
3109 // mark as changed
3110 valueChanged = true;
3112 break;
3115 if (valueChanged) {
3116 cliPrintf("%s set to ", val->name);
3117 cliPrintVar(val, 0);
3118 } else {
3119 cliPrintLine("Invalid value");
3120 cliPrintVarRange(val);
3123 return;
3126 cliPrintLine("Invalid name");
3127 } else {
3128 // no equals, check for matching variables.
3129 cliGet(cmdline);
3133 static void cliStatus(char *cmdline)
3135 UNUSED(cmdline);
3137 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3139 #ifdef USE_RTC_TIME
3140 char buf[FORMATTED_DATE_TIME_BUFSIZE];
3141 dateTime_t dt;
3142 if (rtcGetDateTime(&dt)) {
3143 dateTimeFormatLocal(buf, &dt);
3144 cliPrintLinef("Current Time: %s", buf);
3146 #endif
3148 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
3150 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3152 #ifdef USE_ADC_INTERNAL
3153 uint16_t vrefintMv = getVrefMv();
3154 int16_t coretemp = getCoreTemperatureCelsius();
3155 cliPrintf(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
3156 #endif
3158 #if defined(USE_SENSOR_NAMES)
3159 const uint32_t detectedSensorsMask = sensorsMask();
3160 for (uint32_t i = 0; ; i++) {
3161 if (sensorTypeNames[i] == NULL) {
3162 break;
3164 const uint32_t mask = (1 << i);
3165 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3166 const uint8_t sensorHardwareIndex = detectedSensors[i];
3167 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3168 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3169 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
3170 cliPrintf(".%c", acc.dev.revisionCode);
3174 #endif /* USE_SENSOR_NAMES */
3175 cliPrintLinefeed();
3177 #ifdef USE_SDCARD
3178 cliSdInfo(NULL);
3179 #endif
3181 #ifdef USE_I2C
3182 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3183 #else
3184 const uint16_t i2cErrorCounter = 0;
3185 #endif
3187 #ifdef STACK_CHECK
3188 cliPrintf("Stack used: %d, ", stackUsedSize());
3189 #endif
3190 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
3191 #ifdef EEPROM_IN_RAM
3192 #define CONFIG_SIZE EEPROM_SIZE
3193 #else
3194 #define CONFIG_SIZE (&__config_end - &__config_start)
3195 #endif
3196 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), CONFIG_SIZE);
3198 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
3199 const int rxRate = currentRxRefreshRate == 0 ? 0 : (int)(1000000.0f / ((float)currentRxRefreshRate));
3200 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3201 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
3202 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
3203 cliPrint("Arming disable flags:");
3204 armingDisableFlags_e flags = getArmingDisableFlags();
3205 while (flags) {
3206 const int bitpos = ffs(flags) - 1;
3207 flags &= ~(1 << bitpos);
3208 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
3210 cliPrintLinefeed();
3213 #ifndef SKIP_TASK_STATISTICS
3214 static void cliTasks(char *cmdline)
3216 UNUSED(cmdline);
3217 int maxLoadSum = 0;
3218 int averageLoadSum = 0;
3220 #ifndef MINIMAL_CLI
3221 if (systemConfig()->task_statistics) {
3222 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
3223 } else {
3224 cliPrintLine("Task list");
3226 #endif
3227 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3228 cfTaskInfo_t taskInfo;
3229 getTaskInfo(taskId, &taskInfo);
3230 if (taskInfo.isEnabled) {
3231 int taskFrequency;
3232 int subTaskFrequency = 0;
3233 if (taskId == TASK_GYROPID) {
3234 subTaskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3235 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
3236 if (pidConfig()->pid_process_denom > 1) {
3237 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3238 } else {
3239 taskFrequency = subTaskFrequency;
3240 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
3242 } else {
3243 taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3244 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3246 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3247 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3248 if (taskId != TASK_SERIAL) {
3249 maxLoadSum += maxLoad;
3250 averageLoadSum += averageLoad;
3252 if (systemConfig()->task_statistics) {
3253 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
3254 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
3255 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
3256 } else {
3257 cliPrintLinef("%6d", taskFrequency);
3259 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
3260 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
3264 if (systemConfig()->task_statistics) {
3265 cfCheckFuncInfo_t checkFuncInfo;
3266 getCheckFuncInfo(&checkFuncInfo);
3267 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
3268 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3271 #endif
3273 static void cliVersion(char *cmdline)
3275 UNUSED(cmdline);
3277 cliPrintLinef("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
3278 FC_FIRMWARE_NAME,
3279 targetName,
3280 systemConfig()->boardIdentifier,
3281 FC_VERSION_STRING,
3282 buildDate,
3283 buildTime,
3284 shortGitRevision,
3285 MSP_API_VERSION_STRING
3289 #if defined(USE_RESOURCE_MGMT)
3291 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
3293 typedef struct {
3294 const uint8_t owner;
3295 pgn_t pgn;
3296 uint16_t offset;
3297 const uint8_t maxIndex;
3298 } cliResourceValue_t;
3300 const cliResourceValue_t resourceTable[] = {
3301 #ifdef USE_BEEPER
3302 { OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, offsetof(beeperDevConfig_t, ioTag), 0 },
3303 #endif
3304 { OWNER_MOTOR, PG_MOTOR_CONFIG, offsetof(motorConfig_t, dev.ioTags[0]), MAX_SUPPORTED_MOTORS },
3305 #ifdef USE_SERVOS
3306 { OWNER_SERVO, PG_SERVO_CONFIG, offsetof(servoConfig_t, dev.ioTags[0]), MAX_SUPPORTED_SERVOS },
3307 #endif
3308 #if defined(USE_PWM) || defined(USE_PPM)
3309 { OWNER_PPMINPUT, PG_PPM_CONFIG, offsetof(ppmConfig_t, ioTag), 0 },
3310 { OWNER_PWMINPUT, PG_PWM_CONFIG, offsetof(pwmConfig_t, ioTags[0]), PWM_INPUT_PORT_COUNT },
3311 #endif
3312 #ifdef USE_RANGEFINDER_HCSR04
3313 { OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, offsetof(sonarConfig_t, triggerTag), 0 },
3314 { OWNER_SONAR_ECHO, PG_SONAR_CONFIG, offsetof(sonarConfig_t, echoTag), 0 },
3315 #endif
3316 #ifdef USE_LED_STRIP
3317 { OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, offsetof(ledStripConfig_t, ioTag), 0 },
3318 #endif
3319 { OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagTx[0]), SERIAL_PORT_MAX_INDEX },
3320 { OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagRx[0]), SERIAL_PORT_MAX_INDEX },
3321 #ifdef USE_INVERTER
3322 { OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagInverter[0]), SERIAL_PORT_MAX_INDEX },
3323 #endif
3324 #ifdef USE_I2C
3325 { OWNER_I2C_SCL, PG_I2C_CONFIG, offsetof(i2cConfig_t, ioTagScl[0]), I2CDEV_COUNT },
3326 { OWNER_I2C_SDA, PG_I2C_CONFIG, offsetof(i2cConfig_t, ioTagSda[0]), I2CDEV_COUNT },
3327 #endif
3328 { OWNER_LED, PG_STATUS_LED_CONFIG, offsetof(statusLedConfig_t, ioTags[0]), STATUS_LED_NUMBER },
3329 #ifdef USE_SPEKTRUM_BIND
3330 { OWNER_RX_BIND, PG_RX_CONFIG, offsetof(rxConfig_t, spektrum_bind_pin_override_ioTag), 0 },
3331 { OWNER_RX_BIND_PLUG, PG_RX_CONFIG, offsetof(rxConfig_t, spektrum_bind_plug_ioTag), 0 },
3332 #endif
3333 #ifdef USE_TRANSPONDER
3334 { OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, offsetof(transponderConfig_t, ioTag), 0 },
3335 #endif
3336 #ifdef USE_SPI
3337 { OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, offsetof(spiPinConfig_t, ioTagSck[0]), SPIDEV_COUNT },
3338 { OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, offsetof(spiPinConfig_t, ioTagMiso[0]), SPIDEV_COUNT },
3339 { OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, offsetof(spiPinConfig_t, ioTagMosi[0]), SPIDEV_COUNT },
3340 #endif
3341 #ifdef USE_ESCSERIAL
3342 { OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, offsetof(escSerialConfig_t, ioTag), 0 },
3343 #endif
3344 #ifdef USE_CAMERA_CONTROL
3345 { OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, offsetof(cameraControlConfig_t, ioTag), 0 },
3346 #endif
3347 #ifdef USE_ADC
3348 { OWNER_ADC_BATT, PG_ADC_CONFIG, offsetof(adcConfig_t, vbat.ioTag), 0 },
3349 { OWNER_ADC_RSSI, PG_ADC_CONFIG, offsetof(adcConfig_t, rssi.ioTag), 0 },
3350 { OWNER_ADC_CURR, PG_ADC_CONFIG, offsetof(adcConfig_t, current.ioTag), 0 },
3351 { OWNER_ADC_EXT, PG_ADC_CONFIG, offsetof(adcConfig_t, external1.ioTag), 0 },
3352 #endif
3353 #ifdef USE_BARO
3354 { OWNER_BARO_CS, PG_BAROMETER_CONFIG, offsetof(barometerConfig_t, baro_spi_csn), 0 },
3355 #endif
3356 #ifdef USE_MAG
3357 { OWNER_COMPASS_CS, PG_COMPASS_CONFIG, offsetof(compassConfig_t, mag_spi_csn), 0 },
3358 #endif
3359 #ifdef USE_SDCARD
3360 { OWNER_SDCARD_CS, PG_SDCARD_CONFIG, offsetof(sdcardConfig_t, chipSelectTag), 0 },
3361 { OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, offsetof(sdcardConfig_t, cardDetectTag), 0 },
3362 #endif
3363 #ifdef USE_PINIO
3364 { OWNER_PINIO, PG_PINIO_CONFIG, offsetof(pinioConfig_t, ioTag), PINIO_COUNT },
3365 #endif
3366 #if defined(USE_USB_MSC)
3367 { OWNER_USB_MSC_PIN, PG_USB_CONFIG, offsetof(usbDev_t, mscButtonPin), 0 },
3368 #endif
3369 #ifdef USE_FLASH
3370 { OWNER_FLASH_CS, PG_FLASH_CONFIG, offsetof(flashConfig_t, csTag), 0 },
3371 #endif
3372 #ifdef USE_MAX7456
3373 { OWNER_OSD_CS, PG_MAX7456_CONFIG, offsetof(max7456Config_t, csTag), 0 },
3374 #endif
3377 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
3379 const pgRegistry_t* rec = pgFind(value.pgn);
3380 return CONST_CAST(ioTag_t *, rec->address + value.offset + index);
3383 static void printResource(uint8_t dumpMask)
3385 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
3386 const char* owner = ownerNames[resourceTable[i].owner];
3387 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
3388 const void *currentConfig;
3389 const void *defaultConfig;
3390 if (configIsInCopy) {
3391 currentConfig = pg->copy;
3392 defaultConfig = pg->address;
3393 } else {
3394 currentConfig = pg->address;
3395 defaultConfig = NULL;
3398 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
3399 const ioTag_t ioTag = *((const ioTag_t *)currentConfig + resourceTable[i].offset + index);
3400 const ioTag_t ioTagDefault = *((const ioTag_t *)defaultConfig + resourceTable[i].offset + index);
3402 bool equalsDefault = ioTag == ioTagDefault;
3403 const char *format = "resource %s %d %c%02d";
3404 const char *formatUnassigned = "resource %s %d NONE";
3405 if (!ioTagDefault) {
3406 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3407 } else {
3408 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
3410 if (!ioTag) {
3411 if (!(dumpMask & HIDE_UNUSED)) {
3412 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3414 } else {
3415 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
3421 static void printResourceOwner(uint8_t owner, uint8_t index)
3423 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
3425 if (resourceTable[owner].maxIndex > 0) {
3426 cliPrintf(" %d", RESOURCE_INDEX(index));
3430 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
3432 if (!newTag) {
3433 return;
3436 const char * format = "\r\nNOTE: %c%02d already assigned to ";
3437 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
3438 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
3439 ioTag_t *tag = getIoTag(resourceTable[r], i);
3440 if (*tag == newTag) {
3441 bool cleared = false;
3442 if (r == resourceIndex) {
3443 if (i == index) {
3444 continue;
3446 *tag = IO_TAG_NONE;
3447 cleared = true;
3450 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
3452 printResourceOwner(r, i);
3454 if (cleared) {
3455 cliPrintf(". ");
3456 printResourceOwner(r, i);
3457 cliPrintf(" disabled");
3460 cliPrintLine(".");
3466 static bool strToPin(char *pch, ioTag_t *tag)
3468 if (strcasecmp(pch, "NONE") == 0) {
3469 *tag = IO_TAG_NONE;
3470 return true;
3471 } else {
3472 unsigned pin = 0;
3473 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
3475 if (port < 8) {
3476 pch++;
3477 pin = atoi(pch);
3478 if (pin < 16) {
3479 *tag = DEFIO_TAG_MAKE(port, pin);
3480 return true;
3484 return false;
3487 static void cliResource(char *cmdline)
3489 int len = strlen(cmdline);
3491 if (len == 0) {
3492 printResource(DUMP_MASTER | HIDE_UNUSED);
3494 return;
3495 } else if (strncasecmp(cmdline, "list", len) == 0) {
3496 #ifdef MINIMAL_CLI
3497 cliPrintLine("IO");
3498 #else
3499 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
3500 cliRepeat('-', 20);
3501 #endif
3502 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3503 const char* owner;
3504 owner = ownerNames[ioRecs[i].owner];
3506 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
3507 if (ioRecs[i].index > 0) {
3508 cliPrintf(" %d", ioRecs[i].index);
3510 cliPrintLinefeed();
3513 #ifndef MINIMAL_CLI
3514 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
3515 #endif
3517 return;
3520 uint8_t resourceIndex = 0;
3521 int index = 0;
3522 char *pch = NULL;
3523 char *saveptr;
3525 pch = strtok_r(cmdline, " ", &saveptr);
3526 for (resourceIndex = 0; ; resourceIndex++) {
3527 if (resourceIndex >= ARRAYLEN(resourceTable)) {
3528 cliPrintLine("Invalid");
3529 return;
3532 if (strncasecmp(pch, ownerNames[resourceTable[resourceIndex].owner], len) == 0) {
3533 break;
3537 pch = strtok_r(NULL, " ", &saveptr);
3538 index = atoi(pch);
3540 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
3541 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
3542 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
3543 return;
3545 index -= 1;
3547 pch = strtok_r(NULL, " ", &saveptr);
3550 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
3552 if (strlen(pch) > 0) {
3553 if (strToPin(pch, tag)) {
3554 if (*tag == IO_TAG_NONE) {
3555 #ifdef MINIMAL_CLI
3556 cliPrintLine("Freed");
3557 #else
3558 cliPrintLine("Resource is freed");
3559 #endif
3560 return;
3561 } else {
3562 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
3563 if (rec) {
3564 resourceCheck(resourceIndex, index, *tag);
3565 #ifdef MINIMAL_CLI
3566 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
3567 #else
3568 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
3569 #endif
3570 } else {
3571 cliShowParseError();
3573 return;
3578 cliShowParseError();
3581 static void printDma(void)
3583 cliPrintLinefeed();
3585 #ifdef MINIMAL_CLI
3586 cliPrintLine("DMA:");
3587 #else
3588 cliPrintLine("Currently active DMA:");
3589 cliRepeat('-', 20);
3590 #endif
3591 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
3592 const char* owner;
3593 owner = ownerNames[dmaGetOwner(i)];
3595 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
3596 uint8_t resourceIndex = dmaGetResourceIndex(i);
3597 if (resourceIndex > 0) {
3598 cliPrintLinef(" %s %d", owner, resourceIndex);
3599 } else {
3600 cliPrintLinef(" %s", owner);
3605 static void cliDma(char* cmdLine)
3607 UNUSED(cmdLine);
3608 printDma();
3610 #endif /* USE_RESOURCE_MGMT */
3612 static void backupConfigs(void)
3614 // make copies of configs to do differencing
3615 PG_FOREACH(pg) {
3616 memcpy(pg->copy, pg->address, pg->size);
3619 configIsInCopy = true;
3622 static void restoreConfigs(void)
3624 PG_FOREACH(pg) {
3625 memcpy(pg->address, pg->copy, pg->size);
3628 configIsInCopy = false;
3631 static void printConfig(char *cmdline, bool doDiff)
3633 uint8_t dumpMask = DUMP_MASTER;
3634 char *options;
3635 if ((options = checkCommand(cmdline, "master"))) {
3636 dumpMask = DUMP_MASTER; // only
3637 } else if ((options = checkCommand(cmdline, "profile"))) {
3638 dumpMask = DUMP_PROFILE; // only
3639 } else if ((options = checkCommand(cmdline, "rates"))) {
3640 dumpMask = DUMP_RATES; // only
3641 } else if ((options = checkCommand(cmdline, "all"))) {
3642 dumpMask = DUMP_ALL; // all profiles and rates
3643 } else {
3644 options = cmdline;
3647 if (doDiff) {
3648 dumpMask = dumpMask | DO_DIFF;
3651 backupConfigs();
3652 // reset all configs to defaults to do differencing
3653 resetConfigs();
3655 #if defined(USE_TARGET_CONFIG)
3656 targetConfiguration();
3657 #endif
3658 if (checkCommand(options, "defaults")) {
3659 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
3662 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
3663 cliPrintHashLine("version");
3664 cliVersion(NULL);
3666 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
3667 cliPrintHashLine("reset configuration to default settings");
3668 cliPrint("defaults nosave");
3669 cliPrintLinefeed();
3672 cliPrintHashLine("name");
3673 printName(dumpMask, &pilotConfig_Copy);
3675 #ifdef USE_RESOURCE_MGMT
3676 cliPrintHashLine("resources");
3677 printResource(dumpMask);
3678 #endif
3680 #ifndef USE_QUAD_MIXER_ONLY
3681 cliPrintHashLine("mixer");
3682 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
3683 const char *formatMixer = "mixer %s";
3684 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
3685 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
3687 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
3689 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0));
3691 #ifdef USE_SERVOS
3692 cliPrintHashLine("servo");
3693 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
3695 cliPrintHashLine("servo mix");
3696 // print custom servo mixer if exists
3697 cliDumpPrintLinef(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n");
3698 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
3699 #endif
3700 #endif
3702 cliPrintHashLine("feature");
3703 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
3705 #ifdef USE_BEEPER
3706 cliPrintHashLine("beeper");
3707 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
3708 #endif
3710 cliPrintHashLine("map");
3711 printMap(dumpMask, &rxConfig_Copy, rxConfig());
3713 cliPrintHashLine("serial");
3714 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
3716 #ifdef USE_LED_STRIP
3717 cliPrintHashLine("led");
3718 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
3720 cliPrintHashLine("color");
3721 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
3723 cliPrintHashLine("mode_color");
3724 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
3725 #endif
3727 cliPrintHashLine("aux");
3728 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
3730 cliPrintHashLine("adjrange");
3731 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
3733 cliPrintHashLine("rxrange");
3734 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
3736 #ifdef USE_VTX_CONTROL
3737 cliPrintHashLine("vtx");
3738 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig());
3739 #endif
3741 cliPrintHashLine("rxfail");
3742 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0));
3744 cliPrintHashLine("master");
3745 dumpAllValues(MASTER_VALUE, dumpMask);
3747 if (dumpMask & DUMP_ALL) {
3748 const uint8_t pidProfileIndexSave = systemConfig_Copy.pidProfileIndex;
3749 for (uint32_t pidProfileIndex = 0; pidProfileIndex < MAX_PROFILE_COUNT; pidProfileIndex++) {
3750 cliDumpPidProfile(pidProfileIndex, dumpMask);
3752 changePidProfile(pidProfileIndexSave);
3753 cliPrintHashLine("restore original profile selection");
3754 cliProfile("");
3756 const uint8_t controlRateProfileIndexSave = systemConfig_Copy.activeRateProfile;
3757 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
3758 cliDumpRateProfile(rateIndex, dumpMask);
3760 changeControlRateProfile(controlRateProfileIndexSave);
3761 cliPrintHashLine("restore original rateprofile selection");
3762 cliRateProfile("");
3764 cliPrintHashLine("save configuration");
3765 cliPrint("save");
3766 } else {
3767 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
3769 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
3773 if (dumpMask & DUMP_PROFILE) {
3774 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
3777 if (dumpMask & DUMP_RATES) {
3778 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
3780 // restore configs from copies
3781 restoreConfigs();
3784 static void cliDump(char *cmdline)
3786 printConfig(cmdline, false);
3789 static void cliDiff(char *cmdline)
3791 printConfig(cmdline, true);
3794 #ifdef USE_USB_MSC
3795 static void cliMsc(char *cmdline)
3797 UNUSED(cmdline);
3799 if (false
3800 #ifdef USE_SDCARD
3801 || sdcard_isFunctional()
3802 #endif
3803 #ifdef USE_FLASHFS
3804 || flashfsGetSize() > 0
3805 #endif
3807 cliPrintHashLine("restarting in mass storage mode");
3808 cliPrint("\r\nRebooting");
3809 bufWriterFlush(cliWriter);
3810 delay(1000);
3811 waitForSerialPortToFinishTransmitting(cliPort);
3812 stopPwmAllMotors();
3813 if (mpuResetFn) {
3814 mpuResetFn();
3817 #ifdef STM32F7
3818 *((__IO uint32_t*) BKPSRAM_BASE + 16) = MSC_MAGIC;
3819 #elif defined(STM32F4)
3820 *((uint32_t *)0x2001FFF0) = MSC_MAGIC;
3821 #endif
3823 __disable_irq();
3824 NVIC_SystemReset();
3825 } else {
3826 cliPrint("\r\nStorage not present or failed to initialize!");
3827 bufWriterFlush(cliWriter);
3830 #endif
3832 typedef struct {
3833 const char *name;
3834 #ifndef MINIMAL_CLI
3835 const char *description;
3836 const char *args;
3837 #endif
3838 void (*func)(char *cmdline);
3839 } clicmd_t;
3841 #ifndef MINIMAL_CLI
3842 #define CLI_COMMAND_DEF(name, description, args, method) \
3844 name , \
3845 description , \
3846 args , \
3847 method \
3849 #else
3850 #define CLI_COMMAND_DEF(name, description, args, method) \
3852 name, \
3853 method \
3855 #endif
3857 static void cliHelp(char *cmdline);
3859 // should be sorted a..z for bsearch()
3860 const clicmd_t cmdTable[] = {
3861 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
3862 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
3863 #ifdef USE_BEEPER
3864 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3865 "\t<+|->[name]", cliBeeper),
3866 #endif
3867 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL, cliBootloader),
3868 #ifdef USE_LED_STRIP
3869 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
3870 #endif
3871 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults),
3872 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|all] {defaults}", cliDiff),
3873 #ifdef USE_DSHOT
3874 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
3875 #endif
3876 CLI_COMMAND_DEF("dump", "dump configuration",
3877 "[master|profile|rates|all] {defaults}", cliDump),
3878 #ifdef USE_ESCSERIAL
3879 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
3880 #endif
3881 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
3882 CLI_COMMAND_DEF("feature", "configure features",
3883 "list\r\n"
3884 "\t<+|->[name]", cliFeature),
3885 #ifdef USE_FLASHFS
3886 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
3887 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
3888 #ifdef USE_FLASH_TOOLS
3889 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
3890 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
3891 #endif
3892 #endif
3893 #ifdef USE_RX_FRSKY_SPI
3894 CLI_COMMAND_DEF("frsky_bind", "initiate binding for FrSky SPI RX", NULL, cliFrSkyBind),
3895 #endif
3896 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
3897 #ifdef USE_GPS
3898 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
3899 #endif
3900 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3901 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
3902 #endif
3903 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
3904 #ifdef USE_LED_STRIP
3905 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
3906 #endif
3907 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
3908 #ifndef USE_QUAD_MIXER_ONLY
3909 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
3910 #endif
3911 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
3912 #ifdef USE_LED_STRIP
3913 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
3914 #endif
3915 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
3916 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
3917 #ifndef MINIMAL_CLI
3918 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
3919 #endif
3920 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
3921 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
3922 #ifdef USE_RESOURCE_MGMT
3923 CLI_COMMAND_DEF("resource", "show/set resources", NULL, cliResource),
3924 CLI_COMMAND_DEF("dma", "list dma utilisation", NULL, cliDma),
3925 #endif
3926 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
3927 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
3928 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
3929 #ifdef USE_SDCARD
3930 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
3931 #endif
3932 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
3933 #ifndef SKIP_SERIAL_PASSTHROUGH
3934 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [DTR PINIO]: passthrough to serial", cliSerialPassthrough),
3935 #endif
3936 #ifdef USE_SERVOS
3937 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
3938 #endif
3939 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
3940 #ifdef USE_SERVOS
3941 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
3942 "\treset\r\n"
3943 "\tload <mixer>\r\n"
3944 "\treverse <servo> <source> r|n", cliServoMix),
3945 #endif
3946 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
3947 #ifndef SKIP_TASK_STATISTICS
3948 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
3949 #endif
3950 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
3951 #ifdef USE_VTX_CONTROL
3952 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
3953 #endif
3954 #ifdef USE_USB_MSC
3955 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
3956 #endif
3959 static void cliHelp(char *cmdline)
3961 UNUSED(cmdline);
3963 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
3964 cliPrint(cmdTable[i].name);
3965 #ifndef MINIMAL_CLI
3966 if (cmdTable[i].description) {
3967 cliPrintf(" - %s", cmdTable[i].description);
3969 if (cmdTable[i].args) {
3970 cliPrintf("\r\n\t%s", cmdTable[i].args);
3972 #endif
3973 cliPrintLinefeed();
3977 void cliProcess(void)
3979 if (!cliWriter) {
3980 return;
3983 // Be a little bit tricky. Flush the last inputs buffer, if any.
3984 bufWriterFlush(cliWriter);
3986 while (serialRxBytesWaiting(cliPort)) {
3987 uint8_t c = serialRead(cliPort);
3988 if (c == '\t' || c == '?') {
3989 // do tab completion
3990 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
3991 uint32_t i = bufferIndex;
3992 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
3993 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
3994 continue;
3995 if (!pstart)
3996 pstart = cmd;
3997 pend = cmd;
3999 if (pstart) { /* Buffer matches one or more commands */
4000 for (; ; bufferIndex++) {
4001 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
4002 break;
4003 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
4004 /* Unambiguous -- append a space */
4005 cliBuffer[bufferIndex++] = ' ';
4006 cliBuffer[bufferIndex] = '\0';
4007 break;
4009 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
4012 if (!bufferIndex || pstart != pend) {
4013 /* Print list of ambiguous matches */
4014 cliPrint("\r\033[K");
4015 for (cmd = pstart; cmd <= pend; cmd++) {
4016 cliPrint(cmd->name);
4017 cliWrite('\t');
4019 cliPrompt();
4020 i = 0; /* Redraw prompt */
4022 for (; i < bufferIndex; i++)
4023 cliWrite(cliBuffer[i]);
4024 } else if (!bufferIndex && c == 4) { // CTRL-D
4025 cliExit(cliBuffer);
4026 return;
4027 } else if (c == 12) { // NewPage / CTRL-L
4028 // clear screen
4029 cliPrint("\033[2J\033[1;1H");
4030 cliPrompt();
4031 } else if (bufferIndex && (c == '\n' || c == '\r')) {
4032 // enter pressed
4033 cliPrintLinefeed();
4035 // Strip comment starting with # from line
4036 char *p = cliBuffer;
4037 p = strchr(p, '#');
4038 if (NULL != p) {
4039 bufferIndex = (uint32_t)(p - cliBuffer);
4042 // Strip trailing whitespace
4043 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
4044 bufferIndex--;
4047 // Process non-empty lines
4048 if (bufferIndex > 0) {
4049 cliBuffer[bufferIndex] = 0; // null terminate
4051 const clicmd_t *cmd;
4052 char *options;
4053 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4054 if ((options = checkCommand(cliBuffer, cmd->name))) {
4055 break;
4058 if (cmd < cmdTable + ARRAYLEN(cmdTable))
4059 cmd->func(options);
4060 else
4061 cliPrint("Unknown command, try 'help'");
4062 bufferIndex = 0;
4065 memset(cliBuffer, 0, sizeof(cliBuffer));
4067 // 'exit' will reset this flag, so we don't need to print prompt again
4068 if (!cliMode)
4069 return;
4071 cliPrompt();
4072 } else if (c == 127) {
4073 // backspace
4074 if (bufferIndex) {
4075 cliBuffer[--bufferIndex] = 0;
4076 cliPrint("\010 \010");
4078 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
4079 if (!bufferIndex && c == ' ')
4080 continue; // Ignore leading spaces
4081 cliBuffer[bufferIndex++] = c;
4082 cliWrite(c);
4087 void cliEnter(serialPort_t *serialPort)
4089 cliMode = 1;
4090 cliPort = serialPort;
4091 setPrintfSerialPort(cliPort);
4092 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
4094 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
4096 #ifndef MINIMAL_CLI
4097 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4098 #else
4099 cliPrintLine("\r\nCLI");
4100 #endif
4101 cliPrompt();
4103 setArmingDisabled(ARMING_DISABLED_CLI);
4106 void cliInit(const serialConfig_t *serialConfig)
4108 UNUSED(serialConfig);
4110 #endif // USE_CLI