Refactor to support target specific defaults
[betaflight.git] / src / main / interface / cli.c
blob78a9d9f2d8a335a4ca3ea64d693d920aef6d0d2a
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/pid.h"
100 #include "flight/servos.h"
102 #include "interface/cli.h"
103 #include "interface/msp.h"
104 #include "interface/msp_box.h"
105 #include "interface/msp_protocol.h"
106 #include "interface/settings.h"
108 #include "io/asyncfatfs/asyncfatfs.h"
109 #include "io/beeper.h"
110 #include "io/flashfs.h"
111 #include "io/gimbal.h"
112 #include "io/gps.h"
113 #include "io/ledstrip.h"
114 #include "io/osd.h"
115 #include "io/serial.h"
116 #include "io/transponder_ir.h"
117 #include "io/vtx_control.h"
118 #include "io/vtx.h"
120 #include "pg/adc.h"
121 #include "pg/beeper.h"
122 #include "pg/beeper_dev.h"
123 #include "pg/bus_i2c.h"
124 #include "pg/bus_spi.h"
125 #include "pg/max7456.h"
126 #include "pg/pinio.h"
127 #include "pg/pg.h"
128 #include "pg/pg_ids.h"
129 #include "pg/rx_pwm.h"
130 #include "pg/timerio.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 backupPgConfig(const pgRegistry_t *pg)
220 memcpy(pg->copy, pg->address, pg->size);
223 static void restorePgConfig(const pgRegistry_t *pg)
225 memcpy(pg->address, pg->copy, pg->size);
228 static void backupConfigs(void)
230 // make copies of configs to do differencing
231 PG_FOREACH(pg) {
232 backupPgConfig(pg);
235 configIsInCopy = true;
238 static void restoreConfigs(void)
240 PG_FOREACH(pg) {
241 restorePgConfig(pg);
244 configIsInCopy = false;
247 static void backupAndResetConfigs(void)
249 backupConfigs();
250 // reset all configs to defaults to do differencing
251 resetConfigs();
254 static void cliPrint(const char *str)
256 while (*str) {
257 bufWriterAppend(cliWriter, *str++);
259 bufWriterFlush(cliWriter);
262 static void cliPrintLinefeed(void)
264 cliPrint("\r\n");
267 static void cliPrintLine(const char *str)
269 cliPrint(str);
270 cliPrintLinefeed();
273 #ifdef MINIMAL_CLI
274 #define cliPrintHashLine(str)
275 #else
276 static void cliPrintHashLine(const char *str)
278 cliPrint("\r\n# ");
279 cliPrintLine(str);
281 #endif
283 static void cliPutp(void *p, char ch)
285 bufWriterAppend(p, ch);
288 typedef enum {
289 DUMP_MASTER = (1 << 0),
290 DUMP_PROFILE = (1 << 1),
291 DUMP_RATES = (1 << 2),
292 DUMP_ALL = (1 << 3),
293 DO_DIFF = (1 << 4),
294 SHOW_DEFAULTS = (1 << 5),
295 HIDE_UNUSED = (1 << 6)
296 } dumpFlags_e;
298 static void cliPrintfva(const char *format, va_list va)
300 tfp_format(cliWriter, cliPutp, format, va);
301 bufWriterFlush(cliWriter);
304 static void cliPrintLinefva(const char *format, va_list va)
306 tfp_format(cliWriter, cliPutp, format, va);
307 bufWriterFlush(cliWriter);
308 cliPrintLinefeed();
311 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
313 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
314 va_list va;
315 va_start(va, format);
316 cliPrintLinefva(format, va);
317 va_end(va);
318 return true;
319 } else {
320 return false;
324 static void cliWrite(uint8_t ch)
326 bufWriterAppend(cliWriter, ch);
329 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
331 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
332 cliWrite('#');
334 va_list va;
335 va_start(va, format);
336 cliPrintLinefva(format, va);
337 va_end(va);
338 return true;
339 } else {
340 return false;
344 static void cliPrintf(const char *format, ...)
346 va_list va;
347 va_start(va, format);
348 cliPrintfva(format, va);
349 va_end(va);
353 static void cliPrintLinef(const char *format, ...)
355 va_list va;
356 va_start(va, format);
357 cliPrintLinefva(format, va);
358 va_end(va);
362 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
364 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
365 for (int i = 0; i < var->config.array.length; i++) {
366 switch (var->type & VALUE_TYPE_MASK) {
367 default:
368 case VAR_UINT8:
369 // uint8_t array
370 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
371 break;
373 case VAR_INT8:
374 // int8_t array
375 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
376 break;
378 case VAR_UINT16:
379 // uin16_t array
380 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
381 break;
383 case VAR_INT16:
384 // int16_t array
385 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
386 break;
389 if (i < var->config.array.length - 1) {
390 cliPrint(",");
393 } else {
394 int value = 0;
396 switch (var->type & VALUE_TYPE_MASK) {
397 case VAR_UINT8:
398 value = *(uint8_t *)valuePointer;
399 break;
401 case VAR_INT8:
402 value = *(int8_t *)valuePointer;
403 break;
405 case VAR_UINT16:
406 case VAR_INT16:
407 value = *(int16_t *)valuePointer;
408 break;
409 case VAR_UINT32:
410 value = *(uint32_t *)valuePointer;
411 break;
414 switch (var->type & VALUE_MODE_MASK) {
415 case MODE_DIRECT:
416 cliPrintf("%d", value);
417 if (full) {
418 cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
420 break;
421 case MODE_LOOKUP:
422 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
423 break;
424 case MODE_BITSET:
425 if (value & 1 << var->config.bitpos) {
426 cliPrintf("ON");
427 } else {
428 cliPrintf("OFF");
435 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
437 bool result = true;
438 int elementCount = 1;
439 uint32_t mask = 0xffffffff;
441 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
442 elementCount = var->config.array.length;
444 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
445 mask = 1 << var->config.bitpos;
447 for (int i = 0; i < elementCount; i++) {
448 switch (var->type & VALUE_TYPE_MASK) {
449 case VAR_UINT8:
450 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
451 break;
453 case VAR_INT8:
454 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
455 break;
457 case VAR_UINT16:
458 result = result && (((int16_t *)ptr)[i] & mask) == (((int16_t *)ptrDefault)[i] & mask);
459 break;
460 case VAR_INT16:
461 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
462 break;
463 case VAR_UINT32:
464 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
465 break;
469 return result;
472 static uint16_t getValueOffset(const clivalue_t *value)
474 switch (value->type & VALUE_SECTION_MASK) {
475 case MASTER_VALUE:
476 return value->offset;
477 case PROFILE_VALUE:
478 return value->offset + sizeof(pidProfile_t) * getCurrentPidProfileIndex();
479 case PROFILE_RATE_VALUE:
480 return value->offset + sizeof(controlRateConfig_t) * getCurrentControlRateProfileIndex();
482 return 0;
485 void *cliGetValuePointer(const clivalue_t *value)
487 const pgRegistry_t* rec = pgFind(value->pgn);
488 if (configIsInCopy) {
489 return CONST_CAST(void *, rec->copy + getValueOffset(value));
490 } else {
491 return CONST_CAST(void *, rec->address + getValueOffset(value));
495 const void *cliGetDefaultPointer(const clivalue_t *value)
497 const pgRegistry_t* rec = pgFind(value->pgn);
498 return rec->address + getValueOffset(value);
501 static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
503 const pgRegistry_t *pg = pgFind(value->pgn);
504 #ifdef DEBUG
505 if (!pg) {
506 cliPrintLinef("VALUE %s ERROR", value->name);
507 return; // if it's not found, the pgn shouldn't be in the value table!
509 #endif
511 const char *format = "set %s = ";
512 const char *defaultFormat = "#set %s = ";
513 const int valueOffset = getValueOffset(value);
514 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
516 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
517 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
518 cliPrintf(defaultFormat, value->name);
519 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
520 cliPrintLinefeed();
522 cliPrintf(format, value->name);
523 printValuePointer(value, pg->copy + valueOffset, false);
524 cliPrintLinefeed();
528 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
530 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
531 const clivalue_t *value = &valueTable[i];
532 bufWriterFlush(cliWriter);
533 if ((value->type & VALUE_SECTION_MASK) == valueSection) {
534 dumpPgValue(value, dumpMask);
539 static void cliPrintVar(const clivalue_t *var, bool full)
541 const void *ptr = cliGetValuePointer(var);
543 printValuePointer(var, ptr, full);
546 static void cliPrintVarRange(const clivalue_t *var)
548 switch (var->type & VALUE_MODE_MASK) {
549 case (MODE_DIRECT): {
550 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
552 break;
553 case (MODE_LOOKUP): {
554 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
555 cliPrint("Allowed values: ");
556 bool firstEntry = true;
557 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
558 if (tableEntry->values[i]) {
559 if (!firstEntry) {
560 cliPrint(", ");
562 cliPrintf("%s", tableEntry->values[i]);
563 firstEntry = false;
566 cliPrintLinefeed();
568 break;
569 case (MODE_ARRAY): {
570 cliPrintLinef("Array length: %d", var->config.array.length);
572 break;
573 case (MODE_BITSET): {
574 cliPrintLinef("Allowed values: OFF, ON");
576 break;
580 static void cliSetVar(const clivalue_t *var, const int16_t value)
582 void *ptr = cliGetValuePointer(var);
583 uint32_t workValue;
584 uint32_t mask;
586 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
587 switch (var->type & VALUE_TYPE_MASK) {
588 case VAR_UINT8:
589 mask = (1 << var->config.bitpos) & 0xff;
590 if (value) {
591 workValue = *(uint8_t *)ptr | mask;
592 } else {
593 workValue = *(uint8_t *)ptr & ~mask;
595 *(uint8_t *)ptr = workValue;
596 break;
598 case VAR_UINT16:
599 mask = (1 << var->config.bitpos) & 0xffff;
600 if (value) {
601 workValue = *(uint16_t *)ptr | mask;
602 } else {
603 workValue = *(uint16_t *)ptr & ~mask;
605 *(uint16_t *)ptr = workValue;
606 break;
608 case VAR_UINT32:
609 mask = 1 << var->config.bitpos;
610 if (value) {
611 workValue = *(uint32_t *)ptr | mask;
612 } else {
613 workValue = *(uint32_t *)ptr & ~mask;
615 *(uint32_t *)ptr = workValue;
616 break;
619 } else {
620 switch (var->type & VALUE_TYPE_MASK) {
621 case VAR_UINT8:
622 *(uint8_t *)ptr = value;
623 break;
625 case VAR_INT8:
626 *(int8_t *)ptr = value;
627 break;
629 case VAR_UINT16:
630 case VAR_INT16:
631 *(int16_t *)ptr = value;
632 break;
637 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
638 static void cliRepeat(char ch, uint8_t len)
640 for (int i = 0; i < len; i++) {
641 bufWriterAppend(cliWriter, ch);
643 cliPrintLinefeed();
645 #endif
647 static void cliPrompt(void)
649 cliPrint("\r\n# ");
652 static void cliShowParseError(void)
654 cliPrintLine("Parse error");
657 static void cliShowArgumentRangeError(char *name, int min, int max)
659 cliPrintLinef("%s not between %d and %d", name, min, max);
662 static const char *nextArg(const char *currentArg)
664 const char *ptr = strchr(currentArg, ' ');
665 while (ptr && *ptr == ' ') {
666 ptr++;
669 return ptr;
672 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
674 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
675 ptr = nextArg(ptr);
676 if (ptr) {
677 int val = atoi(ptr);
678 val = CHANNEL_VALUE_TO_STEP(val);
679 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
680 if (argIndex == 0) {
681 range->startStep = val;
682 } else {
683 range->endStep = val;
685 (*validArgumentCount)++;
690 return ptr;
693 // Check if a string's length is zero
694 static bool isEmpty(const char *string)
696 return (string == NULL || *string == '\0') ? true : false;
699 static void printRxFailsafe(uint8_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs)
701 // print out rxConfig failsafe settings
702 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
703 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
704 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
705 const bool equalsDefault = channelFailsafeConfig->mode == defaultChannelFailsafeConfig->mode
706 && channelFailsafeConfig->step == defaultChannelFailsafeConfig->step;
707 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
708 if (requireValue) {
709 const char *format = "rxfail %u %c %d";
710 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
711 channel,
712 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
713 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
715 cliDumpPrintLinef(dumpMask, equalsDefault, format,
716 channel,
717 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
718 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
720 } else {
721 const char *format = "rxfail %u %c";
722 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
723 channel,
724 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
726 cliDumpPrintLinef(dumpMask, equalsDefault, format,
727 channel,
728 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
734 static void cliRxFailsafe(char *cmdline)
736 uint8_t channel;
737 char buf[3];
739 if (isEmpty(cmdline)) {
740 // print out rxConfig failsafe settings
741 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
742 cliRxFailsafe(itoa(channel, buf, 10));
744 } else {
745 const char *ptr = cmdline;
746 channel = atoi(ptr++);
747 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
749 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
751 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
752 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
753 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
755 ptr = nextArg(ptr);
756 if (ptr) {
757 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
758 if (p) {
759 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
760 mode = rxFailsafeModesTable[type][requestedMode];
761 } else {
762 mode = RX_FAILSAFE_MODE_INVALID;
764 if (mode == RX_FAILSAFE_MODE_INVALID) {
765 cliShowParseError();
766 return;
769 requireValue = mode == RX_FAILSAFE_MODE_SET;
771 ptr = nextArg(ptr);
772 if (ptr) {
773 if (!requireValue) {
774 cliShowParseError();
775 return;
777 uint16_t value = atoi(ptr);
778 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
779 if (value > MAX_RXFAIL_RANGE_STEP) {
780 cliPrintLine("Value out of range");
781 return;
784 channelFailsafeConfig->step = value;
785 } else if (requireValue) {
786 cliShowParseError();
787 return;
789 channelFailsafeConfig->mode = mode;
792 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
794 // double use of cliPrintf below
795 // 1. acknowledge interpretation on command,
796 // 2. query current setting on single item,
798 if (requireValue) {
799 cliPrintLinef("rxfail %u %c %d",
800 channel,
801 modeCharacter,
802 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
804 } else {
805 cliPrintLinef("rxfail %u %c",
806 channel,
807 modeCharacter
810 } else {
811 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
816 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
818 const char *format = "aux %u %u %u %u %u %u";
819 // print out aux channel settings
820 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
821 const modeActivationCondition_t *mac = &modeActivationConditions[i];
822 bool equalsDefault = false;
823 if (defaultModeActivationConditions) {
824 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
825 equalsDefault = mac->modeId == macDefault->modeId
826 && mac->auxChannelIndex == macDefault->auxChannelIndex
827 && mac->range.startStep == macDefault->range.startStep
828 && mac->range.endStep == macDefault->range.endStep
829 && mac->modeLogic == macDefault->modeLogic;
830 const box_t *box = findBoxByBoxId(macDefault->modeId);
831 if (box) {
832 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
834 box->permanentId,
835 macDefault->auxChannelIndex,
836 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
837 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
838 macDefault->modeLogic
842 const box_t *box = findBoxByBoxId(mac->modeId);
843 if (box) {
844 cliDumpPrintLinef(dumpMask, equalsDefault, format,
846 box->permanentId,
847 mac->auxChannelIndex,
848 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
849 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
850 mac->modeLogic
856 static void cliAux(char *cmdline)
858 int i, val = 0;
859 const char *ptr;
861 if (isEmpty(cmdline)) {
862 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
863 } else {
864 ptr = cmdline;
865 i = atoi(ptr++);
866 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
867 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
868 uint8_t validArgumentCount = 0;
869 ptr = nextArg(ptr);
870 if (ptr) {
871 val = atoi(ptr);
872 const box_t *box = findBoxByPermanentId(val);
873 if (box) {
874 mac->modeId = box->boxId;
875 validArgumentCount++;
878 ptr = nextArg(ptr);
879 if (ptr) {
880 val = atoi(ptr);
881 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
882 mac->auxChannelIndex = val;
883 validArgumentCount++;
886 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
887 ptr = nextArg(ptr);
888 if (ptr) {
889 val = atoi(ptr);
890 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
891 mac->modeLogic = val;
892 validArgumentCount++;
895 if (validArgumentCount == 4) { // for backwards compatibility
896 mac->modeLogic = MODELOGIC_OR;
897 } else if (validArgumentCount != 5) {
898 memset(mac, 0, sizeof(modeActivationCondition_t));
900 cliPrintLinef( "aux %u %u %u %u %u %u",
902 mac->modeId,
903 mac->auxChannelIndex,
904 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
905 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
906 mac->modeLogic
908 } else {
909 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
914 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
916 const char *format = "serial %d %d %ld %ld %ld %ld";
917 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
918 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
919 continue;
921 bool equalsDefault = false;
922 if (serialConfigDefault) {
923 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
924 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
925 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
926 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
927 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
928 && serialConfig->portConfigs[i].blackbox_baudrateIndex == serialConfigDefault->portConfigs[i].blackbox_baudrateIndex;
929 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
930 serialConfigDefault->portConfigs[i].identifier,
931 serialConfigDefault->portConfigs[i].functionMask,
932 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
933 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
934 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
935 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
938 cliDumpPrintLinef(dumpMask, equalsDefault, format,
939 serialConfig->portConfigs[i].identifier,
940 serialConfig->portConfigs[i].functionMask,
941 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
942 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
943 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
944 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
949 static void cliSerial(char *cmdline)
951 const char *format = "serial %d %d %ld %ld %ld %ld";
952 if (isEmpty(cmdline)) {
953 printSerial(DUMP_MASTER, serialConfig(), NULL);
954 return;
956 serialPortConfig_t portConfig;
957 memset(&portConfig, 0 , sizeof(portConfig));
959 serialPortConfig_t *currentConfig;
961 uint8_t validArgumentCount = 0;
963 const char *ptr = cmdline;
965 int val = atoi(ptr++);
966 currentConfig = serialFindPortConfiguration(val);
967 if (currentConfig) {
968 portConfig.identifier = val;
969 validArgumentCount++;
972 ptr = nextArg(ptr);
973 if (ptr) {
974 val = atoi(ptr);
975 portConfig.functionMask = val & 0xFFFF;
976 validArgumentCount++;
979 for (int i = 0; i < 4; i ++) {
980 ptr = nextArg(ptr);
981 if (!ptr) {
982 break;
985 val = atoi(ptr);
987 uint8_t baudRateIndex = lookupBaudRateIndex(val);
988 if (baudRates[baudRateIndex] != (uint32_t) val) {
989 break;
992 switch (i) {
993 case 0:
994 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
995 continue;
997 portConfig.msp_baudrateIndex = baudRateIndex;
998 break;
999 case 1:
1000 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1001 continue;
1003 portConfig.gps_baudrateIndex = baudRateIndex;
1004 break;
1005 case 2:
1006 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1007 continue;
1009 portConfig.telemetry_baudrateIndex = baudRateIndex;
1010 break;
1011 case 3:
1012 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1013 continue;
1015 portConfig.blackbox_baudrateIndex = baudRateIndex;
1016 break;
1019 validArgumentCount++;
1022 if (validArgumentCount < 6) {
1023 cliShowParseError();
1024 return;
1027 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1029 cliDumpPrintLinef(0, false, format,
1030 portConfig.identifier,
1031 portConfig.functionMask,
1032 baudRates[portConfig.msp_baudrateIndex],
1033 baudRates[portConfig.gps_baudrateIndex],
1034 baudRates[portConfig.telemetry_baudrateIndex],
1035 baudRates[portConfig.blackbox_baudrateIndex]
1040 #ifndef SKIP_SERIAL_PASSTHROUGH
1041 #ifdef USE_PINIO
1042 static void cbCtrlLine(void *context, uint16_t ctrl)
1044 int pinioDtr = (int)(long)context;
1046 pinioSet(pinioDtr, !(ctrl & CTRL_LINE_STATE_DTR));
1048 #endif /* USE_PINIO */
1050 static void cliSerialPassthrough(char *cmdline)
1052 if (isEmpty(cmdline)) {
1053 cliShowParseError();
1054 return;
1057 int id = -1;
1058 uint32_t baud = 0;
1059 bool enableBaudCb = false;
1060 #ifdef USE_PINIO
1061 int pinioDtr = 0;
1062 #endif /* USE_PINIO */
1063 unsigned mode = 0;
1064 char *saveptr;
1065 char* tok = strtok_r(cmdline, " ", &saveptr);
1066 int index = 0;
1068 while (tok != NULL) {
1069 switch (index) {
1070 case 0:
1071 id = atoi(tok);
1072 break;
1073 case 1:
1074 baud = atoi(tok);
1075 break;
1076 case 2:
1077 if (strstr(tok, "rx") || strstr(tok, "RX"))
1078 mode |= MODE_RX;
1079 if (strstr(tok, "tx") || strstr(tok, "TX"))
1080 mode |= MODE_TX;
1081 break;
1082 #ifdef USE_PINIO
1083 case 3:
1084 pinioDtr = atoi(tok);
1085 break;
1086 #endif /* USE_PINIO */
1088 index++;
1089 tok = strtok_r(NULL, " ", &saveptr);
1092 if (baud == 0) {
1093 enableBaudCb = true;
1096 cliPrintf("Port %d ", id);
1097 serialPort_t *passThroughPort;
1098 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
1099 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
1100 if (enableBaudCb) {
1101 // Set default baud
1102 baud = 57600;
1105 if (!mode) {
1106 mode = MODE_RXTX;
1109 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
1110 baud, mode,
1111 SERIAL_NOT_INVERTED);
1112 if (!passThroughPort) {
1113 cliPrintLine("could not be opened.");
1114 return;
1117 if (enableBaudCb) {
1118 cliPrintf("opened, default baud = %d.\r\n", baud);
1119 } else {
1120 cliPrintf("opened, baud = %d.\r\n", baud);
1122 } else {
1123 passThroughPort = passThroughPortUsage->serialPort;
1124 // If the user supplied a mode, override the port's mode, otherwise
1125 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1126 // Set the baud rate if specified
1127 if (baud) {
1128 cliPrintf("already open, setting baud = %d.\n\r", baud);
1129 serialSetBaudRate(passThroughPort, baud);
1130 } else {
1131 cliPrintf("already open, baud = %d.\n\r", passThroughPort->baudRate);
1134 if (mode && passThroughPort->mode != mode) {
1135 cliPrintf("Mode changed from %d to %d.\r\n",
1136 passThroughPort->mode, mode);
1137 serialSetMode(passThroughPort, mode);
1140 // If this port has a rx callback associated we need to remove it now.
1141 // Otherwise no data will be pushed in the serial port buffer!
1142 if (passThroughPort->rxCallback) {
1143 passThroughPort->rxCallback = 0;
1147 // If no baud rate is specified allow to be set via USB
1148 if (enableBaudCb) {
1149 cliPrintLine("Baud rate change over USB enabled.");
1150 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1151 // baud rate over USB without setting it using the serialpassthrough command
1152 serialSetBaudRateCb(cliPort, serialSetBaudRate, passThroughPort);
1155 cliPrintLine("Forwarding, power cycle to exit.");
1157 #ifdef USE_PINIO
1158 // Register control line state callback
1159 if (pinioDtr) {
1160 serialSetCtrlLineStateCb(cliPort, cbCtrlLine, (void *)(intptr_t)(pinioDtr - 1));
1162 #endif /* USE_PINIO */
1164 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
1166 #endif
1168 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
1170 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1171 // print out adjustment ranges channel settings
1172 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1173 const adjustmentRange_t *ar = &adjustmentRanges[i];
1174 bool equalsDefault = false;
1175 if (defaultAdjustmentRanges) {
1176 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1177 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
1178 && ar->range.startStep == arDefault->range.startStep
1179 && ar->range.endStep == arDefault->range.endStep
1180 && ar->adjustmentFunction == arDefault->adjustmentFunction
1181 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
1182 && ar->adjustmentIndex == arDefault->adjustmentIndex
1183 && ar->adjustmentCenter == arDefault->adjustmentCenter
1184 && ar->adjustmentScale == arDefault->adjustmentScale;
1185 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1187 arDefault->adjustmentIndex,
1188 arDefault->auxChannelIndex,
1189 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1190 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1191 arDefault->adjustmentFunction,
1192 arDefault->auxSwitchChannelIndex,
1193 arDefault->adjustmentCenter,
1194 arDefault->adjustmentScale
1197 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1199 ar->adjustmentIndex,
1200 ar->auxChannelIndex,
1201 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1202 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1203 ar->adjustmentFunction,
1204 ar->auxSwitchChannelIndex,
1205 ar->adjustmentCenter,
1206 ar->adjustmentScale
1211 static void cliAdjustmentRange(char *cmdline)
1213 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1214 int i, val = 0;
1215 const char *ptr;
1217 if (isEmpty(cmdline)) {
1218 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
1219 } else {
1220 ptr = cmdline;
1221 i = atoi(ptr++);
1222 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1223 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1224 uint8_t validArgumentCount = 0;
1226 ptr = nextArg(ptr);
1227 if (ptr) {
1228 val = atoi(ptr);
1229 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1230 ar->adjustmentIndex = val;
1231 validArgumentCount++;
1234 ptr = nextArg(ptr);
1235 if (ptr) {
1236 val = atoi(ptr);
1237 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1238 ar->auxChannelIndex = val;
1239 validArgumentCount++;
1243 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1245 ptr = nextArg(ptr);
1246 if (ptr) {
1247 val = atoi(ptr);
1248 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1249 ar->adjustmentFunction = val;
1250 validArgumentCount++;
1253 ptr = nextArg(ptr);
1254 if (ptr) {
1255 val = atoi(ptr);
1256 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1257 ar->auxSwitchChannelIndex = val;
1258 validArgumentCount++;
1262 if (validArgumentCount != 6) {
1263 memset(ar, 0, sizeof(adjustmentRange_t));
1264 cliShowParseError();
1265 return;
1268 // Optional arguments
1269 ar->adjustmentCenter = 0;
1270 ar->adjustmentScale = 0;
1272 ptr = nextArg(ptr);
1273 if (ptr) {
1274 val = atoi(ptr);
1275 ar->adjustmentCenter = val;
1276 validArgumentCount++;
1278 ptr = nextArg(ptr);
1279 if (ptr) {
1280 val = atoi(ptr);
1281 ar->adjustmentScale = val;
1282 validArgumentCount++;
1284 cliDumpPrintLinef(0, false, format,
1286 ar->adjustmentIndex,
1287 ar->auxChannelIndex,
1288 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1289 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1290 ar->adjustmentFunction,
1291 ar->auxSwitchChannelIndex,
1292 ar->adjustmentCenter,
1293 ar->adjustmentScale
1296 } else {
1297 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1302 #ifndef USE_QUAD_MIXER_ONLY
1303 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer)
1305 const char *format = "mmix %d %s %s %s %s";
1306 char buf0[FTOA_BUFFER_LENGTH];
1307 char buf1[FTOA_BUFFER_LENGTH];
1308 char buf2[FTOA_BUFFER_LENGTH];
1309 char buf3[FTOA_BUFFER_LENGTH];
1310 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1311 if (customMotorMixer[i].throttle == 0.0f)
1312 break;
1313 const float thr = customMotorMixer[i].throttle;
1314 const float roll = customMotorMixer[i].roll;
1315 const float pitch = customMotorMixer[i].pitch;
1316 const float yaw = customMotorMixer[i].yaw;
1317 bool equalsDefault = false;
1318 if (defaultCustomMotorMixer) {
1319 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1320 const float rollDefault = defaultCustomMotorMixer[i].roll;
1321 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1322 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1323 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1325 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1327 ftoa(thrDefault, buf0),
1328 ftoa(rollDefault, buf1),
1329 ftoa(pitchDefault, buf2),
1330 ftoa(yawDefault, buf3));
1332 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1334 ftoa(thr, buf0),
1335 ftoa(roll, buf1),
1336 ftoa(pitch, buf2),
1337 ftoa(yaw, buf3));
1340 #endif // USE_QUAD_MIXER_ONLY
1342 static void cliMotorMix(char *cmdline)
1344 #ifdef USE_QUAD_MIXER_ONLY
1345 UNUSED(cmdline);
1346 #else
1347 int check = 0;
1348 uint8_t len;
1349 const char *ptr;
1351 if (isEmpty(cmdline)) {
1352 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1353 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1354 // erase custom mixer
1355 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1356 customMotorMixerMutable(i)->throttle = 0.0f;
1358 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1359 ptr = nextArg(cmdline);
1360 if (ptr) {
1361 len = strlen(ptr);
1362 for (uint32_t i = 0; ; i++) {
1363 if (mixerNames[i] == NULL) {
1364 cliPrintLine("Invalid name");
1365 break;
1367 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1368 mixerLoadMix(i, customMotorMixerMutable(0));
1369 cliPrintLinef("Loaded %s", mixerNames[i]);
1370 cliMotorMix("");
1371 break;
1375 } else {
1376 ptr = cmdline;
1377 uint32_t i = atoi(ptr); // get motor number
1378 if (i < MAX_SUPPORTED_MOTORS) {
1379 ptr = nextArg(ptr);
1380 if (ptr) {
1381 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1382 check++;
1384 ptr = nextArg(ptr);
1385 if (ptr) {
1386 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1387 check++;
1389 ptr = nextArg(ptr);
1390 if (ptr) {
1391 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1392 check++;
1394 ptr = nextArg(ptr);
1395 if (ptr) {
1396 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1397 check++;
1399 if (check != 4) {
1400 cliShowParseError();
1401 } else {
1402 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1404 } else {
1405 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1408 #endif
1411 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1413 const char *format = "rxrange %u %u %u";
1414 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1415 bool equalsDefault = false;
1416 if (defaultChannelRangeConfigs) {
1417 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1418 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1419 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1421 defaultChannelRangeConfigs[i].min,
1422 defaultChannelRangeConfigs[i].max
1425 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1427 channelRangeConfigs[i].min,
1428 channelRangeConfigs[i].max
1433 static void cliRxRange(char *cmdline)
1435 const char *format = "rxrange %u %u %u";
1436 int i, validArgumentCount = 0;
1437 const char *ptr;
1439 if (isEmpty(cmdline)) {
1440 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1441 } else if (strcasecmp(cmdline, "reset") == 0) {
1442 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1443 } else {
1444 ptr = cmdline;
1445 i = atoi(ptr);
1446 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1447 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1449 ptr = nextArg(ptr);
1450 if (ptr) {
1451 rangeMin = atoi(ptr);
1452 validArgumentCount++;
1455 ptr = nextArg(ptr);
1456 if (ptr) {
1457 rangeMax = atoi(ptr);
1458 validArgumentCount++;
1461 if (validArgumentCount != 2) {
1462 cliShowParseError();
1463 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1464 cliShowParseError();
1465 } else {
1466 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1467 channelRangeConfig->min = rangeMin;
1468 channelRangeConfig->max = rangeMax;
1469 cliDumpPrintLinef(0, false, format,
1471 channelRangeConfig->min,
1472 channelRangeConfig->max
1476 } else {
1477 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1482 #ifdef USE_LED_STRIP
1483 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1485 const char *format = "led %u %s";
1486 char ledConfigBuffer[20];
1487 char ledConfigDefaultBuffer[20];
1488 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1489 ledConfig_t ledConfig = ledConfigs[i];
1490 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1491 bool equalsDefault = false;
1492 if (defaultLedConfigs) {
1493 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1494 equalsDefault = ledConfig == ledConfigDefault;
1495 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1496 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1498 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1502 static void cliLed(char *cmdline)
1504 const char *format = "led %u %s";
1505 char ledConfigBuffer[20];
1506 int i;
1507 const char *ptr;
1509 if (isEmpty(cmdline)) {
1510 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1511 } else {
1512 ptr = cmdline;
1513 i = atoi(ptr);
1514 if (i < LED_MAX_STRIP_LENGTH) {
1515 ptr = nextArg(cmdline);
1516 if (parseLedStripConfig(i, ptr)) {
1517 generateLedConfig((ledConfig_t *)&ledStripConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1518 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1519 } else {
1520 cliShowParseError();
1522 } else {
1523 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1528 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1530 const char *format = "color %u %d,%u,%u";
1531 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1532 const hsvColor_t *color = &colors[i];
1533 bool equalsDefault = false;
1534 if (defaultColors) {
1535 const hsvColor_t *colorDefault = &defaultColors[i];
1536 equalsDefault = color->h == colorDefault->h
1537 && color->s == colorDefault->s
1538 && color->v == colorDefault->v;
1539 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1541 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1545 static void cliColor(char *cmdline)
1547 const char *format = "color %u %d,%u,%u";
1548 if (isEmpty(cmdline)) {
1549 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1550 } else {
1551 const char *ptr = cmdline;
1552 const int i = atoi(ptr);
1553 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1554 ptr = nextArg(cmdline);
1555 if (parseColor(i, ptr)) {
1556 const hsvColor_t *color = &ledStripConfig()->colors[i];
1557 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1558 } else {
1559 cliShowParseError();
1561 } else {
1562 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1567 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1569 const char *format = "mode_color %u %u %u";
1570 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1571 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1572 int colorIndex = ledStripConfig->modeColors[i].color[j];
1573 bool equalsDefault = false;
1574 if (defaultLedStripConfig) {
1575 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1576 equalsDefault = colorIndex == colorIndexDefault;
1577 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1579 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1583 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1584 const int colorIndex = ledStripConfig->specialColors.color[j];
1585 bool equalsDefault = false;
1586 if (defaultLedStripConfig) {
1587 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1588 equalsDefault = colorIndex == colorIndexDefault;
1589 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1591 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1594 const int ledStripAuxChannel = ledStripConfig->ledstrip_aux_channel;
1595 bool equalsDefault = false;
1596 if (defaultLedStripConfig) {
1597 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1598 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1599 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1601 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1604 static void cliModeColor(char *cmdline)
1606 if (isEmpty(cmdline)) {
1607 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1608 } else {
1609 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1610 int args[ARGS_COUNT];
1611 int argNo = 0;
1612 char *saveptr;
1613 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1614 while (ptr && argNo < ARGS_COUNT) {
1615 args[argNo++] = atoi(ptr);
1616 ptr = strtok_r(NULL, " ", &saveptr);
1619 if (ptr != NULL || argNo != ARGS_COUNT) {
1620 cliShowParseError();
1621 return;
1624 int modeIdx = args[MODE];
1625 int funIdx = args[FUNCTION];
1626 int color = args[COLOR];
1627 if (!setModeColor(modeIdx, funIdx, color)) {
1628 cliShowParseError();
1629 return;
1631 // values are validated
1632 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1635 #endif
1637 #ifdef USE_SERVOS
1638 static void printServo(uint8_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams)
1640 // print out servo settings
1641 const char *format = "servo %u %d %d %d %d %d";
1642 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1643 const servoParam_t *servoConf = &servoParams[i];
1644 bool equalsDefault = false;
1645 if (defaultServoParams) {
1646 const servoParam_t *defaultServoConf = &defaultServoParams[i];
1647 equalsDefault = servoConf->min == defaultServoConf->min
1648 && servoConf->max == defaultServoConf->max
1649 && servoConf->middle == defaultServoConf->middle
1650 && servoConf->rate == defaultServoConf->rate
1651 && servoConf->forwardFromChannel == defaultServoConf->forwardFromChannel;
1652 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1654 defaultServoConf->min,
1655 defaultServoConf->max,
1656 defaultServoConf->middle,
1657 defaultServoConf->rate,
1658 defaultServoConf->forwardFromChannel
1661 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1663 servoConf->min,
1664 servoConf->max,
1665 servoConf->middle,
1666 servoConf->rate,
1667 servoConf->forwardFromChannel
1670 // print servo directions
1671 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1672 const char *format = "smix reverse %d %d r";
1673 const servoParam_t *servoConf = &servoParams[i];
1674 const servoParam_t *servoConfDefault = &defaultServoParams[i];
1675 if (defaultServoParams) {
1676 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
1677 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1678 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
1679 if (servoConfDefault->reversedSources & (1 << channel)) {
1680 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
1682 if (servoConf->reversedSources & (1 << channel)) {
1683 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
1686 } else {
1687 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1688 if (servoConf->reversedSources & (1 << channel)) {
1689 cliDumpPrintLinef(dumpMask, true, format, i , channel);
1696 static void cliServo(char *cmdline)
1698 const char *format = "servo %u %d %d %d %d %d";
1699 enum { SERVO_ARGUMENT_COUNT = 6 };
1700 int16_t arguments[SERVO_ARGUMENT_COUNT];
1702 servoParam_t *servo;
1704 int i;
1705 char *ptr;
1707 if (isEmpty(cmdline)) {
1708 printServo(DUMP_MASTER, servoParams(0), NULL);
1709 } else {
1710 int validArgumentCount = 0;
1712 ptr = cmdline;
1714 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1716 // If command line doesn't fit the format, don't modify the config
1717 while (*ptr) {
1718 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1719 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1720 cliShowParseError();
1721 return;
1724 arguments[validArgumentCount++] = atoi(ptr);
1726 do {
1727 ptr++;
1728 } while (*ptr >= '0' && *ptr <= '9');
1729 } else if (*ptr == ' ') {
1730 ptr++;
1731 } else {
1732 cliShowParseError();
1733 return;
1737 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
1739 i = arguments[INDEX];
1741 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1742 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1743 cliShowParseError();
1744 return;
1747 servo = servoParamsMutable(i);
1749 if (
1750 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
1751 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
1752 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1753 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1754 arguments[RATE] < -100 || arguments[RATE] > 100 ||
1755 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1757 cliShowParseError();
1758 return;
1761 servo->min = arguments[MIN];
1762 servo->max = arguments[MAX];
1763 servo->middle = arguments[MIDDLE];
1764 servo->rate = arguments[RATE];
1765 servo->forwardFromChannel = arguments[FORWARD];
1767 cliDumpPrintLinef(0, false, format,
1769 servo->min,
1770 servo->max,
1771 servo->middle,
1772 servo->rate,
1773 servo->forwardFromChannel
1778 #endif
1780 #ifdef USE_SERVOS
1781 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1783 const char *format = "smix %d %d %d %d %d %d %d %d";
1784 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1785 const servoMixer_t customServoMixer = customServoMixers[i];
1786 if (customServoMixer.rate == 0) {
1787 break;
1790 bool equalsDefault = false;
1791 if (defaultCustomServoMixers) {
1792 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1793 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1794 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1795 && customServoMixer.rate == customServoMixerDefault.rate
1796 && customServoMixer.speed == customServoMixerDefault.speed
1797 && customServoMixer.min == customServoMixerDefault.min
1798 && customServoMixer.max == customServoMixerDefault.max
1799 && customServoMixer.box == customServoMixerDefault.box;
1801 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1803 customServoMixerDefault.targetChannel,
1804 customServoMixerDefault.inputSource,
1805 customServoMixerDefault.rate,
1806 customServoMixerDefault.speed,
1807 customServoMixerDefault.min,
1808 customServoMixerDefault.max,
1809 customServoMixerDefault.box
1812 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1814 customServoMixer.targetChannel,
1815 customServoMixer.inputSource,
1816 customServoMixer.rate,
1817 customServoMixer.speed,
1818 customServoMixer.min,
1819 customServoMixer.max,
1820 customServoMixer.box
1824 cliPrintLinefeed();
1827 static void cliServoMix(char *cmdline)
1829 int args[8], check = 0;
1830 int len = strlen(cmdline);
1832 if (len == 0) {
1833 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
1834 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1835 // erase custom mixer
1836 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1837 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1838 servoParamsMutable(i)->reversedSources = 0;
1840 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1841 const char *ptr = nextArg(cmdline);
1842 if (ptr) {
1843 len = strlen(ptr);
1844 for (uint32_t i = 0; ; i++) {
1845 if (mixerNames[i] == NULL) {
1846 cliPrintLine("Invalid name");
1847 break;
1849 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1850 servoMixerLoadMix(i);
1851 cliPrintLinef("Loaded %s", mixerNames[i]);
1852 cliServoMix("");
1853 break;
1857 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
1858 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
1859 char *ptr = strchr(cmdline, ' ');
1861 len = strlen(ptr);
1862 if (len == 0) {
1863 cliPrintf("s");
1864 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1865 cliPrintf("\ti%d", inputSource);
1866 cliPrintLinefeed();
1868 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
1869 cliPrintf("%d", servoIndex);
1870 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1871 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
1872 cliPrintLinefeed();
1874 return;
1877 char *saveptr;
1878 ptr = strtok_r(ptr, " ", &saveptr);
1879 while (ptr != NULL && check < ARGS_COUNT - 1) {
1880 args[check++] = atoi(ptr);
1881 ptr = strtok_r(NULL, " ", &saveptr);
1884 if (ptr == NULL || check != ARGS_COUNT - 1) {
1885 cliShowParseError();
1886 return;
1889 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
1890 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
1891 && (*ptr == 'r' || *ptr == 'n')) {
1892 if (*ptr == 'r')
1893 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
1894 else
1895 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
1896 } else
1897 cliShowParseError();
1899 cliServoMix("reverse");
1900 } else {
1901 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
1902 char *saveptr;
1903 char *ptr = strtok_r(cmdline, " ", &saveptr);
1904 while (ptr != NULL && check < ARGS_COUNT) {
1905 args[check++] = atoi(ptr);
1906 ptr = strtok_r(NULL, " ", &saveptr);
1909 if (ptr != NULL || check != ARGS_COUNT) {
1910 cliShowParseError();
1911 return;
1914 int32_t i = args[RULE];
1915 if (i >= 0 && i < MAX_SERVO_RULES &&
1916 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1917 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1918 args[RATE] >= -100 && args[RATE] <= 100 &&
1919 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1920 args[MIN] >= 0 && args[MIN] <= 100 &&
1921 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
1922 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
1923 customServoMixersMutable(i)->targetChannel = args[TARGET];
1924 customServoMixersMutable(i)->inputSource = args[INPUT];
1925 customServoMixersMutable(i)->rate = args[RATE];
1926 customServoMixersMutable(i)->speed = args[SPEED];
1927 customServoMixersMutable(i)->min = args[MIN];
1928 customServoMixersMutable(i)->max = args[MAX];
1929 customServoMixersMutable(i)->box = args[BOX];
1930 cliServoMix("");
1931 } else {
1932 cliShowParseError();
1936 #endif
1938 #ifdef USE_SDCARD
1940 static void cliWriteBytes(const uint8_t *buffer, int count)
1942 while (count > 0) {
1943 cliWrite(*buffer);
1944 buffer++;
1945 count--;
1949 static void cliSdInfo(char *cmdline)
1951 UNUSED(cmdline);
1953 cliPrint("SD card: ");
1955 if (!sdcard_isInserted()) {
1956 cliPrintLine("None inserted");
1957 return;
1960 if (!sdcard_isInitialized()) {
1961 cliPrintLine("Startup failed");
1962 return;
1965 const sdcardMetadata_t *metadata = sdcard_getMetadata();
1967 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1968 metadata->manufacturerID,
1969 metadata->numBlocks / 2, /* One block is half a kB */
1970 metadata->productionMonth,
1971 metadata->productionYear,
1972 metadata->productRevisionMajor,
1973 metadata->productRevisionMinor
1976 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
1978 cliPrint("'\r\n" "Filesystem: ");
1980 switch (afatfs_getFilesystemState()) {
1981 case AFATFS_FILESYSTEM_STATE_READY:
1982 cliPrint("Ready");
1983 break;
1984 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
1985 cliPrint("Initializing");
1986 break;
1987 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
1988 case AFATFS_FILESYSTEM_STATE_FATAL:
1989 cliPrint("Fatal");
1991 switch (afatfs_getLastError()) {
1992 case AFATFS_ERROR_BAD_MBR:
1993 cliPrint(" - no FAT MBR partitions");
1994 break;
1995 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
1996 cliPrint(" - bad FAT header");
1997 break;
1998 case AFATFS_ERROR_GENERIC:
1999 case AFATFS_ERROR_NONE:
2000 ; // Nothing more detailed to print
2001 break;
2003 break;
2005 cliPrintLinefeed();
2008 #endif
2010 #ifdef USE_FLASHFS
2012 static void cliFlashInfo(char *cmdline)
2014 const flashGeometry_t *layout = flashfsGetGeometry();
2016 UNUSED(cmdline);
2018 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
2019 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
2023 static void cliFlashErase(char *cmdline)
2025 UNUSED(cmdline);
2027 #ifndef MINIMAL_CLI
2028 uint32_t i = 0;
2029 cliPrintLine("Erasing, please wait ... ");
2030 #else
2031 cliPrintLine("Erasing,");
2032 #endif
2034 bufWriterFlush(cliWriter);
2035 flashfsEraseCompletely();
2037 while (!flashfsIsReady()) {
2038 #ifndef MINIMAL_CLI
2039 cliPrintf(".");
2040 if (i++ > 120) {
2041 i=0;
2042 cliPrintLinefeed();
2045 bufWriterFlush(cliWriter);
2046 #endif
2047 delay(100);
2049 beeper(BEEPER_BLACKBOX_ERASE);
2050 cliPrintLinefeed();
2051 cliPrintLine("Done.");
2054 #ifdef USE_FLASH_TOOLS
2056 static void cliFlashWrite(char *cmdline)
2058 const uint32_t address = atoi(cmdline);
2059 const char *text = strchr(cmdline, ' ');
2061 if (!text) {
2062 cliShowParseError();
2063 } else {
2064 flashfsSeekAbs(address);
2065 flashfsWrite((uint8_t*)text, strlen(text), true);
2066 flashfsFlushSync();
2068 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2072 static void cliFlashRead(char *cmdline)
2074 uint32_t address = atoi(cmdline);
2076 const char *nextArg = strchr(cmdline, ' ');
2078 if (!nextArg) {
2079 cliShowParseError();
2080 } else {
2081 uint32_t length = atoi(nextArg);
2083 cliPrintLinef("Reading %u bytes at %u:", length, address);
2085 uint8_t buffer[32];
2086 while (length > 0) {
2087 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2089 for (int i = 0; i < bytesRead; i++) {
2090 cliWrite(buffer[i]);
2093 length -= bytesRead;
2094 address += bytesRead;
2096 if (bytesRead == 0) {
2097 //Assume we reached the end of the volume or something fatal happened
2098 break;
2101 cliPrintLinefeed();
2105 #endif
2106 #endif
2108 #ifdef USE_VTX_CONTROL
2109 static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault)
2111 // print out vtx channel settings
2112 const char *format = "vtx %u %u %u %u %u %u";
2113 bool equalsDefault = false;
2114 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2115 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2116 if (vtxConfigDefault) {
2117 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2118 equalsDefault = cac->auxChannelIndex == cacDefault->auxChannelIndex
2119 && cac->band == cacDefault->band
2120 && cac->channel == cacDefault->channel
2121 && cac->range.startStep == cacDefault->range.startStep
2122 && cac->range.endStep == cacDefault->range.endStep;
2123 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2125 cacDefault->auxChannelIndex,
2126 cacDefault->band,
2127 cacDefault->channel,
2128 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2129 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2132 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2134 cac->auxChannelIndex,
2135 cac->band,
2136 cac->channel,
2137 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2138 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2143 static void cliVtx(char *cmdline)
2145 const char *format = "vtx %u %u %u %u %u %u";
2146 int i, val = 0;
2147 const char *ptr;
2149 if (isEmpty(cmdline)) {
2150 printVtx(DUMP_MASTER, vtxConfig(), NULL);
2151 } else {
2152 ptr = cmdline;
2153 i = atoi(ptr++);
2154 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2155 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2156 uint8_t validArgumentCount = 0;
2157 ptr = nextArg(ptr);
2158 if (ptr) {
2159 val = atoi(ptr);
2160 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2161 cac->auxChannelIndex = val;
2162 validArgumentCount++;
2165 ptr = nextArg(ptr);
2166 if (ptr) {
2167 val = atoi(ptr);
2168 // FIXME Use VTX API to get min/max
2169 if (val >= VTX_SETTINGS_MIN_BAND && val <= VTX_SETTINGS_MAX_BAND) {
2170 cac->band = val;
2171 validArgumentCount++;
2174 ptr = nextArg(ptr);
2175 if (ptr) {
2176 val = atoi(ptr);
2177 // FIXME Use VTX API to get min/max
2178 if (val >= VTX_SETTINGS_MIN_CHANNEL && val <= VTX_SETTINGS_MAX_CHANNEL) {
2179 cac->channel = val;
2180 validArgumentCount++;
2183 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2185 if (validArgumentCount != 5) {
2186 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2187 cliShowParseError();
2188 } else {
2189 cliDumpPrintLinef(0, false, format,
2191 cac->auxChannelIndex,
2192 cac->band,
2193 cac->channel,
2194 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2195 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2198 } else {
2199 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2204 #endif // VTX_CONTROL
2206 static void printName(uint8_t dumpMask, const pilotConfig_t *pilotConfig)
2208 const bool equalsDefault = strlen(pilotConfig->name) == 0;
2209 cliDumpPrintLinef(dumpMask, equalsDefault, "name %s", equalsDefault ? emptyName : pilotConfig->name);
2212 static void cliName(char *cmdline)
2214 const unsigned int len = strlen(cmdline);
2215 if (len > 0) {
2216 memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
2217 if (strncmp(cmdline, emptyName, len)) {
2218 strncpy(pilotConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
2221 printName(DUMP_MASTER, pilotConfig());
2224 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2226 const uint32_t mask = featureConfig->enabledFeatures;
2227 const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2228 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
2229 if (strcmp(featureNames[i], emptryString) != 0) { //Skip unused
2230 const char *format = "feature -%s";
2231 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2232 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2235 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
2236 if (strcmp(featureNames[i], emptryString) != 0) { //Skip unused
2237 const char *format = "feature %s";
2238 if (defaultMask & (1 << i)) {
2239 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2241 if (mask & (1 << i)) {
2242 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2248 static void cliFeature(char *cmdline)
2250 uint32_t len = strlen(cmdline);
2251 uint32_t mask = featureMask();
2253 if (len == 0) {
2254 cliPrint("Enabled: ");
2255 for (uint32_t i = 0; ; i++) {
2256 if (featureNames[i] == NULL)
2257 break;
2258 if (mask & (1 << i))
2259 cliPrintf("%s ", featureNames[i]);
2261 cliPrintLinefeed();
2262 } else if (strncasecmp(cmdline, "list", len) == 0) {
2263 cliPrint("Available:");
2264 for (uint32_t i = 0; ; i++) {
2265 if (featureNames[i] == NULL)
2266 break;
2267 if (strcmp(featureNames[i], emptryString) != 0) //Skip unused
2268 cliPrintf(" %s", featureNames[i]);
2270 cliPrintLinefeed();
2271 return;
2272 } else {
2273 bool remove = false;
2274 if (cmdline[0] == '-') {
2275 // remove feature
2276 remove = true;
2277 cmdline++; // skip over -
2278 len--;
2281 for (uint32_t i = 0; ; i++) {
2282 if (featureNames[i] == NULL) {
2283 cliPrintLine("Invalid name");
2284 break;
2287 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
2289 mask = 1 << i;
2290 #ifndef USE_GPS
2291 if (mask & FEATURE_GPS) {
2292 cliPrintLine("unavailable");
2293 break;
2295 #endif
2296 #ifndef USE_RANGEFINDER
2297 if (mask & FEATURE_RANGEFINDER) {
2298 cliPrintLine("unavailable");
2299 break;
2301 #endif
2302 if (remove) {
2303 featureClear(mask);
2304 cliPrint("Disabled");
2305 } else {
2306 featureSet(mask);
2307 cliPrint("Enabled");
2309 cliPrintLinef(" %s", featureNames[i]);
2310 break;
2316 #ifdef USE_BEEPER
2317 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
2319 const uint8_t beeperCount = beeperTableEntryCount();
2320 const uint32_t mask = beeperConfig->beeper_off_flags;
2321 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
2322 for (int32_t i = 0; i < beeperCount - 2; i++) {
2323 const char *formatOff = "beeper -%s";
2324 const char *formatOn = "beeper %s";
2325 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
2326 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & beeperModeMask, mask & beeperModeMask ? formatOn : formatOff, beeperNameForTableIndex(i));
2327 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & beeperModeMask, mask & beeperModeMask ? formatOff : formatOn, beeperNameForTableIndex(i));
2331 static void cliBeeper(char *cmdline)
2333 uint32_t len = strlen(cmdline);
2334 uint8_t beeperCount = beeperTableEntryCount();
2335 uint32_t mask = getBeeperOffMask();
2337 if (len == 0) {
2338 cliPrintf("Disabled:");
2339 for (int32_t i = 0; ; i++) {
2340 if (i == beeperCount - 2) {
2341 if (mask == 0)
2342 cliPrint(" none");
2343 break;
2346 if (mask & beeperModeMaskForTableIndex(i))
2347 cliPrintf(" %s", beeperNameForTableIndex(i));
2349 cliPrintLinefeed();
2350 } else if (strncasecmp(cmdline, "list", len) == 0) {
2351 cliPrint("Available:");
2352 for (uint32_t i = 0; i < beeperCount; i++)
2353 cliPrintf(" %s", beeperNameForTableIndex(i));
2354 cliPrintLinefeed();
2355 return;
2356 } else {
2357 bool remove = false;
2358 if (cmdline[0] == '-') {
2359 remove = true; // this is for beeper OFF condition
2360 cmdline++;
2361 len--;
2364 for (uint32_t i = 0; ; i++) {
2365 if (i == beeperCount) {
2366 cliPrintLine("Invalid name");
2367 break;
2369 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
2370 if (remove) { // beeper off
2371 if (i == BEEPER_ALL-1)
2372 beeperOffSetAll(beeperCount-2);
2373 else
2374 if (i == BEEPER_PREFERENCE-1)
2375 setBeeperOffMask(getPreferredBeeperOffMask());
2376 else {
2377 beeperOffSet(beeperModeMaskForTableIndex(i));
2379 cliPrint("Disabled");
2381 else { // beeper on
2382 if (i == BEEPER_ALL-1)
2383 beeperOffClearAll();
2384 else
2385 if (i == BEEPER_PREFERENCE-1)
2386 setPreferredBeeperOffMask(getBeeperOffMask());
2387 else {
2388 beeperOffClear(beeperModeMaskForTableIndex(i));
2390 cliPrint("Enabled");
2392 cliPrintLinef(" %s", beeperNameForTableIndex(i));
2393 break;
2398 #endif
2400 void cliFrSkyBind(char *cmdline){
2401 UNUSED(cmdline);
2402 switch (rxConfig()->rx_spi_protocol) {
2403 #ifdef USE_RX_FRSKY_SPI
2404 case RX_SPI_FRSKY_D:
2405 case RX_SPI_FRSKY_X:
2406 frSkySpiBind();
2408 cliPrint("Binding...");
2410 break;
2411 #endif
2412 default:
2413 cliPrint("Not supported.");
2415 break;
2419 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
2421 bool equalsDefault = true;
2422 char buf[16];
2423 char bufDefault[16];
2424 uint32_t i;
2425 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2426 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
2427 if (defaultRxConfig) {
2428 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
2429 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
2432 buf[i] = '\0';
2434 const char *formatMap = "map %s";
2435 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
2436 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
2440 static void cliMap(char *cmdline)
2442 uint32_t i;
2443 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
2445 uint32_t len = strlen(cmdline);
2446 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
2448 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2449 buf[i] = toupper((unsigned char)cmdline[i]);
2451 buf[i] = '\0';
2453 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2454 buf[i] = toupper((unsigned char)cmdline[i]);
2456 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
2457 continue;
2459 cliShowParseError();
2460 return;
2462 parseRcChannels(buf, rxConfigMutable());
2463 } else if (len > 0) {
2464 cliShowParseError();
2465 return;
2468 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2469 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
2472 buf[i] = '\0';
2473 cliPrintLinef("map %s", buf);
2476 static char *skipSpace(char *buffer)
2478 while (*(buffer) == ' ') {
2479 buffer++;
2482 return buffer;
2485 static char *checkCommand(char *cmdLine, const char *command)
2487 if (!strncasecmp(cmdLine, command, strlen(command)) // command names match
2488 && (isspace((unsigned)cmdLine[strlen(command)]) || cmdLine[strlen(command)] == 0)) {
2489 return skipSpace(cmdLine + strlen(command) + 1);
2490 } else {
2491 return 0;
2495 static void cliRebootEx(bool bootLoader)
2497 cliPrint("\r\nRebooting");
2498 bufWriterFlush(cliWriter);
2499 waitForSerialPortToFinishTransmitting(cliPort);
2500 stopPwmAllMotors();
2501 if (bootLoader) {
2502 systemResetToBootloader();
2503 return;
2505 systemReset();
2508 static void cliReboot(void)
2510 cliRebootEx(false);
2513 static void cliBootloader(char *cmdLine)
2515 UNUSED(cmdLine);
2517 cliPrintHashLine("restarting in bootloader mode");
2518 cliRebootEx(true);
2521 static void cliExit(char *cmdline)
2523 UNUSED(cmdline);
2525 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2526 bufWriterFlush(cliWriter);
2528 *cliBuffer = '\0';
2529 bufferIndex = 0;
2530 cliMode = 0;
2531 // incase a motor was left running during motortest, clear it here
2532 mixerResetDisarmedMotors();
2533 cliReboot();
2535 cliWriter = NULL;
2538 #ifdef USE_GPS
2539 static void cliGpsPassthrough(char *cmdline)
2541 UNUSED(cmdline);
2543 gpsEnablePassthrough(cliPort);
2545 #endif
2547 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
2548 static void cliPrintGyroRegisters(uint8_t whichSensor)
2550 tfp_printf("# WHO_AM_I 0x%X\r\n", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
2551 tfp_printf("# CONFIG 0x%X\r\n", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
2552 tfp_printf("# GYRO_CONFIG 0x%X\r\n", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
2555 static void cliDumpGyroRegisters(char *cmdline)
2557 #ifdef USE_DUAL_GYRO
2558 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
2559 tfp_printf("\r\n# Gyro 1\r\n");
2560 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
2562 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
2563 tfp_printf("\r\n# Gyro 2\r\n");
2564 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
2566 #else
2567 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
2568 #endif // USE_DUAL_GYRO
2569 UNUSED(cmdline);
2571 #endif
2574 static int parseOutputIndex(char *pch, bool allowAllEscs) {
2575 int outputIndex = atoi(pch);
2576 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
2577 tfp_printf("Using output %d.\r\n", outputIndex);
2578 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
2579 tfp_printf("Using all outputs.\r\n");
2580 } else {
2581 tfp_printf("Invalid output number. Range: 0 %d.\r\n", getMotorCount() - 1);
2583 return -1;
2586 return outputIndex;
2589 #if defined(USE_DSHOT)
2590 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2592 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
2593 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
2594 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
2596 enum {
2597 ESC_INFO_KISS_V1,
2598 ESC_INFO_KISS_V2,
2599 ESC_INFO_BLHELI32
2602 #define ESC_INFO_VERSION_POSITION 12
2604 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
2606 bool escInfoReceived = false;
2607 if (bytesRead > ESC_INFO_VERSION_POSITION) {
2608 uint8_t escInfoVersion;
2609 uint8_t frameLength;
2610 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
2611 escInfoVersion = ESC_INFO_BLHELI32;
2612 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
2613 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
2614 escInfoVersion = ESC_INFO_KISS_V2;
2615 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
2616 } else {
2617 escInfoVersion = ESC_INFO_KISS_V1;
2618 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
2621 if (bytesRead == frameLength) {
2622 escInfoReceived = true;
2624 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
2625 uint8_t firmwareVersion = 0;
2626 uint8_t firmwareSubVersion = 0;
2627 uint8_t escType = 0;
2628 switch (escInfoVersion) {
2629 case ESC_INFO_KISS_V1:
2630 firmwareVersion = escInfoBuffer[12];
2631 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
2632 escType = (escInfoBuffer[13] & 0xe0) >> 5;
2634 break;
2635 case ESC_INFO_KISS_V2:
2636 firmwareVersion = escInfoBuffer[13];
2637 firmwareSubVersion = escInfoBuffer[14];
2638 escType = escInfoBuffer[15];
2640 break;
2641 case ESC_INFO_BLHELI32:
2642 firmwareVersion = escInfoBuffer[13];
2643 firmwareSubVersion = escInfoBuffer[14];
2644 escType = escInfoBuffer[15];
2646 break;
2649 cliPrint("ESC Type: ");
2650 switch (escInfoVersion) {
2651 case ESC_INFO_KISS_V1:
2652 case ESC_INFO_KISS_V2:
2653 switch (escType) {
2654 case 1:
2655 cliPrintLine("KISS8A");
2657 break;
2658 case 2:
2659 cliPrintLine("KISS16A");
2661 break;
2662 case 3:
2663 cliPrintLine("KISS24A");
2665 break;
2666 case 5:
2667 cliPrintLine("KISS Ultralite");
2669 break;
2670 default:
2671 cliPrintLine("unknown");
2673 break;
2676 break;
2677 case ESC_INFO_BLHELI32:
2679 char *escType = (char *)(escInfoBuffer + 31);
2680 escType[32] = 0;
2681 cliPrintLine(escType);
2684 break;
2687 cliPrint("MCU Serial No: 0x");
2688 for (int i = 0; i < 12; i++) {
2689 if (i && (i % 3 == 0)) {
2690 cliPrint("-");
2692 cliPrintf("%02x", escInfoBuffer[i]);
2694 cliPrintLinefeed();
2696 switch (escInfoVersion) {
2697 case ESC_INFO_KISS_V1:
2698 case ESC_INFO_KISS_V2:
2699 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
2701 break;
2702 case ESC_INFO_BLHELI32:
2703 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
2705 break;
2707 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
2708 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
2709 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
2710 if (escInfoVersion == ESC_INFO_BLHELI32) {
2711 uint8_t setting = escInfoBuffer[18];
2712 cliPrint("Low voltage Limit: ");
2713 switch (setting) {
2714 case 0:
2715 cliPrintLine("off");
2717 break;
2718 case 255:
2719 cliPrintLine("unsupported");
2721 break;
2722 default:
2723 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
2725 break;
2728 setting = escInfoBuffer[19];
2729 cliPrint("Current Limit: ");
2730 switch (setting) {
2731 case 0:
2732 cliPrintLine("off");
2734 break;
2735 case 255:
2736 cliPrintLine("unsupported");
2738 break;
2739 default:
2740 cliPrintLinef("%d", setting);
2742 break;
2745 for (int i = 0; i < 4; i++) {
2746 setting = escInfoBuffer[i + 20];
2747 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
2751 } else {
2752 cliPrintLine("Checksum Error.");
2757 if (!escInfoReceived) {
2758 cliPrintLine("No Info.");
2762 static void executeEscInfoCommand(uint8_t escIndex)
2764 cliPrintLinef("Info for ESC %d:", escIndex);
2766 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
2768 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
2770 pwmWriteDshotCommand(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO);
2772 delay(10);
2774 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
2776 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
2778 static void cliDshotProg(char *cmdline)
2780 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
2781 cliShowParseError();
2783 return;
2786 char *saveptr;
2787 char *pch = strtok_r(cmdline, " ", &saveptr);
2788 int pos = 0;
2789 int escIndex = 0;
2790 bool firstCommand = true;
2791 while (pch != NULL) {
2792 switch (pos) {
2793 case 0:
2794 escIndex = parseOutputIndex(pch, true);
2795 if (escIndex == -1) {
2796 return;
2799 break;
2800 default:
2802 int command = atoi(pch);
2803 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
2804 if (firstCommand) {
2805 pwmDisableMotors();
2807 if (command == DSHOT_CMD_ESC_INFO) {
2808 delay(5); // Wait for potential ESC telemetry transmission to finish
2809 } else {
2810 delay(1);
2813 firstCommand = false;
2816 if (command != DSHOT_CMD_ESC_INFO) {
2817 pwmWriteDshotCommand(escIndex, getMotorCount(), command);
2818 } else {
2819 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2820 if (feature(FEATURE_ESC_SENSOR)) {
2821 if (escIndex != ALL_MOTORS) {
2822 executeEscInfoCommand(escIndex);
2823 } else {
2824 for (uint8_t i = 0; i < getMotorCount(); i++) {
2825 executeEscInfoCommand(i);
2828 } else
2829 #endif
2831 cliPrintLine("Not supported.");
2835 cliPrintLinef("Command Sent: %d", command);
2837 if (command <= 5) {
2838 delay(20); // wait for sound output to finish
2840 } else {
2841 cliPrintLinef("Invalid command. Range: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
2845 break;
2848 pos++;
2849 pch = strtok_r(NULL, " ", &saveptr);
2852 pwmEnableMotors();
2854 #endif // USE_DSHOT
2856 #ifdef USE_ESCSERIAL
2857 static void cliEscPassthrough(char *cmdline)
2859 if (isEmpty(cmdline)) {
2860 cliShowParseError();
2862 return;
2865 char *saveptr;
2866 char *pch = strtok_r(cmdline, " ", &saveptr);
2867 int pos = 0;
2868 uint8_t mode = 0;
2869 int escIndex = 0;
2870 while (pch != NULL) {
2871 switch (pos) {
2872 case 0:
2873 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
2874 mode = PROTOCOL_SIMONK;
2875 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
2876 mode = PROTOCOL_BLHELI;
2877 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
2878 mode = PROTOCOL_KISS;
2879 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
2880 mode = PROTOCOL_KISSALL;
2881 } else {
2882 cliShowParseError();
2884 return;
2886 break;
2887 case 1:
2888 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
2889 if (escIndex == -1) {
2890 return;
2893 break;
2894 default:
2895 cliShowParseError();
2897 return;
2899 break;
2902 pos++;
2903 pch = strtok_r(NULL, " ", &saveptr);
2906 escEnablePassthrough(cliPort, escIndex, mode);
2908 #endif
2910 #ifndef USE_QUAD_MIXER_ONLY
2911 static void cliMixer(char *cmdline)
2913 int len;
2915 len = strlen(cmdline);
2917 if (len == 0) {
2918 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
2919 return;
2920 } else if (strncasecmp(cmdline, "list", len) == 0) {
2921 cliPrint("Available:");
2922 for (uint32_t i = 0; ; i++) {
2923 if (mixerNames[i] == NULL)
2924 break;
2925 cliPrintf(" %s", mixerNames[i]);
2927 cliPrintLinefeed();
2928 return;
2931 for (uint32_t i = 0; ; i++) {
2932 if (mixerNames[i] == NULL) {
2933 cliPrintLine("Invalid name");
2934 return;
2936 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
2937 mixerConfigMutable()->mixerMode = i + 1;
2938 break;
2942 cliMixer("");
2944 #endif
2946 static void cliMotor(char *cmdline)
2948 if (isEmpty(cmdline)) {
2949 cliShowParseError();
2951 return;
2954 int motorIndex = 0;
2955 int motorValue = 0;
2957 char *saveptr;
2958 char *pch = strtok_r(cmdline, " ", &saveptr);
2959 int index = 0;
2960 while (pch != NULL) {
2961 switch (index) {
2962 case 0:
2963 motorIndex = parseOutputIndex(pch, true);
2964 if (motorIndex == -1) {
2965 return;
2968 break;
2969 case 1:
2970 motorValue = atoi(pch);
2972 break;
2974 index++;
2975 pch = strtok_r(NULL, " ", &saveptr);
2978 if (index == 2) {
2979 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
2980 cliShowArgumentRangeError("value", 1000, 2000);
2981 } else {
2982 uint32_t motorOutputValue = convertExternalToMotor(motorValue);
2984 if (motorIndex != ALL_MOTORS) {
2985 motor_disarmed[motorIndex] = motorOutputValue;
2987 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
2988 } else {
2989 for (int i = 0; i < getMotorCount(); i++) {
2990 motor_disarmed[i] = motorOutputValue;
2993 cliPrintLinef("all motors: %d", motorOutputValue);
2996 } else {
2997 cliShowParseError();
3001 #ifndef MINIMAL_CLI
3002 static void cliPlaySound(char *cmdline)
3004 int i;
3005 const char *name;
3006 static int lastSoundIdx = -1;
3008 if (isEmpty(cmdline)) {
3009 i = lastSoundIdx + 1; //next sound index
3010 if ((name=beeperNameForTableIndex(i)) == NULL) {
3011 while (true) { //no name for index; try next one
3012 if (++i >= beeperTableEntryCount())
3013 i = 0; //if end then wrap around to first entry
3014 if ((name=beeperNameForTableIndex(i)) != NULL)
3015 break; //if name OK then play sound below
3016 if (i == lastSoundIdx + 1) { //prevent infinite loop
3017 cliPrintLine("Error playing sound");
3018 return;
3022 } else { //index value was given
3023 i = atoi(cmdline);
3024 if ((name=beeperNameForTableIndex(i)) == NULL) {
3025 cliPrintLinef("No sound for index %d", i);
3026 return;
3029 lastSoundIdx = i;
3030 beeperSilence();
3031 cliPrintLinef("Playing sound %d: %s", i, name);
3032 beeper(beeperModeForTableIndex(i));
3034 #endif
3036 static void cliProfile(char *cmdline)
3038 if (isEmpty(cmdline)) {
3039 cliPrintLinef("profile %d", getCurrentPidProfileIndex());
3040 return;
3041 } else {
3042 const int i = atoi(cmdline);
3043 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3044 systemConfigMutable()->pidProfileIndex = i;
3045 cliProfile("");
3050 static void cliRateProfile(char *cmdline)
3052 if (isEmpty(cmdline)) {
3053 cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
3054 return;
3055 } else {
3056 const int i = atoi(cmdline);
3057 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
3058 changeControlRateProfile(i);
3059 cliRateProfile("");
3064 static void cliDumpPidProfile(uint8_t pidProfileIndex, uint8_t dumpMask)
3066 if (pidProfileIndex >= MAX_PROFILE_COUNT) {
3067 // Faulty values
3068 return;
3070 changePidProfile(pidProfileIndex);
3071 cliPrintHashLine("profile");
3072 cliProfile("");
3073 cliPrintLinefeed();
3074 dumpAllValues(PROFILE_VALUE, dumpMask);
3077 static void cliDumpRateProfile(uint8_t rateProfileIndex, uint8_t dumpMask)
3079 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
3080 // Faulty values
3081 return;
3083 changeControlRateProfile(rateProfileIndex);
3084 cliPrintHashLine("rateprofile");
3085 cliRateProfile("");
3086 cliPrintLinefeed();
3087 dumpAllValues(PROFILE_RATE_VALUE, dumpMask);
3090 static void cliSave(char *cmdline)
3092 UNUSED(cmdline);
3094 cliPrintHashLine("saving");
3095 writeEEPROM();
3096 cliReboot();
3099 static void cliDefaults(char *cmdline)
3101 bool saveConfigs;
3103 if (isEmpty(cmdline)) {
3104 saveConfigs = true;
3105 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
3106 saveConfigs = false;
3107 } else {
3108 return;
3111 cliPrintHashLine("resetting to defaults");
3112 resetConfigs();
3113 if (saveConfigs) {
3114 cliSave(NULL);
3118 void cliPrintVarDefault(const clivalue_t *value)
3120 const pgRegistry_t *pg = pgFind(value->pgn);
3121 if (pg) {
3122 const char *defaultFormat = "Default value: ";
3123 const int valueOffset = getValueOffset(value);
3124 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
3125 if (!equalsDefault) {
3126 cliPrintf(defaultFormat, value->name);
3127 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
3128 cliPrintLinefeed();
3133 STATIC_UNIT_TESTED void cliGet(char *cmdline)
3135 const clivalue_t *val;
3136 int matchedCommands = 0;
3138 backupAndResetConfigs();
3140 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3141 if (strcasestr(valueTable[i].name, cmdline)) {
3142 val = &valueTable[i];
3143 if (matchedCommands > 0) {
3144 cliPrintLinefeed();
3146 cliPrintf("%s = ", valueTable[i].name);
3147 cliPrintVar(val, 0);
3148 cliPrintLinefeed();
3149 cliPrintVarRange(val);
3150 cliPrintVarDefault(val);
3151 matchedCommands++;
3155 restoreConfigs();
3157 if (matchedCommands) {
3158 return;
3161 cliPrintLine("Invalid name");
3164 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
3166 while (*(bufEnd - 1) == ' ') {
3167 bufEnd--;
3170 return bufEnd - bufBegin;
3173 STATIC_UNIT_TESTED void cliSet(char *cmdline)
3175 const uint32_t len = strlen(cmdline);
3176 char *eqptr;
3178 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
3179 cliPrintLine("Current settings: ");
3181 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3182 const clivalue_t *val = &valueTable[i];
3183 cliPrintf("%s = ", valueTable[i].name);
3184 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3185 cliPrintLinefeed();
3187 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
3188 // has equals
3190 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
3192 // skip the '=' and any ' ' characters
3193 eqptr++;
3194 eqptr = skipSpace(eqptr);
3196 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3197 const clivalue_t *val = &valueTable[i];
3199 // ensure exact match when setting to prevent setting variables with shorter names
3200 if (strncasecmp(cmdline, val->name, strlen(val->name)) == 0 && variableNameLength == strlen(val->name)) {
3202 bool valueChanged = false;
3203 int16_t value = 0;
3204 switch (val->type & VALUE_MODE_MASK) {
3205 case MODE_DIRECT: {
3206 int16_t value = atoi(eqptr);
3208 if (value >= val->config.minmax.min && value <= val->config.minmax.max) {
3209 cliSetVar(val, value);
3210 valueChanged = true;
3214 break;
3215 case MODE_LOOKUP:
3216 case MODE_BITSET: {
3217 int tableIndex;
3218 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
3219 tableIndex = TABLE_OFF_ON;
3220 } else {
3221 tableIndex = val->config.lookup.tableIndex;
3223 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
3224 bool matched = false;
3225 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
3226 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
3228 if (matched) {
3229 value = tableValueIndex;
3231 cliSetVar(val, value);
3232 valueChanged = true;
3237 break;
3239 case MODE_ARRAY: {
3240 const uint8_t arrayLength = val->config.array.length;
3241 char *valPtr = eqptr;
3243 int i = 0;
3244 while (i < arrayLength && valPtr != NULL) {
3245 // skip spaces
3246 valPtr = skipSpace(valPtr);
3248 // process substring starting at valPtr
3249 // note: no need to copy substrings for atoi()
3250 // it stops at the first character that cannot be converted...
3251 switch (val->type & VALUE_TYPE_MASK) {
3252 default:
3253 case VAR_UINT8:
3255 // fetch data pointer
3256 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
3257 // store value
3258 *data = (uint8_t)atoi((const char*) valPtr);
3261 break;
3262 case VAR_INT8:
3264 // fetch data pointer
3265 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
3266 // store value
3267 *data = (int8_t)atoi((const char*) valPtr);
3270 break;
3271 case VAR_UINT16:
3273 // fetch data pointer
3274 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
3275 // store value
3276 *data = (uint16_t)atoi((const char*) valPtr);
3279 break;
3280 case VAR_INT16:
3282 // fetch data pointer
3283 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
3284 // store value
3285 *data = (int16_t)atoi((const char*) valPtr);
3288 break;
3291 // find next comma (or end of string)
3292 valPtr = strchr(valPtr, ',') + 1;
3294 i++;
3298 // mark as changed
3299 valueChanged = true;
3301 break;
3305 if (valueChanged) {
3306 cliPrintf("%s set to ", val->name);
3307 cliPrintVar(val, 0);
3308 } else {
3309 cliPrintLine("Invalid value");
3310 cliPrintVarRange(val);
3313 return;
3316 cliPrintLine("Invalid name");
3317 } else {
3318 // no equals, check for matching variables.
3319 cliGet(cmdline);
3323 static void cliStatus(char *cmdline)
3325 UNUSED(cmdline);
3327 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3329 #ifdef USE_RTC_TIME
3330 char buf[FORMATTED_DATE_TIME_BUFSIZE];
3331 dateTime_t dt;
3332 if (rtcGetDateTime(&dt)) {
3333 dateTimeFormatLocal(buf, &dt);
3334 cliPrintLinef("Current Time: %s", buf);
3336 #endif
3338 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
3340 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3342 #ifdef USE_ADC_INTERNAL
3343 uint16_t vrefintMv = getVrefMv();
3344 int16_t coretemp = getCoreTemperatureCelsius();
3345 cliPrintf(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
3346 #endif
3348 #if defined(USE_SENSOR_NAMES)
3349 const uint32_t detectedSensorsMask = sensorsMask();
3350 for (uint32_t i = 0; ; i++) {
3351 if (sensorTypeNames[i] == NULL) {
3352 break;
3354 const uint32_t mask = (1 << i);
3355 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3356 const uint8_t sensorHardwareIndex = detectedSensors[i];
3357 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3358 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3359 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
3360 cliPrintf(".%c", acc.dev.revisionCode);
3364 #endif /* USE_SENSOR_NAMES */
3365 cliPrintLinefeed();
3367 #ifdef USE_SDCARD
3368 cliSdInfo(NULL);
3369 #endif
3371 #ifdef USE_I2C
3372 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3373 #else
3374 const uint16_t i2cErrorCounter = 0;
3375 #endif
3377 #ifdef STACK_CHECK
3378 cliPrintf("Stack used: %d, ", stackUsedSize());
3379 #endif
3380 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
3381 #ifdef EEPROM_IN_RAM
3382 #define CONFIG_SIZE EEPROM_SIZE
3383 #else
3384 #define CONFIG_SIZE (&__config_end - &__config_start)
3385 #endif
3386 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), CONFIG_SIZE);
3388 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
3389 const int rxRate = currentRxRefreshRate == 0 ? 0 : (int)(1000000.0f / ((float)currentRxRefreshRate));
3390 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3391 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
3392 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
3393 cliPrint("Arming disable flags:");
3394 armingDisableFlags_e flags = getArmingDisableFlags();
3395 while (flags) {
3396 const int bitpos = ffs(flags) - 1;
3397 flags &= ~(1 << bitpos);
3398 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
3400 cliPrintLinefeed();
3403 #ifndef SKIP_TASK_STATISTICS
3404 static void cliTasks(char *cmdline)
3406 UNUSED(cmdline);
3407 int maxLoadSum = 0;
3408 int averageLoadSum = 0;
3410 #ifndef MINIMAL_CLI
3411 if (systemConfig()->task_statistics) {
3412 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
3413 } else {
3414 cliPrintLine("Task list");
3416 #endif
3417 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3418 cfTaskInfo_t taskInfo;
3419 getTaskInfo(taskId, &taskInfo);
3420 if (taskInfo.isEnabled) {
3421 int taskFrequency;
3422 int subTaskFrequency = 0;
3423 if (taskId == TASK_GYROPID) {
3424 subTaskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3425 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
3426 if (pidConfig()->pid_process_denom > 1) {
3427 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3428 } else {
3429 taskFrequency = subTaskFrequency;
3430 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
3432 } else {
3433 taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3434 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3436 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3437 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3438 if (taskId != TASK_SERIAL) {
3439 maxLoadSum += maxLoad;
3440 averageLoadSum += averageLoad;
3442 if (systemConfig()->task_statistics) {
3443 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
3444 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
3445 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
3446 } else {
3447 cliPrintLinef("%6d", taskFrequency);
3449 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
3450 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
3454 if (systemConfig()->task_statistics) {
3455 cfCheckFuncInfo_t checkFuncInfo;
3456 getCheckFuncInfo(&checkFuncInfo);
3457 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
3458 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3461 #endif
3463 static void cliVersion(char *cmdline)
3465 UNUSED(cmdline);
3467 cliPrintLinef("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
3468 FC_FIRMWARE_NAME,
3469 targetName,
3470 systemConfig()->boardIdentifier,
3471 FC_VERSION_STRING,
3472 buildDate,
3473 buildTime,
3474 shortGitRevision,
3475 MSP_API_VERSION_STRING
3479 #if defined(USE_RESOURCE_MGMT)
3481 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
3483 typedef struct {
3484 const uint8_t owner;
3485 pgn_t pgn;
3486 uint8_t stride;
3487 uint8_t offset;
3488 const uint8_t maxIndex;
3489 } cliResourceValue_t;
3491 // Handy macros for keeping the table tidy.
3492 // DEFS : Single entry
3493 // DEFA : Array of uint8_t (stride = 1)
3494 // DEFW : Wider stride case; array of structs.
3496 #define DEFS(owner, pgn, type, member) \
3497 { owner, pgn, 0, offsetof(type, member), 0 }
3499 #define DEFA(owner, pgn, type, member, max) \
3500 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
3502 #define DEFW(owner, pgn, type, member, max) \
3503 { owner, pgn, sizeof(type), offsetof(type, member), max }
3505 const cliResourceValue_t resourceTable[] = {
3506 #ifdef USE_BEEPER
3507 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
3508 #endif
3509 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
3510 #ifdef USE_SERVOS
3511 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
3512 #endif
3513 #if defined(USE_PWM) || defined(USE_PPM)
3514 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
3515 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
3516 #endif
3517 #ifdef USE_RANGEFINDER_HCSR04
3518 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
3519 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
3520 #endif
3521 #ifdef USE_LED_STRIP
3522 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
3523 #endif
3524 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
3525 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
3526 #ifdef USE_INVERTER
3527 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
3528 #endif
3529 #ifdef USE_I2C
3530 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
3531 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
3532 #endif
3533 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
3534 #ifdef USE_SPEKTRUM_BIND
3535 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
3536 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
3537 #endif
3538 #ifdef USE_TRANSPONDER
3539 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
3540 #endif
3541 #ifdef USE_SPI
3542 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
3543 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
3544 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
3545 #endif
3546 #ifdef USE_ESCSERIAL
3547 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
3548 #endif
3549 #ifdef USE_CAMERA_CONTROL
3550 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
3551 #endif
3552 #ifdef USE_ADC
3553 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
3554 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
3555 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
3556 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
3557 #endif
3558 #ifdef USE_BARO
3559 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
3560 #endif
3561 #ifdef USE_MAG
3562 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
3563 #endif
3564 #ifdef USE_SDCARD
3565 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
3566 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
3567 #endif
3568 #ifdef USE_PINIO
3569 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
3570 #endif
3571 #if defined(USE_USB_MSC)
3572 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
3573 #endif
3574 #ifdef USE_FLASH
3575 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
3576 #endif
3577 #ifdef USE_MAX7456
3578 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
3579 #endif
3582 #undef DEFS
3583 #undef DEFA
3584 #undef DEFW
3586 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
3588 const pgRegistry_t* rec = pgFind(value.pgn);
3589 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
3592 static void printResource(uint8_t dumpMask)
3594 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
3595 const char* owner = ownerNames[resourceTable[i].owner];
3596 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
3597 const void *currentConfig;
3598 const void *defaultConfig;
3599 if (configIsInCopy) {
3600 currentConfig = pg->copy;
3601 defaultConfig = pg->address;
3602 } else {
3603 currentConfig = pg->address;
3604 defaultConfig = NULL;
3607 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
3608 const ioTag_t ioTag = *((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
3609 const ioTag_t ioTagDefault = *((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
3611 bool equalsDefault = ioTag == ioTagDefault;
3612 const char *format = "resource %s %d %c%02d";
3613 const char *formatUnassigned = "resource %s %d NONE";
3614 if (!ioTagDefault) {
3615 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3616 } else {
3617 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
3619 if (!ioTag) {
3620 if (!(dumpMask & HIDE_UNUSED)) {
3621 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3623 } else {
3624 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
3630 static void printResourceOwner(uint8_t owner, uint8_t index)
3632 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
3634 if (resourceTable[owner].maxIndex > 0) {
3635 cliPrintf(" %d", RESOURCE_INDEX(index));
3639 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
3641 if (!newTag) {
3642 return;
3645 const char * format = "\r\nNOTE: %c%02d already assigned to ";
3646 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
3647 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
3648 ioTag_t *tag = getIoTag(resourceTable[r], i);
3649 if (*tag == newTag) {
3650 bool cleared = false;
3651 if (r == resourceIndex) {
3652 if (i == index) {
3653 continue;
3655 *tag = IO_TAG_NONE;
3656 cleared = true;
3659 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
3661 printResourceOwner(r, i);
3663 if (cleared) {
3664 cliPrintf(". ");
3665 printResourceOwner(r, i);
3666 cliPrintf(" disabled");
3669 cliPrintLine(".");
3675 static bool strToPin(char *pch, ioTag_t *tag)
3677 if (strcasecmp(pch, "NONE") == 0) {
3678 *tag = IO_TAG_NONE;
3679 return true;
3680 } else {
3681 unsigned pin = 0;
3682 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
3684 if (port < 8) {
3685 pch++;
3686 pin = atoi(pch);
3687 if (pin < 16) {
3688 *tag = DEFIO_TAG_MAKE(port, pin);
3689 return true;
3693 return false;
3696 static void cliResource(char *cmdline)
3698 int len = strlen(cmdline);
3700 if (len == 0) {
3701 printResource(DUMP_MASTER | HIDE_UNUSED);
3703 return;
3704 } else if (strncasecmp(cmdline, "list", len) == 0) {
3705 #ifdef MINIMAL_CLI
3706 cliPrintLine("IO");
3707 #else
3708 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
3709 cliRepeat('-', 20);
3710 #endif
3711 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3712 const char* owner;
3713 owner = ownerNames[ioRecs[i].owner];
3715 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
3716 if (ioRecs[i].index > 0) {
3717 cliPrintf(" %d", ioRecs[i].index);
3719 cliPrintLinefeed();
3722 #ifndef MINIMAL_CLI
3723 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
3724 #endif
3726 return;
3729 uint8_t resourceIndex = 0;
3730 int index = 0;
3731 char *pch = NULL;
3732 char *saveptr;
3734 pch = strtok_r(cmdline, " ", &saveptr);
3735 for (resourceIndex = 0; ; resourceIndex++) {
3736 if (resourceIndex >= ARRAYLEN(resourceTable)) {
3737 cliPrintLine("Invalid");
3738 return;
3741 if (strncasecmp(pch, ownerNames[resourceTable[resourceIndex].owner], len) == 0) {
3742 break;
3746 pch = strtok_r(NULL, " ", &saveptr);
3747 index = atoi(pch);
3749 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
3750 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
3751 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
3752 return;
3754 index -= 1;
3756 pch = strtok_r(NULL, " ", &saveptr);
3759 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
3761 if (strlen(pch) > 0) {
3762 if (strToPin(pch, tag)) {
3763 if (*tag == IO_TAG_NONE) {
3764 #ifdef MINIMAL_CLI
3765 cliPrintLine("Freed");
3766 #else
3767 cliPrintLine("Resource is freed");
3768 #endif
3769 return;
3770 } else {
3771 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
3772 if (rec) {
3773 resourceCheck(resourceIndex, index, *tag);
3774 #ifdef MINIMAL_CLI
3775 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
3776 #else
3777 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
3778 #endif
3779 } else {
3780 cliShowParseError();
3782 return;
3787 cliShowParseError();
3790 static void printDma(void)
3792 cliPrintLinefeed();
3794 #ifdef MINIMAL_CLI
3795 cliPrintLine("DMA:");
3796 #else
3797 cliPrintLine("Currently active DMA:");
3798 cliRepeat('-', 20);
3799 #endif
3800 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
3801 const char* owner;
3802 owner = ownerNames[dmaGetOwner(i)];
3804 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
3805 uint8_t resourceIndex = dmaGetResourceIndex(i);
3806 if (resourceIndex > 0) {
3807 cliPrintLinef(" %s %d", owner, resourceIndex);
3808 } else {
3809 cliPrintLinef(" %s", owner);
3814 static void cliDma(char* cmdLine)
3816 UNUSED(cmdLine);
3817 printDma();
3819 #endif /* USE_RESOURCE_MGMT */
3821 #ifdef USE_TIMER_MGMT
3823 static void printTimer(uint8_t dumpMask)
3825 cliPrintLine("# examples: ");
3826 const char *format = "timer %c%02d %d";
3827 cliPrint("#");
3828 cliPrintLinef(format, 'A', 1, 1);
3830 cliPrint("#");
3831 cliPrintLinef(format, 'A', 1, 0);
3833 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
3835 const ioTag_t ioTag = timerIOConfig(i)->ioTag;
3836 const uint8_t timerIndex = timerIOConfig(i)->index;
3838 if (!ioTag) {
3839 continue;
3842 if (timerIndex != 0 && !(dumpMask & HIDE_UNUSED)) {
3843 cliDumpPrintLinef(dumpMask, false, format,
3844 IO_GPIOPortIdxByTag(ioTag) + 'A',
3845 IO_GPIOPinIdxByTag(ioTag),
3846 timerIndex
3852 static void cliTimer(char *cmdline)
3854 int len = strlen(cmdline);
3856 if (len == 0) {
3857 printTimer(DUMP_MASTER | HIDE_UNUSED);
3858 return;
3859 } else if (strncasecmp(cmdline, "list", len) == 0) {
3860 printTimer(DUMP_MASTER);
3861 return;
3864 char *pch = NULL;
3865 char *saveptr;
3866 int timerIOIndex = -1;
3868 ioTag_t ioTag = 0;
3869 pch = strtok_r(cmdline, " ", &saveptr);
3870 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
3871 goto error;
3874 /* find existing entry, or go for next available */
3875 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
3876 if (timerIOConfig(i)->ioTag == ioTag) {
3877 timerIOIndex = i;
3878 break;
3881 /* first available empty slot */
3882 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
3883 timerIOIndex = i;
3887 if (timerIOIndex < 0) {
3888 cliPrintLine("Error: out of index");
3889 return;
3892 uint8_t timerIndex = 0;
3893 pch = strtok_r(NULL, " ", &saveptr);
3894 if (pch) {
3895 if (strcasecmp(pch, "list") == 0) {
3896 /* output the list of available options */
3897 uint8_t index = 1;
3898 for (unsigned i = 0; i < USABLE_TIMER_CHANNEL_COUNT; i++) {
3899 if (timerHardware[i].tag == ioTag) {
3900 cliPrintLinef("# %d. TIM%d CH%d",
3901 index,
3902 timerGetTIMNumber(timerHardware[i].tim),
3903 CC_INDEX_FROM_CHANNEL(timerHardware[i].channel)
3905 index++;
3908 return;
3909 } else if (strcasecmp(pch, "none") == 0) {
3910 goto success;
3911 } else {
3912 timerIndex = atoi(pch);
3914 } else {
3915 goto error;
3918 success:
3919 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == 0 ? IO_TAG_NONE : ioTag;
3920 timerIOConfigMutable(timerIOIndex)->index = timerIndex;
3922 cliPrintLine("Success");
3923 return;
3925 error:
3926 cliShowParseError();
3928 #endif
3930 static void printConfig(char *cmdline, bool doDiff)
3932 uint8_t dumpMask = DUMP_MASTER;
3933 char *options;
3934 if ((options = checkCommand(cmdline, "master"))) {
3935 dumpMask = DUMP_MASTER; // only
3936 } else if ((options = checkCommand(cmdline, "profile"))) {
3937 dumpMask = DUMP_PROFILE; // only
3938 } else if ((options = checkCommand(cmdline, "rates"))) {
3939 dumpMask = DUMP_RATES; // only
3940 } else if ((options = checkCommand(cmdline, "all"))) {
3941 dumpMask = DUMP_ALL; // all profiles and rates
3942 } else {
3943 options = cmdline;
3946 if (doDiff) {
3947 dumpMask = dumpMask | DO_DIFF;
3950 backupAndResetConfigs();
3951 if (checkCommand(options, "defaults")) {
3952 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
3955 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
3956 cliPrintHashLine("version");
3957 cliVersion(NULL);
3959 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
3960 cliPrintHashLine("reset configuration to default settings");
3961 cliPrint("defaults nosave");
3962 cliPrintLinefeed();
3965 cliPrintHashLine("name");
3966 printName(dumpMask, &pilotConfig_Copy);
3968 #ifdef USE_RESOURCE_MGMT
3969 cliPrintHashLine("resources");
3970 printResource(dumpMask);
3971 #endif
3973 #ifndef USE_QUAD_MIXER_ONLY
3974 cliPrintHashLine("mixer");
3975 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
3976 const char *formatMixer = "mixer %s";
3977 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
3978 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
3980 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
3982 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0));
3984 #ifdef USE_SERVOS
3985 cliPrintHashLine("servo");
3986 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
3988 cliPrintHashLine("servo mix");
3989 // print custom servo mixer if exists
3990 cliDumpPrintLinef(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n");
3991 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
3992 #endif
3993 #endif
3995 cliPrintHashLine("feature");
3996 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
3998 #ifdef USE_BEEPER
3999 cliPrintHashLine("beeper");
4000 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
4001 #endif
4003 cliPrintHashLine("map");
4004 printMap(dumpMask, &rxConfig_Copy, rxConfig());
4006 cliPrintHashLine("serial");
4007 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
4009 #ifdef USE_LED_STRIP
4010 cliPrintHashLine("led");
4011 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
4013 cliPrintHashLine("color");
4014 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
4016 cliPrintHashLine("mode_color");
4017 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
4018 #endif
4020 cliPrintHashLine("aux");
4021 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
4023 cliPrintHashLine("adjrange");
4024 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
4026 cliPrintHashLine("rxrange");
4027 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
4029 #ifdef USE_VTX_CONTROL
4030 cliPrintHashLine("vtx");
4031 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig());
4032 #endif
4034 cliPrintHashLine("rxfail");
4035 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0));
4037 cliPrintHashLine("master");
4038 dumpAllValues(MASTER_VALUE, dumpMask);
4040 if (dumpMask & DUMP_ALL) {
4041 const uint8_t pidProfileIndexSave = systemConfig_Copy.pidProfileIndex;
4042 for (uint32_t pidProfileIndex = 0; pidProfileIndex < MAX_PROFILE_COUNT; pidProfileIndex++) {
4043 cliDumpPidProfile(pidProfileIndex, dumpMask);
4045 changePidProfile(pidProfileIndexSave);
4046 cliPrintHashLine("restore original profile selection");
4047 cliProfile("");
4049 const uint8_t controlRateProfileIndexSave = systemConfig_Copy.activeRateProfile;
4050 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
4051 cliDumpRateProfile(rateIndex, dumpMask);
4053 changeControlRateProfile(controlRateProfileIndexSave);
4054 cliPrintHashLine("restore original rateprofile selection");
4055 cliRateProfile("");
4057 cliPrintHashLine("save configuration");
4058 cliPrint("save");
4059 } else {
4060 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
4062 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
4066 if (dumpMask & DUMP_PROFILE) {
4067 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
4070 if (dumpMask & DUMP_RATES) {
4071 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
4073 // restore configs from copies
4074 restoreConfigs();
4077 static void cliDump(char *cmdline)
4079 printConfig(cmdline, false);
4082 static void cliDiff(char *cmdline)
4084 printConfig(cmdline, true);
4087 #ifdef USE_USB_MSC
4088 static void cliMsc(char *cmdline)
4090 UNUSED(cmdline);
4092 if (false
4093 #ifdef USE_SDCARD
4094 || sdcard_isFunctional()
4095 #endif
4096 #ifdef USE_FLASHFS
4097 || flashfsGetSize() > 0
4098 #endif
4100 cliPrintHashLine("restarting in mass storage mode");
4101 cliPrint("\r\nRebooting");
4102 bufWriterFlush(cliWriter);
4103 delay(1000);
4104 waitForSerialPortToFinishTransmitting(cliPort);
4105 stopPwmAllMotors();
4106 if (mpuResetFn) {
4107 mpuResetFn();
4110 #ifdef STM32F7
4111 *((__IO uint32_t*) BKPSRAM_BASE + 16) = MSC_MAGIC;
4112 #elif defined(STM32F4)
4113 *((uint32_t *)0x2001FFF0) = MSC_MAGIC;
4114 #endif
4116 __disable_irq();
4117 NVIC_SystemReset();
4118 } else {
4119 cliPrint("\r\nStorage not present or failed to initialize!");
4120 bufWriterFlush(cliWriter);
4123 #endif
4125 typedef struct {
4126 const char *name;
4127 #ifndef MINIMAL_CLI
4128 const char *description;
4129 const char *args;
4130 #endif
4131 void (*func)(char *cmdline);
4132 } clicmd_t;
4134 #ifndef MINIMAL_CLI
4135 #define CLI_COMMAND_DEF(name, description, args, method) \
4137 name , \
4138 description , \
4139 args , \
4140 method \
4142 #else
4143 #define CLI_COMMAND_DEF(name, description, args, method) \
4145 name, \
4146 method \
4148 #endif
4150 static void cliHelp(char *cmdline);
4152 // should be sorted a..z for bsearch()
4153 const clicmd_t cmdTable[] = {
4154 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
4155 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
4156 #ifdef USE_BEEPER
4157 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
4158 "\t<+|->[name]", cliBeeper),
4159 #endif
4160 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL, cliBootloader),
4161 #ifdef USE_LED_STRIP
4162 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
4163 #endif
4164 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults),
4165 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|all] {defaults}", cliDiff),
4166 #ifdef USE_RESOURCE_MGMT
4167 CLI_COMMAND_DEF("dma", "list dma utilisation", NULL, cliDma),
4168 #endif
4169 #ifdef USE_DSHOT
4170 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
4171 #endif
4172 CLI_COMMAND_DEF("dump", "dump configuration",
4173 "[master|profile|rates|all] {defaults}", cliDump),
4174 #ifdef USE_ESCSERIAL
4175 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
4176 #endif
4177 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
4178 CLI_COMMAND_DEF("feature", "configure features",
4179 "list\r\n"
4180 "\t<+|->[name]", cliFeature),
4181 #ifdef USE_FLASHFS
4182 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
4183 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
4184 #ifdef USE_FLASH_TOOLS
4185 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
4186 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
4187 #endif
4188 #endif
4189 #ifdef USE_RX_FRSKY_SPI
4190 CLI_COMMAND_DEF("frsky_bind", "initiate binding for FrSky SPI RX", NULL, cliFrSkyBind),
4191 #endif
4192 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
4193 #ifdef USE_GPS
4194 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
4195 #endif
4196 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
4197 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
4198 #endif
4199 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
4200 #ifdef USE_LED_STRIP
4201 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
4202 #endif
4203 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
4204 #ifndef USE_QUAD_MIXER_ONLY
4205 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
4206 #endif
4207 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
4208 #ifdef USE_LED_STRIP
4209 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
4210 #endif
4211 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
4212 #ifdef USE_USB_MSC
4213 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
4214 #endif
4215 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
4216 #ifndef MINIMAL_CLI
4217 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
4218 #endif
4219 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
4220 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
4221 #ifdef USE_RESOURCE_MGMT
4222 CLI_COMMAND_DEF("resource", "show/set resources", NULL, cliResource),
4223 #endif
4224 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
4225 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
4226 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
4227 #ifdef USE_SDCARD
4228 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
4229 #endif
4230 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
4231 #ifndef SKIP_SERIAL_PASSTHROUGH
4232 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [DTR PINIO]: passthrough to serial", cliSerialPassthrough),
4233 #endif
4234 #ifdef USE_SERVOS
4235 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
4236 #endif
4237 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
4238 #ifdef USE_SERVOS
4239 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
4240 "\treset\r\n"
4241 "\tload <mixer>\r\n"
4242 "\treverse <servo> <source> r|n", cliServoMix),
4243 #endif
4244 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
4245 #ifndef SKIP_TASK_STATISTICS
4246 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
4247 #endif
4248 #ifdef USE_TIMER_MGMT
4249 CLI_COMMAND_DEF("timer", "show timer configuration", NULL, cliTimer),
4250 #endif
4251 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
4252 #ifdef USE_VTX_CONTROL
4253 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
4254 #endif
4257 static void cliHelp(char *cmdline)
4259 UNUSED(cmdline);
4261 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
4262 cliPrint(cmdTable[i].name);
4263 #ifndef MINIMAL_CLI
4264 if (cmdTable[i].description) {
4265 cliPrintf(" - %s", cmdTable[i].description);
4267 if (cmdTable[i].args) {
4268 cliPrintf("\r\n\t%s", cmdTable[i].args);
4270 #endif
4271 cliPrintLinefeed();
4275 void cliProcess(void)
4277 if (!cliWriter) {
4278 return;
4281 // Be a little bit tricky. Flush the last inputs buffer, if any.
4282 bufWriterFlush(cliWriter);
4284 while (serialRxBytesWaiting(cliPort)) {
4285 uint8_t c = serialRead(cliPort);
4286 if (c == '\t' || c == '?') {
4287 // do tab completion
4288 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
4289 uint32_t i = bufferIndex;
4290 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4291 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
4292 continue;
4293 if (!pstart)
4294 pstart = cmd;
4295 pend = cmd;
4297 if (pstart) { /* Buffer matches one or more commands */
4298 for (; ; bufferIndex++) {
4299 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
4300 break;
4301 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
4302 /* Unambiguous -- append a space */
4303 cliBuffer[bufferIndex++] = ' ';
4304 cliBuffer[bufferIndex] = '\0';
4305 break;
4307 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
4310 if (!bufferIndex || pstart != pend) {
4311 /* Print list of ambiguous matches */
4312 cliPrint("\r\033[K");
4313 for (cmd = pstart; cmd <= pend; cmd++) {
4314 cliPrint(cmd->name);
4315 cliWrite('\t');
4317 cliPrompt();
4318 i = 0; /* Redraw prompt */
4320 for (; i < bufferIndex; i++)
4321 cliWrite(cliBuffer[i]);
4322 } else if (!bufferIndex && c == 4) { // CTRL-D
4323 cliExit(cliBuffer);
4324 return;
4325 } else if (c == 12) { // NewPage / CTRL-L
4326 // clear screen
4327 cliPrint("\033[2J\033[1;1H");
4328 cliPrompt();
4329 } else if (bufferIndex && (c == '\n' || c == '\r')) {
4330 // enter pressed
4331 cliPrintLinefeed();
4333 // Strip comment starting with # from line
4334 char *p = cliBuffer;
4335 p = strchr(p, '#');
4336 if (NULL != p) {
4337 bufferIndex = (uint32_t)(p - cliBuffer);
4340 // Strip trailing whitespace
4341 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
4342 bufferIndex--;
4345 // Process non-empty lines
4346 if (bufferIndex > 0) {
4347 cliBuffer[bufferIndex] = 0; // null terminate
4349 const clicmd_t *cmd;
4350 char *options;
4351 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4352 if ((options = checkCommand(cliBuffer, cmd->name))) {
4353 break;
4356 if (cmd < cmdTable + ARRAYLEN(cmdTable))
4357 cmd->func(options);
4358 else
4359 cliPrint("Unknown command, try 'help'");
4360 bufferIndex = 0;
4363 memset(cliBuffer, 0, sizeof(cliBuffer));
4365 // 'exit' will reset this flag, so we don't need to print prompt again
4366 if (!cliMode)
4367 return;
4369 cliPrompt();
4370 } else if (c == 127) {
4371 // backspace
4372 if (bufferIndex) {
4373 cliBuffer[--bufferIndex] = 0;
4374 cliPrint("\010 \010");
4376 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
4377 if (!bufferIndex && c == ' ')
4378 continue; // Ignore leading spaces
4379 cliBuffer[bufferIndex++] = c;
4380 cliWrite(c);
4385 void cliEnter(serialPort_t *serialPort)
4387 cliMode = 1;
4388 cliPort = serialPort;
4389 setPrintfSerialPort(cliPort);
4390 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
4392 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
4394 #ifndef MINIMAL_CLI
4395 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4396 #else
4397 cliPrintLine("\r\nCLI");
4398 #endif
4399 cliPrompt();
4401 setArmingDisabled(ARMING_DISABLED_CLI);
4404 void cliInit(const serialConfig_t *serialConfig)
4406 UNUSED(serialConfig);
4408 #endif // USE_CLI