Cleaned up CLI output generation, added 'cliPrintErrorLinef'.
[betaflight.git] / src / main / interface / cli.c
bloba886f418841ab4cb17fa00424544e43aba00fd39
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"
31 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
32 // signal that we're in cli mode
33 uint8_t cliMode = 0;
34 #ifndef EEPROM_IN_RAM
35 extern uint8_t __config_start; // configured via linker script when building binaries.
36 extern uint8_t __config_end;
37 #endif
39 #ifdef USE_CLI
41 #include "blackbox/blackbox.h"
43 #include "build/build_config.h"
44 #include "build/debug.h"
45 #include "build/version.h"
47 #include "cms/cms.h"
49 #include "common/axis.h"
50 #include "common/color.h"
51 #include "common/maths.h"
52 #include "common/printf.h"
53 #include "common/time.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/failsafe.h"
96 #include "flight/imu.h"
97 #include "flight/mixer.h"
98 #include "flight/pid.h"
99 #include "flight/position.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.h"
130 #include "pg/rx_pwm.h"
131 #include "pg/timerio.h"
132 #include "pg/usb.h"
134 #include "rx/rx.h"
135 #include "rx/spektrum.h"
136 #include "rx/cc2500_frsky_common.h"
137 #include "rx/cc2500_frsky_x.h"
139 #include "scheduler/scheduler.h"
141 #include "sensors/acceleration.h"
142 #include "sensors/adcinternal.h"
143 #include "sensors/barometer.h"
144 #include "sensors/battery.h"
145 #include "sensors/boardalignment.h"
146 #include "sensors/compass.h"
147 #include "sensors/esc_sensor.h"
148 #include "sensors/gyro.h"
149 #include "sensors/sensors.h"
151 #include "telemetry/frsky_hub.h"
152 #include "telemetry/telemetry.h"
155 static serialPort_t *cliPort;
157 #ifdef STM32F1
158 #define CLI_IN_BUFFER_SIZE 128
159 #else
160 // Space required to set array parameters
161 #define CLI_IN_BUFFER_SIZE 256
162 #endif
163 #define CLI_OUT_BUFFER_SIZE 64
165 static bufWriter_t *cliWriter;
166 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
168 static char cliBuffer[CLI_IN_BUFFER_SIZE];
169 static uint32_t bufferIndex = 0;
171 static bool configIsInCopy = false;
173 static const char* const emptyName = "-";
174 static const char* const emptryString = "";
176 #ifndef USE_QUAD_MIXER_ONLY
177 // sync this with mixerMode_e
178 static const char * const mixerNames[] = {
179 "TRI", "QUADP", "QUADX", "BI",
180 "GIMBAL", "Y6", "HEX6",
181 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
182 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
183 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
184 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
186 #endif
188 // sync this with features_e
189 static const char * const featureNames[] = {
190 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
191 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
192 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
193 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
194 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
195 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
198 // sync this with rxFailsafeChannelMode_e
199 static const char rxFailsafeModeCharacters[] = "ahs";
201 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
202 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_INVALID },
203 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
206 #if defined(USE_SENSOR_NAMES)
207 // sync this with sensors_e
208 static const char * const sensorTypeNames[] = {
209 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
212 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
214 static const char * const *sensorHardwareNames[] = {
215 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
217 #endif // USE_SENSOR_NAMES
219 static void backupPgConfig(const pgRegistry_t *pg)
221 memcpy(pg->copy, pg->address, pg->size);
224 static void restorePgConfig(const pgRegistry_t *pg)
226 memcpy(pg->address, pg->copy, pg->size);
229 static void backupConfigs(void)
231 // make copies of configs to do differencing
232 PG_FOREACH(pg) {
233 backupPgConfig(pg);
236 configIsInCopy = true;
239 static void restoreConfigs(void)
241 PG_FOREACH(pg) {
242 restorePgConfig(pg);
245 configIsInCopy = false;
248 static void backupAndResetConfigs(void)
250 backupConfigs();
251 // reset all configs to defaults to do differencing
252 resetConfigs();
255 static void cliPrint(const char *str)
257 while (*str) {
258 bufWriterAppend(cliWriter, *str++);
260 bufWriterFlush(cliWriter);
263 static void cliPrintLinefeed(void)
265 cliPrint("\r\n");
268 static void cliPrintLine(const char *str)
270 cliPrint(str);
271 cliPrintLinefeed();
274 #ifdef MINIMAL_CLI
275 #define cliPrintHashLine(str)
276 #else
277 static void cliPrintHashLine(const char *str)
279 cliPrint("\r\n# ");
280 cliPrintLine(str);
282 #endif
284 static void cliPutp(void *p, char ch)
286 bufWriterAppend(p, ch);
289 typedef enum {
290 DUMP_MASTER = (1 << 0),
291 DUMP_PROFILE = (1 << 1),
292 DUMP_RATES = (1 << 2),
293 DUMP_ALL = (1 << 3),
294 DO_DIFF = (1 << 4),
295 SHOW_DEFAULTS = (1 << 5),
296 HIDE_UNUSED = (1 << 6)
297 } dumpFlags_e;
299 static void cliPrintfva(const char *format, va_list va)
301 tfp_format(cliWriter, cliPutp, format, va);
302 bufWriterFlush(cliWriter);
305 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
307 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
308 va_list va;
309 va_start(va, format);
310 cliPrintfva(format, va);
311 va_end(va);
312 cliPrintLinefeed();
313 return true;
314 } else {
315 return false;
319 static void cliWrite(uint8_t ch)
321 bufWriterAppend(cliWriter, ch);
324 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
326 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
327 cliWrite('#');
329 va_list va;
330 va_start(va, format);
331 cliPrintfva(format, va);
332 va_end(va);
333 cliPrintLinefeed();
334 return true;
335 } else {
336 return false;
340 static void cliPrintf(const char *format, ...)
342 va_list va;
343 va_start(va, format);
344 cliPrintfva(format, va);
345 va_end(va);
349 static void cliPrintLinef(const char *format, ...)
351 va_list va;
352 va_start(va, format);
353 cliPrintfva(format, va);
354 va_end(va);
355 cliPrintLinefeed();
358 static void cliPrintErrorLinef(const char *format, ...)
360 cliPrint("###ERROR### ");
361 va_list va;
362 va_start(va, format);
363 cliPrintfva(format, va);
364 va_end(va);
365 cliPrintLinefeed();
369 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
371 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
372 for (int i = 0; i < var->config.array.length; i++) {
373 switch (var->type & VALUE_TYPE_MASK) {
374 default:
375 case VAR_UINT8:
376 // uint8_t array
377 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
378 break;
380 case VAR_INT8:
381 // int8_t array
382 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
383 break;
385 case VAR_UINT16:
386 // uin16_t array
387 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
388 break;
390 case VAR_INT16:
391 // int16_t array
392 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
393 break;
396 if (i < var->config.array.length - 1) {
397 cliPrint(",");
400 } else {
401 int value = 0;
403 switch (var->type & VALUE_TYPE_MASK) {
404 case VAR_UINT8:
405 value = *(uint8_t *)valuePointer;
406 break;
408 case VAR_INT8:
409 value = *(int8_t *)valuePointer;
410 break;
412 case VAR_UINT16:
413 case VAR_INT16:
414 value = *(int16_t *)valuePointer;
415 break;
416 case VAR_UINT32:
417 value = *(uint32_t *)valuePointer;
418 break;
421 switch (var->type & VALUE_MODE_MASK) {
422 case MODE_DIRECT:
423 cliPrintf("%d", value);
424 if (full) {
425 cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
427 break;
428 case MODE_LOOKUP:
429 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
430 break;
431 case MODE_BITSET:
432 if (value & 1 << var->config.bitpos) {
433 cliPrintf("ON");
434 } else {
435 cliPrintf("OFF");
442 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
444 bool result = true;
445 int elementCount = 1;
446 uint32_t mask = 0xffffffff;
448 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
449 elementCount = var->config.array.length;
451 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
452 mask = 1 << var->config.bitpos;
454 for (int i = 0; i < elementCount; i++) {
455 switch (var->type & VALUE_TYPE_MASK) {
456 case VAR_UINT8:
457 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
458 break;
460 case VAR_INT8:
461 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
462 break;
464 case VAR_UINT16:
465 result = result && (((int16_t *)ptr)[i] & mask) == (((int16_t *)ptrDefault)[i] & mask);
466 break;
467 case VAR_INT16:
468 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
469 break;
470 case VAR_UINT32:
471 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
472 break;
476 return result;
479 static uint16_t getValueOffset(const clivalue_t *value)
481 switch (value->type & VALUE_SECTION_MASK) {
482 case MASTER_VALUE:
483 return value->offset;
484 case PROFILE_VALUE:
485 return value->offset + sizeof(pidProfile_t) * getCurrentPidProfileIndex();
486 case PROFILE_RATE_VALUE:
487 return value->offset + sizeof(controlRateConfig_t) * getCurrentControlRateProfileIndex();
489 return 0;
492 void *cliGetValuePointer(const clivalue_t *value)
494 const pgRegistry_t* rec = pgFind(value->pgn);
495 if (configIsInCopy) {
496 return CONST_CAST(void *, rec->copy + getValueOffset(value));
497 } else {
498 return CONST_CAST(void *, rec->address + getValueOffset(value));
502 const void *cliGetDefaultPointer(const clivalue_t *value)
504 const pgRegistry_t* rec = pgFind(value->pgn);
505 return rec->address + getValueOffset(value);
508 static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
510 const pgRegistry_t *pg = pgFind(value->pgn);
511 #ifdef DEBUG
512 if (!pg) {
513 cliPrintLinef("VALUE %s ERROR", value->name);
514 return; // if it's not found, the pgn shouldn't be in the value table!
516 #endif
518 const char *format = "set %s = ";
519 const char *defaultFormat = "#set %s = ";
520 const int valueOffset = getValueOffset(value);
521 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
523 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
524 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
525 cliPrintf(defaultFormat, value->name);
526 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
527 cliPrintLinefeed();
529 cliPrintf(format, value->name);
530 printValuePointer(value, pg->copy + valueOffset, false);
531 cliPrintLinefeed();
535 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
537 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
538 const clivalue_t *value = &valueTable[i];
539 bufWriterFlush(cliWriter);
540 if ((value->type & VALUE_SECTION_MASK) == valueSection) {
541 dumpPgValue(value, dumpMask);
546 static void cliPrintVar(const clivalue_t *var, bool full)
548 const void *ptr = cliGetValuePointer(var);
550 printValuePointer(var, ptr, full);
553 static void cliPrintVarRange(const clivalue_t *var)
555 switch (var->type & VALUE_MODE_MASK) {
556 case (MODE_DIRECT): {
557 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
559 break;
560 case (MODE_LOOKUP): {
561 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
562 cliPrint("Allowed values: ");
563 bool firstEntry = true;
564 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
565 if (tableEntry->values[i]) {
566 if (!firstEntry) {
567 cliPrint(", ");
569 cliPrintf("%s", tableEntry->values[i]);
570 firstEntry = false;
573 cliPrintLinefeed();
575 break;
576 case (MODE_ARRAY): {
577 cliPrintLinef("Array length: %d", var->config.array.length);
579 break;
580 case (MODE_BITSET): {
581 cliPrintLinef("Allowed values: OFF, ON");
583 break;
587 static void cliSetVar(const clivalue_t *var, const int16_t value)
589 void *ptr = cliGetValuePointer(var);
590 uint32_t workValue;
591 uint32_t mask;
593 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
594 switch (var->type & VALUE_TYPE_MASK) {
595 case VAR_UINT8:
596 mask = (1 << var->config.bitpos) & 0xff;
597 if (value) {
598 workValue = *(uint8_t *)ptr | mask;
599 } else {
600 workValue = *(uint8_t *)ptr & ~mask;
602 *(uint8_t *)ptr = workValue;
603 break;
605 case VAR_UINT16:
606 mask = (1 << var->config.bitpos) & 0xffff;
607 if (value) {
608 workValue = *(uint16_t *)ptr | mask;
609 } else {
610 workValue = *(uint16_t *)ptr & ~mask;
612 *(uint16_t *)ptr = workValue;
613 break;
615 case VAR_UINT32:
616 mask = 1 << var->config.bitpos;
617 if (value) {
618 workValue = *(uint32_t *)ptr | mask;
619 } else {
620 workValue = *(uint32_t *)ptr & ~mask;
622 *(uint32_t *)ptr = workValue;
623 break;
626 } else {
627 switch (var->type & VALUE_TYPE_MASK) {
628 case VAR_UINT8:
629 *(uint8_t *)ptr = value;
630 break;
632 case VAR_INT8:
633 *(int8_t *)ptr = value;
634 break;
636 case VAR_UINT16:
637 case VAR_INT16:
638 *(int16_t *)ptr = value;
639 break;
644 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
645 static void cliRepeat(char ch, uint8_t len)
647 for (int i = 0; i < len; i++) {
648 bufWriterAppend(cliWriter, ch);
650 cliPrintLinefeed();
652 #endif
654 static void cliPrompt(void)
656 cliPrint("\r\n# ");
659 static void cliShowParseError(void)
661 cliPrintErrorLinef("Parse error");
664 static void cliShowArgumentRangeError(char *name, int min, int max)
666 cliPrintErrorLinef("%s not between %d and %d", name, min, max);
669 static const char *nextArg(const char *currentArg)
671 const char *ptr = strchr(currentArg, ' ');
672 while (ptr && *ptr == ' ') {
673 ptr++;
676 return ptr;
679 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
681 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
682 ptr = nextArg(ptr);
683 if (ptr) {
684 int val = atoi(ptr);
685 val = CHANNEL_VALUE_TO_STEP(val);
686 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
687 if (argIndex == 0) {
688 range->startStep = val;
689 } else {
690 range->endStep = val;
692 (*validArgumentCount)++;
697 return ptr;
700 // Check if a string's length is zero
701 static bool isEmpty(const char *string)
703 return (string == NULL || *string == '\0') ? true : false;
706 static void printRxFailsafe(uint8_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs)
708 // print out rxConfig failsafe settings
709 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
710 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
711 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
712 const bool equalsDefault = channelFailsafeConfig->mode == defaultChannelFailsafeConfig->mode
713 && channelFailsafeConfig->step == defaultChannelFailsafeConfig->step;
714 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
715 if (requireValue) {
716 const char *format = "rxfail %u %c %d";
717 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
718 channel,
719 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
720 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
722 cliDumpPrintLinef(dumpMask, equalsDefault, format,
723 channel,
724 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
725 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
727 } else {
728 const char *format = "rxfail %u %c";
729 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
730 channel,
731 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
733 cliDumpPrintLinef(dumpMask, equalsDefault, format,
734 channel,
735 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
741 static void cliRxFailsafe(char *cmdline)
743 uint8_t channel;
744 char buf[3];
746 if (isEmpty(cmdline)) {
747 // print out rxConfig failsafe settings
748 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
749 cliRxFailsafe(itoa(channel, buf, 10));
751 } else {
752 const char *ptr = cmdline;
753 channel = atoi(ptr++);
754 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
756 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
758 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
759 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
760 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
762 ptr = nextArg(ptr);
763 if (ptr) {
764 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
765 if (p) {
766 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
767 mode = rxFailsafeModesTable[type][requestedMode];
768 } else {
769 mode = RX_FAILSAFE_MODE_INVALID;
771 if (mode == RX_FAILSAFE_MODE_INVALID) {
772 cliShowParseError();
773 return;
776 requireValue = mode == RX_FAILSAFE_MODE_SET;
778 ptr = nextArg(ptr);
779 if (ptr) {
780 if (!requireValue) {
781 cliShowParseError();
782 return;
784 uint16_t value = atoi(ptr);
785 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
786 if (value > MAX_RXFAIL_RANGE_STEP) {
787 cliPrintLine("Value out of range");
788 return;
791 channelFailsafeConfig->step = value;
792 } else if (requireValue) {
793 cliShowParseError();
794 return;
796 channelFailsafeConfig->mode = mode;
799 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
801 // double use of cliPrintf below
802 // 1. acknowledge interpretation on command,
803 // 2. query current setting on single item,
805 if (requireValue) {
806 cliPrintLinef("rxfail %u %c %d",
807 channel,
808 modeCharacter,
809 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
811 } else {
812 cliPrintLinef("rxfail %u %c",
813 channel,
814 modeCharacter
817 } else {
818 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
823 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
825 const char *format = "aux %u %u %u %u %u %u";
826 // print out aux channel settings
827 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
828 const modeActivationCondition_t *mac = &modeActivationConditions[i];
829 bool equalsDefault = false;
830 if (defaultModeActivationConditions) {
831 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
832 equalsDefault = mac->modeId == macDefault->modeId
833 && mac->auxChannelIndex == macDefault->auxChannelIndex
834 && mac->range.startStep == macDefault->range.startStep
835 && mac->range.endStep == macDefault->range.endStep
836 && mac->modeLogic == macDefault->modeLogic;
837 const box_t *box = findBoxByBoxId(macDefault->modeId);
838 if (box) {
839 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
841 box->permanentId,
842 macDefault->auxChannelIndex,
843 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
844 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
845 macDefault->modeLogic
849 const box_t *box = findBoxByBoxId(mac->modeId);
850 if (box) {
851 cliDumpPrintLinef(dumpMask, equalsDefault, format,
853 box->permanentId,
854 mac->auxChannelIndex,
855 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
856 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
857 mac->modeLogic
863 static void cliAux(char *cmdline)
865 int i, val = 0;
866 const char *ptr;
868 if (isEmpty(cmdline)) {
869 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
870 } else {
871 ptr = cmdline;
872 i = atoi(ptr++);
873 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
874 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
875 uint8_t validArgumentCount = 0;
876 ptr = nextArg(ptr);
877 if (ptr) {
878 val = atoi(ptr);
879 const box_t *box = findBoxByPermanentId(val);
880 if (box) {
881 mac->modeId = box->boxId;
882 validArgumentCount++;
885 ptr = nextArg(ptr);
886 if (ptr) {
887 val = atoi(ptr);
888 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
889 mac->auxChannelIndex = val;
890 validArgumentCount++;
893 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
894 ptr = nextArg(ptr);
895 if (ptr) {
896 val = atoi(ptr);
897 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
898 mac->modeLogic = val;
899 validArgumentCount++;
902 if (validArgumentCount == 4) { // for backwards compatibility
903 mac->modeLogic = MODELOGIC_OR;
904 } else if (validArgumentCount != 5) {
905 memset(mac, 0, sizeof(modeActivationCondition_t));
907 cliPrintLinef( "aux %u %u %u %u %u %u",
909 mac->modeId,
910 mac->auxChannelIndex,
911 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
912 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
913 mac->modeLogic
915 } else {
916 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
921 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
923 const char *format = "serial %d %d %ld %ld %ld %ld";
924 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
925 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
926 continue;
928 bool equalsDefault = false;
929 if (serialConfigDefault) {
930 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
931 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
932 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
933 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
934 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
935 && serialConfig->portConfigs[i].blackbox_baudrateIndex == serialConfigDefault->portConfigs[i].blackbox_baudrateIndex;
936 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
937 serialConfigDefault->portConfigs[i].identifier,
938 serialConfigDefault->portConfigs[i].functionMask,
939 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
940 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
941 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
942 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
945 cliDumpPrintLinef(dumpMask, equalsDefault, format,
946 serialConfig->portConfigs[i].identifier,
947 serialConfig->portConfigs[i].functionMask,
948 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
949 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
950 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
951 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
956 static void cliSerial(char *cmdline)
958 const char *format = "serial %d %d %ld %ld %ld %ld";
959 if (isEmpty(cmdline)) {
960 printSerial(DUMP_MASTER, serialConfig(), NULL);
961 return;
963 serialPortConfig_t portConfig;
964 memset(&portConfig, 0 , sizeof(portConfig));
966 serialPortConfig_t *currentConfig;
968 uint8_t validArgumentCount = 0;
970 const char *ptr = cmdline;
972 int val = atoi(ptr++);
973 currentConfig = serialFindPortConfiguration(val);
974 if (currentConfig) {
975 portConfig.identifier = val;
976 validArgumentCount++;
979 ptr = nextArg(ptr);
980 if (ptr) {
981 val = atoi(ptr);
982 portConfig.functionMask = val & 0xFFFF;
983 validArgumentCount++;
986 for (int i = 0; i < 4; i ++) {
987 ptr = nextArg(ptr);
988 if (!ptr) {
989 break;
992 val = atoi(ptr);
994 uint8_t baudRateIndex = lookupBaudRateIndex(val);
995 if (baudRates[baudRateIndex] != (uint32_t) val) {
996 break;
999 switch (i) {
1000 case 0:
1001 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1002 continue;
1004 portConfig.msp_baudrateIndex = baudRateIndex;
1005 break;
1006 case 1:
1007 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1008 continue;
1010 portConfig.gps_baudrateIndex = baudRateIndex;
1011 break;
1012 case 2:
1013 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1014 continue;
1016 portConfig.telemetry_baudrateIndex = baudRateIndex;
1017 break;
1018 case 3:
1019 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1020 continue;
1022 portConfig.blackbox_baudrateIndex = baudRateIndex;
1023 break;
1026 validArgumentCount++;
1029 if (validArgumentCount < 6) {
1030 cliShowParseError();
1031 return;
1034 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1036 cliDumpPrintLinef(0, false, format,
1037 portConfig.identifier,
1038 portConfig.functionMask,
1039 baudRates[portConfig.msp_baudrateIndex],
1040 baudRates[portConfig.gps_baudrateIndex],
1041 baudRates[portConfig.telemetry_baudrateIndex],
1042 baudRates[portConfig.blackbox_baudrateIndex]
1047 #ifndef SKIP_SERIAL_PASSTHROUGH
1048 #ifdef USE_PINIO
1049 static void cbCtrlLine(void *context, uint16_t ctrl)
1051 int pinioDtr = (int)(long)context;
1053 pinioSet(pinioDtr, !(ctrl & CTRL_LINE_STATE_DTR));
1055 #endif /* USE_PINIO */
1057 static void cliSerialPassthrough(char *cmdline)
1059 if (isEmpty(cmdline)) {
1060 cliShowParseError();
1061 return;
1064 int id = -1;
1065 uint32_t baud = 0;
1066 bool enableBaudCb = false;
1067 #ifdef USE_PINIO
1068 int pinioDtr = 0;
1069 #endif /* USE_PINIO */
1070 unsigned mode = 0;
1071 char *saveptr;
1072 char* tok = strtok_r(cmdline, " ", &saveptr);
1073 int index = 0;
1075 while (tok != NULL) {
1076 switch (index) {
1077 case 0:
1078 id = atoi(tok);
1079 break;
1080 case 1:
1081 baud = atoi(tok);
1082 break;
1083 case 2:
1084 if (strstr(tok, "rx") || strstr(tok, "RX"))
1085 mode |= MODE_RX;
1086 if (strstr(tok, "tx") || strstr(tok, "TX"))
1087 mode |= MODE_TX;
1088 break;
1089 #ifdef USE_PINIO
1090 case 3:
1091 pinioDtr = atoi(tok);
1092 break;
1093 #endif /* USE_PINIO */
1095 index++;
1096 tok = strtok_r(NULL, " ", &saveptr);
1099 if (baud == 0) {
1100 enableBaudCb = true;
1103 cliPrintf("Port %d ", id);
1104 serialPort_t *passThroughPort;
1105 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
1106 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
1107 if (enableBaudCb) {
1108 // Set default baud
1109 baud = 57600;
1112 if (!mode) {
1113 mode = MODE_RXTX;
1116 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
1117 baud, mode,
1118 SERIAL_NOT_INVERTED);
1119 if (!passThroughPort) {
1120 cliPrintLine("could not be opened.");
1121 return;
1124 if (enableBaudCb) {
1125 cliPrintf("opened, default baud = %d.\r\n", baud);
1126 } else {
1127 cliPrintf("opened, baud = %d.\r\n", baud);
1129 } else {
1130 passThroughPort = passThroughPortUsage->serialPort;
1131 // If the user supplied a mode, override the port's mode, otherwise
1132 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1133 // Set the baud rate if specified
1134 if (baud) {
1135 cliPrintf("already open, setting baud = %d.\n\r", baud);
1136 serialSetBaudRate(passThroughPort, baud);
1137 } else {
1138 cliPrintf("already open, baud = %d.\n\r", passThroughPort->baudRate);
1141 if (mode && passThroughPort->mode != mode) {
1142 cliPrintf("Mode changed from %d to %d.\r\n",
1143 passThroughPort->mode, mode);
1144 serialSetMode(passThroughPort, mode);
1147 // If this port has a rx callback associated we need to remove it now.
1148 // Otherwise no data will be pushed in the serial port buffer!
1149 if (passThroughPort->rxCallback) {
1150 passThroughPort->rxCallback = 0;
1154 // If no baud rate is specified allow to be set via USB
1155 if (enableBaudCb) {
1156 cliPrintLine("Baud rate change over USB enabled.");
1157 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1158 // baud rate over USB without setting it using the serialpassthrough command
1159 serialSetBaudRateCb(cliPort, serialSetBaudRate, passThroughPort);
1162 cliPrintLine("Forwarding, power cycle to exit.");
1164 #ifdef USE_PINIO
1165 // Register control line state callback
1166 if (pinioDtr) {
1167 serialSetCtrlLineStateCb(cliPort, cbCtrlLine, (void *)(intptr_t)(pinioDtr - 1));
1169 #endif /* USE_PINIO */
1171 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
1173 #endif
1175 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
1177 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1178 // print out adjustment ranges channel settings
1179 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1180 const adjustmentRange_t *ar = &adjustmentRanges[i];
1181 bool equalsDefault = false;
1182 if (defaultAdjustmentRanges) {
1183 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1184 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
1185 && ar->range.startStep == arDefault->range.startStep
1186 && ar->range.endStep == arDefault->range.endStep
1187 && ar->adjustmentFunction == arDefault->adjustmentFunction
1188 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
1189 && ar->adjustmentIndex == arDefault->adjustmentIndex
1190 && ar->adjustmentCenter == arDefault->adjustmentCenter
1191 && ar->adjustmentScale == arDefault->adjustmentScale;
1192 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1194 arDefault->adjustmentIndex,
1195 arDefault->auxChannelIndex,
1196 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1197 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1198 arDefault->adjustmentFunction,
1199 arDefault->auxSwitchChannelIndex,
1200 arDefault->adjustmentCenter,
1201 arDefault->adjustmentScale
1204 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1206 ar->adjustmentIndex,
1207 ar->auxChannelIndex,
1208 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1209 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1210 ar->adjustmentFunction,
1211 ar->auxSwitchChannelIndex,
1212 ar->adjustmentCenter,
1213 ar->adjustmentScale
1218 static void cliAdjustmentRange(char *cmdline)
1220 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1221 int i, val = 0;
1222 const char *ptr;
1224 if (isEmpty(cmdline)) {
1225 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
1226 } else {
1227 ptr = cmdline;
1228 i = atoi(ptr++);
1229 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1230 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1231 uint8_t validArgumentCount = 0;
1233 ptr = nextArg(ptr);
1234 if (ptr) {
1235 val = atoi(ptr);
1236 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1237 ar->adjustmentIndex = val;
1238 validArgumentCount++;
1241 ptr = nextArg(ptr);
1242 if (ptr) {
1243 val = atoi(ptr);
1244 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1245 ar->auxChannelIndex = val;
1246 validArgumentCount++;
1250 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1252 ptr = nextArg(ptr);
1253 if (ptr) {
1254 val = atoi(ptr);
1255 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1256 ar->adjustmentFunction = val;
1257 validArgumentCount++;
1260 ptr = nextArg(ptr);
1261 if (ptr) {
1262 val = atoi(ptr);
1263 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1264 ar->auxSwitchChannelIndex = val;
1265 validArgumentCount++;
1269 if (validArgumentCount != 6) {
1270 memset(ar, 0, sizeof(adjustmentRange_t));
1271 cliShowParseError();
1272 return;
1275 // Optional arguments
1276 ar->adjustmentCenter = 0;
1277 ar->adjustmentScale = 0;
1279 ptr = nextArg(ptr);
1280 if (ptr) {
1281 val = atoi(ptr);
1282 ar->adjustmentCenter = val;
1283 validArgumentCount++;
1285 ptr = nextArg(ptr);
1286 if (ptr) {
1287 val = atoi(ptr);
1288 ar->adjustmentScale = val;
1289 validArgumentCount++;
1291 cliDumpPrintLinef(0, false, format,
1293 ar->adjustmentIndex,
1294 ar->auxChannelIndex,
1295 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1296 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1297 ar->adjustmentFunction,
1298 ar->auxSwitchChannelIndex,
1299 ar->adjustmentCenter,
1300 ar->adjustmentScale
1303 } else {
1304 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1309 #ifndef USE_QUAD_MIXER_ONLY
1310 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer)
1312 const char *format = "mmix %d %s %s %s %s";
1313 char buf0[FTOA_BUFFER_LENGTH];
1314 char buf1[FTOA_BUFFER_LENGTH];
1315 char buf2[FTOA_BUFFER_LENGTH];
1316 char buf3[FTOA_BUFFER_LENGTH];
1317 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1318 if (customMotorMixer[i].throttle == 0.0f)
1319 break;
1320 const float thr = customMotorMixer[i].throttle;
1321 const float roll = customMotorMixer[i].roll;
1322 const float pitch = customMotorMixer[i].pitch;
1323 const float yaw = customMotorMixer[i].yaw;
1324 bool equalsDefault = false;
1325 if (defaultCustomMotorMixer) {
1326 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1327 const float rollDefault = defaultCustomMotorMixer[i].roll;
1328 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1329 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1330 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1332 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1334 ftoa(thrDefault, buf0),
1335 ftoa(rollDefault, buf1),
1336 ftoa(pitchDefault, buf2),
1337 ftoa(yawDefault, buf3));
1339 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1341 ftoa(thr, buf0),
1342 ftoa(roll, buf1),
1343 ftoa(pitch, buf2),
1344 ftoa(yaw, buf3));
1347 #endif // USE_QUAD_MIXER_ONLY
1349 static void cliMotorMix(char *cmdline)
1351 #ifdef USE_QUAD_MIXER_ONLY
1352 UNUSED(cmdline);
1353 #else
1354 int check = 0;
1355 uint8_t len;
1356 const char *ptr;
1358 if (isEmpty(cmdline)) {
1359 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1360 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1361 // erase custom mixer
1362 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1363 customMotorMixerMutable(i)->throttle = 0.0f;
1365 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1366 ptr = nextArg(cmdline);
1367 if (ptr) {
1368 len = strlen(ptr);
1369 for (uint32_t i = 0; ; i++) {
1370 if (mixerNames[i] == NULL) {
1371 cliPrintErrorLinef("Invalid name");
1372 break;
1374 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1375 mixerLoadMix(i, customMotorMixerMutable(0));
1376 cliPrintLinef("Loaded %s", mixerNames[i]);
1377 cliMotorMix("");
1378 break;
1382 } else {
1383 ptr = cmdline;
1384 uint32_t i = atoi(ptr); // get motor number
1385 if (i < MAX_SUPPORTED_MOTORS) {
1386 ptr = nextArg(ptr);
1387 if (ptr) {
1388 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1389 check++;
1391 ptr = nextArg(ptr);
1392 if (ptr) {
1393 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1394 check++;
1396 ptr = nextArg(ptr);
1397 if (ptr) {
1398 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1399 check++;
1401 ptr = nextArg(ptr);
1402 if (ptr) {
1403 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1404 check++;
1406 if (check != 4) {
1407 cliShowParseError();
1408 } else {
1409 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1411 } else {
1412 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1415 #endif
1418 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1420 const char *format = "rxrange %u %u %u";
1421 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1422 bool equalsDefault = false;
1423 if (defaultChannelRangeConfigs) {
1424 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1425 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1426 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1428 defaultChannelRangeConfigs[i].min,
1429 defaultChannelRangeConfigs[i].max
1432 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1434 channelRangeConfigs[i].min,
1435 channelRangeConfigs[i].max
1440 static void cliRxRange(char *cmdline)
1442 const char *format = "rxrange %u %u %u";
1443 int i, validArgumentCount = 0;
1444 const char *ptr;
1446 if (isEmpty(cmdline)) {
1447 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1448 } else if (strcasecmp(cmdline, "reset") == 0) {
1449 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1450 } else {
1451 ptr = cmdline;
1452 i = atoi(ptr);
1453 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1454 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1456 ptr = nextArg(ptr);
1457 if (ptr) {
1458 rangeMin = atoi(ptr);
1459 validArgumentCount++;
1462 ptr = nextArg(ptr);
1463 if (ptr) {
1464 rangeMax = atoi(ptr);
1465 validArgumentCount++;
1468 if (validArgumentCount != 2) {
1469 cliShowParseError();
1470 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1471 cliShowParseError();
1472 } else {
1473 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1474 channelRangeConfig->min = rangeMin;
1475 channelRangeConfig->max = rangeMax;
1476 cliDumpPrintLinef(0, false, format,
1478 channelRangeConfig->min,
1479 channelRangeConfig->max
1483 } else {
1484 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1489 #ifdef USE_LED_STRIP
1490 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1492 const char *format = "led %u %s";
1493 char ledConfigBuffer[20];
1494 char ledConfigDefaultBuffer[20];
1495 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1496 ledConfig_t ledConfig = ledConfigs[i];
1497 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1498 bool equalsDefault = false;
1499 if (defaultLedConfigs) {
1500 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1501 equalsDefault = ledConfig == ledConfigDefault;
1502 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1503 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1505 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1509 static void cliLed(char *cmdline)
1511 const char *format = "led %u %s";
1512 char ledConfigBuffer[20];
1513 int i;
1514 const char *ptr;
1516 if (isEmpty(cmdline)) {
1517 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1518 } else {
1519 ptr = cmdline;
1520 i = atoi(ptr);
1521 if (i < LED_MAX_STRIP_LENGTH) {
1522 ptr = nextArg(cmdline);
1523 if (parseLedStripConfig(i, ptr)) {
1524 generateLedConfig((ledConfig_t *)&ledStripConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1525 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1526 } else {
1527 cliShowParseError();
1529 } else {
1530 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1535 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1537 const char *format = "color %u %d,%u,%u";
1538 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1539 const hsvColor_t *color = &colors[i];
1540 bool equalsDefault = false;
1541 if (defaultColors) {
1542 const hsvColor_t *colorDefault = &defaultColors[i];
1543 equalsDefault = color->h == colorDefault->h
1544 && color->s == colorDefault->s
1545 && color->v == colorDefault->v;
1546 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1548 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1552 static void cliColor(char *cmdline)
1554 const char *format = "color %u %d,%u,%u";
1555 if (isEmpty(cmdline)) {
1556 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1557 } else {
1558 const char *ptr = cmdline;
1559 const int i = atoi(ptr);
1560 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1561 ptr = nextArg(cmdline);
1562 if (parseColor(i, ptr)) {
1563 const hsvColor_t *color = &ledStripConfig()->colors[i];
1564 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1565 } else {
1566 cliShowParseError();
1568 } else {
1569 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1574 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1576 const char *format = "mode_color %u %u %u";
1577 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1578 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1579 int colorIndex = ledStripConfig->modeColors[i].color[j];
1580 bool equalsDefault = false;
1581 if (defaultLedStripConfig) {
1582 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1583 equalsDefault = colorIndex == colorIndexDefault;
1584 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1586 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1590 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1591 const int colorIndex = ledStripConfig->specialColors.color[j];
1592 bool equalsDefault = false;
1593 if (defaultLedStripConfig) {
1594 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1595 equalsDefault = colorIndex == colorIndexDefault;
1596 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1598 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1601 const int ledStripAuxChannel = ledStripConfig->ledstrip_aux_channel;
1602 bool equalsDefault = false;
1603 if (defaultLedStripConfig) {
1604 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1605 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1606 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1608 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1611 static void cliModeColor(char *cmdline)
1613 if (isEmpty(cmdline)) {
1614 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1615 } else {
1616 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1617 int args[ARGS_COUNT];
1618 int argNo = 0;
1619 char *saveptr;
1620 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1621 while (ptr && argNo < ARGS_COUNT) {
1622 args[argNo++] = atoi(ptr);
1623 ptr = strtok_r(NULL, " ", &saveptr);
1626 if (ptr != NULL || argNo != ARGS_COUNT) {
1627 cliShowParseError();
1628 return;
1631 int modeIdx = args[MODE];
1632 int funIdx = args[FUNCTION];
1633 int color = args[COLOR];
1634 if (!setModeColor(modeIdx, funIdx, color)) {
1635 cliShowParseError();
1636 return;
1638 // values are validated
1639 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1642 #endif
1644 #ifdef USE_SERVOS
1645 static void printServo(uint8_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams)
1647 // print out servo settings
1648 const char *format = "servo %u %d %d %d %d %d";
1649 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1650 const servoParam_t *servoConf = &servoParams[i];
1651 bool equalsDefault = false;
1652 if (defaultServoParams) {
1653 const servoParam_t *defaultServoConf = &defaultServoParams[i];
1654 equalsDefault = servoConf->min == defaultServoConf->min
1655 && servoConf->max == defaultServoConf->max
1656 && servoConf->middle == defaultServoConf->middle
1657 && servoConf->rate == defaultServoConf->rate
1658 && servoConf->forwardFromChannel == defaultServoConf->forwardFromChannel;
1659 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1661 defaultServoConf->min,
1662 defaultServoConf->max,
1663 defaultServoConf->middle,
1664 defaultServoConf->rate,
1665 defaultServoConf->forwardFromChannel
1668 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1670 servoConf->min,
1671 servoConf->max,
1672 servoConf->middle,
1673 servoConf->rate,
1674 servoConf->forwardFromChannel
1677 // print servo directions
1678 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1679 const char *format = "smix reverse %d %d r";
1680 const servoParam_t *servoConf = &servoParams[i];
1681 const servoParam_t *servoConfDefault = &defaultServoParams[i];
1682 if (defaultServoParams) {
1683 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
1684 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1685 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
1686 if (servoConfDefault->reversedSources & (1 << channel)) {
1687 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
1689 if (servoConf->reversedSources & (1 << channel)) {
1690 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
1693 } else {
1694 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1695 if (servoConf->reversedSources & (1 << channel)) {
1696 cliDumpPrintLinef(dumpMask, true, format, i , channel);
1703 static void cliServo(char *cmdline)
1705 const char *format = "servo %u %d %d %d %d %d";
1706 enum { SERVO_ARGUMENT_COUNT = 6 };
1707 int16_t arguments[SERVO_ARGUMENT_COUNT];
1709 servoParam_t *servo;
1711 int i;
1712 char *ptr;
1714 if (isEmpty(cmdline)) {
1715 printServo(DUMP_MASTER, servoParams(0), NULL);
1716 } else {
1717 int validArgumentCount = 0;
1719 ptr = cmdline;
1721 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1723 // If command line doesn't fit the format, don't modify the config
1724 while (*ptr) {
1725 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1726 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1727 cliShowParseError();
1728 return;
1731 arguments[validArgumentCount++] = atoi(ptr);
1733 do {
1734 ptr++;
1735 } while (*ptr >= '0' && *ptr <= '9');
1736 } else if (*ptr == ' ') {
1737 ptr++;
1738 } else {
1739 cliShowParseError();
1740 return;
1744 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
1746 i = arguments[INDEX];
1748 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1749 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1750 cliShowParseError();
1751 return;
1754 servo = servoParamsMutable(i);
1756 if (
1757 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
1758 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
1759 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1760 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1761 arguments[RATE] < -100 || arguments[RATE] > 100 ||
1762 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1764 cliShowParseError();
1765 return;
1768 servo->min = arguments[MIN];
1769 servo->max = arguments[MAX];
1770 servo->middle = arguments[MIDDLE];
1771 servo->rate = arguments[RATE];
1772 servo->forwardFromChannel = arguments[FORWARD];
1774 cliDumpPrintLinef(0, false, format,
1776 servo->min,
1777 servo->max,
1778 servo->middle,
1779 servo->rate,
1780 servo->forwardFromChannel
1785 #endif
1787 #ifdef USE_SERVOS
1788 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1790 const char *format = "smix %d %d %d %d %d %d %d %d";
1791 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1792 const servoMixer_t customServoMixer = customServoMixers[i];
1793 if (customServoMixer.rate == 0) {
1794 break;
1797 bool equalsDefault = false;
1798 if (defaultCustomServoMixers) {
1799 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1800 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1801 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1802 && customServoMixer.rate == customServoMixerDefault.rate
1803 && customServoMixer.speed == customServoMixerDefault.speed
1804 && customServoMixer.min == customServoMixerDefault.min
1805 && customServoMixer.max == customServoMixerDefault.max
1806 && customServoMixer.box == customServoMixerDefault.box;
1808 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1810 customServoMixerDefault.targetChannel,
1811 customServoMixerDefault.inputSource,
1812 customServoMixerDefault.rate,
1813 customServoMixerDefault.speed,
1814 customServoMixerDefault.min,
1815 customServoMixerDefault.max,
1816 customServoMixerDefault.box
1819 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1821 customServoMixer.targetChannel,
1822 customServoMixer.inputSource,
1823 customServoMixer.rate,
1824 customServoMixer.speed,
1825 customServoMixer.min,
1826 customServoMixer.max,
1827 customServoMixer.box
1831 cliPrintLinefeed();
1834 static void cliServoMix(char *cmdline)
1836 int args[8], check = 0;
1837 int len = strlen(cmdline);
1839 if (len == 0) {
1840 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
1841 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1842 // erase custom mixer
1843 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1844 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1845 servoParamsMutable(i)->reversedSources = 0;
1847 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1848 const char *ptr = nextArg(cmdline);
1849 if (ptr) {
1850 len = strlen(ptr);
1851 for (uint32_t i = 0; ; i++) {
1852 if (mixerNames[i] == NULL) {
1853 cliPrintErrorLinef("Invalid name");
1854 break;
1856 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1857 servoMixerLoadMix(i);
1858 cliPrintLinef("Loaded %s", mixerNames[i]);
1859 cliServoMix("");
1860 break;
1864 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
1865 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
1866 char *ptr = strchr(cmdline, ' ');
1868 len = strlen(ptr);
1869 if (len == 0) {
1870 cliPrintf("s");
1871 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1872 cliPrintf("\ti%d", inputSource);
1873 cliPrintLinefeed();
1875 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
1876 cliPrintf("%d", servoIndex);
1877 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1878 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
1879 cliPrintLinefeed();
1881 return;
1884 char *saveptr;
1885 ptr = strtok_r(ptr, " ", &saveptr);
1886 while (ptr != NULL && check < ARGS_COUNT - 1) {
1887 args[check++] = atoi(ptr);
1888 ptr = strtok_r(NULL, " ", &saveptr);
1891 if (ptr == NULL || check != ARGS_COUNT - 1) {
1892 cliShowParseError();
1893 return;
1896 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
1897 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
1898 && (*ptr == 'r' || *ptr == 'n')) {
1899 if (*ptr == 'r')
1900 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
1901 else
1902 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
1903 } else
1904 cliShowParseError();
1906 cliServoMix("reverse");
1907 } else {
1908 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
1909 char *saveptr;
1910 char *ptr = strtok_r(cmdline, " ", &saveptr);
1911 while (ptr != NULL && check < ARGS_COUNT) {
1912 args[check++] = atoi(ptr);
1913 ptr = strtok_r(NULL, " ", &saveptr);
1916 if (ptr != NULL || check != ARGS_COUNT) {
1917 cliShowParseError();
1918 return;
1921 int32_t i = args[RULE];
1922 if (i >= 0 && i < MAX_SERVO_RULES &&
1923 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1924 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1925 args[RATE] >= -100 && args[RATE] <= 100 &&
1926 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1927 args[MIN] >= 0 && args[MIN] <= 100 &&
1928 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
1929 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
1930 customServoMixersMutable(i)->targetChannel = args[TARGET];
1931 customServoMixersMutable(i)->inputSource = args[INPUT];
1932 customServoMixersMutable(i)->rate = args[RATE];
1933 customServoMixersMutable(i)->speed = args[SPEED];
1934 customServoMixersMutable(i)->min = args[MIN];
1935 customServoMixersMutable(i)->max = args[MAX];
1936 customServoMixersMutable(i)->box = args[BOX];
1937 cliServoMix("");
1938 } else {
1939 cliShowParseError();
1943 #endif
1945 #ifdef USE_SDCARD
1947 static void cliWriteBytes(const uint8_t *buffer, int count)
1949 while (count > 0) {
1950 cliWrite(*buffer);
1951 buffer++;
1952 count--;
1956 static void cliSdInfo(char *cmdline)
1958 UNUSED(cmdline);
1960 cliPrint("SD card: ");
1962 if (!sdcard_isInserted()) {
1963 cliPrintLine("None inserted");
1964 return;
1967 if (!sdcard_isInitialized()) {
1968 cliPrintLine("Startup failed");
1969 return;
1972 const sdcardMetadata_t *metadata = sdcard_getMetadata();
1974 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1975 metadata->manufacturerID,
1976 metadata->numBlocks / 2, /* One block is half a kB */
1977 metadata->productionMonth,
1978 metadata->productionYear,
1979 metadata->productRevisionMajor,
1980 metadata->productRevisionMinor
1983 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
1985 cliPrint("'\r\n" "Filesystem: ");
1987 switch (afatfs_getFilesystemState()) {
1988 case AFATFS_FILESYSTEM_STATE_READY:
1989 cliPrint("Ready");
1990 break;
1991 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
1992 cliPrint("Initializing");
1993 break;
1994 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
1995 case AFATFS_FILESYSTEM_STATE_FATAL:
1996 cliPrint("Fatal");
1998 switch (afatfs_getLastError()) {
1999 case AFATFS_ERROR_BAD_MBR:
2000 cliPrint(" - no FAT MBR partitions");
2001 break;
2002 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2003 cliPrint(" - bad FAT header");
2004 break;
2005 case AFATFS_ERROR_GENERIC:
2006 case AFATFS_ERROR_NONE:
2007 ; // Nothing more detailed to print
2008 break;
2010 break;
2012 cliPrintLinefeed();
2015 #endif
2017 #ifdef USE_FLASHFS
2019 static void cliFlashInfo(char *cmdline)
2021 const flashGeometry_t *layout = flashfsGetGeometry();
2023 UNUSED(cmdline);
2025 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
2026 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
2030 static void cliFlashErase(char *cmdline)
2032 UNUSED(cmdline);
2034 #ifndef MINIMAL_CLI
2035 uint32_t i = 0;
2036 cliPrintLine("Erasing, please wait ... ");
2037 #else
2038 cliPrintLine("Erasing,");
2039 #endif
2041 bufWriterFlush(cliWriter);
2042 flashfsEraseCompletely();
2044 while (!flashfsIsReady()) {
2045 #ifndef MINIMAL_CLI
2046 cliPrintf(".");
2047 if (i++ > 120) {
2048 i=0;
2049 cliPrintLinefeed();
2052 bufWriterFlush(cliWriter);
2053 #endif
2054 delay(100);
2056 beeper(BEEPER_BLACKBOX_ERASE);
2057 cliPrintLinefeed();
2058 cliPrintLine("Done.");
2061 #ifdef USE_FLASH_TOOLS
2063 static void cliFlashWrite(char *cmdline)
2065 const uint32_t address = atoi(cmdline);
2066 const char *text = strchr(cmdline, ' ');
2068 if (!text) {
2069 cliShowParseError();
2070 } else {
2071 flashfsSeekAbs(address);
2072 flashfsWrite((uint8_t*)text, strlen(text), true);
2073 flashfsFlushSync();
2075 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2079 static void cliFlashRead(char *cmdline)
2081 uint32_t address = atoi(cmdline);
2083 const char *nextArg = strchr(cmdline, ' ');
2085 if (!nextArg) {
2086 cliShowParseError();
2087 } else {
2088 uint32_t length = atoi(nextArg);
2090 cliPrintLinef("Reading %u bytes at %u:", length, address);
2092 uint8_t buffer[32];
2093 while (length > 0) {
2094 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2096 for (int i = 0; i < bytesRead; i++) {
2097 cliWrite(buffer[i]);
2100 length -= bytesRead;
2101 address += bytesRead;
2103 if (bytesRead == 0) {
2104 //Assume we reached the end of the volume or something fatal happened
2105 break;
2108 cliPrintLinefeed();
2112 #endif
2113 #endif
2115 #ifdef USE_VTX_CONTROL
2116 static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault)
2118 // print out vtx channel settings
2119 const char *format = "vtx %u %u %u %u %u %u";
2120 bool equalsDefault = false;
2121 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2122 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2123 if (vtxConfigDefault) {
2124 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2125 equalsDefault = cac->auxChannelIndex == cacDefault->auxChannelIndex
2126 && cac->band == cacDefault->band
2127 && cac->channel == cacDefault->channel
2128 && cac->range.startStep == cacDefault->range.startStep
2129 && cac->range.endStep == cacDefault->range.endStep;
2130 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2132 cacDefault->auxChannelIndex,
2133 cacDefault->band,
2134 cacDefault->channel,
2135 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2136 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2139 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2141 cac->auxChannelIndex,
2142 cac->band,
2143 cac->channel,
2144 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2145 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2150 static void cliVtx(char *cmdline)
2152 const char *format = "vtx %u %u %u %u %u %u";
2153 int i, val = 0;
2154 const char *ptr;
2156 if (isEmpty(cmdline)) {
2157 printVtx(DUMP_MASTER, vtxConfig(), NULL);
2158 } else {
2159 ptr = cmdline;
2160 i = atoi(ptr++);
2161 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2162 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2163 uint8_t validArgumentCount = 0;
2164 ptr = nextArg(ptr);
2165 if (ptr) {
2166 val = atoi(ptr);
2167 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2168 cac->auxChannelIndex = val;
2169 validArgumentCount++;
2172 ptr = nextArg(ptr);
2173 if (ptr) {
2174 val = atoi(ptr);
2175 // FIXME Use VTX API to get min/max
2176 if (val >= VTX_SETTINGS_MIN_BAND && val <= VTX_SETTINGS_MAX_BAND) {
2177 cac->band = val;
2178 validArgumentCount++;
2181 ptr = nextArg(ptr);
2182 if (ptr) {
2183 val = atoi(ptr);
2184 // FIXME Use VTX API to get min/max
2185 if (val >= VTX_SETTINGS_MIN_CHANNEL && val <= VTX_SETTINGS_MAX_CHANNEL) {
2186 cac->channel = val;
2187 validArgumentCount++;
2190 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2192 if (validArgumentCount != 5) {
2193 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2194 cliShowParseError();
2195 } else {
2196 cliDumpPrintLinef(0, false, format,
2198 cac->auxChannelIndex,
2199 cac->band,
2200 cac->channel,
2201 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2202 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2205 } else {
2206 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2211 #endif // VTX_CONTROL
2213 static void printName(uint8_t dumpMask, const pilotConfig_t *pilotConfig)
2215 const bool equalsDefault = strlen(pilotConfig->name) == 0;
2216 cliDumpPrintLinef(dumpMask, equalsDefault, "name %s", equalsDefault ? emptyName : pilotConfig->name);
2219 static void cliName(char *cmdline)
2221 const unsigned int len = strlen(cmdline);
2222 if (len > 0) {
2223 memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
2224 if (strncmp(cmdline, emptyName, len)) {
2225 strncpy(pilotConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
2228 printName(DUMP_MASTER, pilotConfig());
2231 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2233 const uint32_t mask = featureConfig->enabledFeatures;
2234 const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2235 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
2236 if (strcmp(featureNames[i], emptryString) != 0) { //Skip unused
2237 const char *format = "feature -%s";
2238 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2239 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2242 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
2243 if (strcmp(featureNames[i], emptryString) != 0) { //Skip unused
2244 const char *format = "feature %s";
2245 if (defaultMask & (1 << i)) {
2246 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2248 if (mask & (1 << i)) {
2249 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2255 static void cliFeature(char *cmdline)
2257 uint32_t len = strlen(cmdline);
2258 uint32_t mask = featureMask();
2260 if (len == 0) {
2261 cliPrint("Enabled: ");
2262 for (uint32_t i = 0; ; i++) {
2263 if (featureNames[i] == NULL)
2264 break;
2265 if (mask & (1 << i))
2266 cliPrintf("%s ", featureNames[i]);
2268 cliPrintLinefeed();
2269 } else if (strncasecmp(cmdline, "list", len) == 0) {
2270 cliPrint("Available:");
2271 for (uint32_t i = 0; ; i++) {
2272 if (featureNames[i] == NULL)
2273 break;
2274 if (strcmp(featureNames[i], emptryString) != 0) //Skip unused
2275 cliPrintf(" %s", featureNames[i]);
2277 cliPrintLinefeed();
2278 return;
2279 } else {
2280 bool remove = false;
2281 if (cmdline[0] == '-') {
2282 // remove feature
2283 remove = true;
2284 cmdline++; // skip over -
2285 len--;
2288 for (uint32_t i = 0; ; i++) {
2289 if (featureNames[i] == NULL) {
2290 cliPrintErrorLinef("Invalid name");
2291 break;
2294 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
2296 mask = 1 << i;
2297 #ifndef USE_GPS
2298 if (mask & FEATURE_GPS) {
2299 cliPrintLine("unavailable");
2300 break;
2302 #endif
2303 #ifndef USE_RANGEFINDER
2304 if (mask & FEATURE_RANGEFINDER) {
2305 cliPrintLine("unavailable");
2306 break;
2308 #endif
2309 if (remove) {
2310 featureClear(mask);
2311 cliPrint("Disabled");
2312 } else {
2313 featureSet(mask);
2314 cliPrint("Enabled");
2316 cliPrintLinef(" %s", featureNames[i]);
2317 break;
2323 #ifdef USE_BEEPER
2324 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
2326 const uint8_t beeperCount = beeperTableEntryCount();
2327 const uint32_t mask = beeperConfig->beeper_off_flags;
2328 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
2329 for (int32_t i = 0; i < beeperCount - 2; i++) {
2330 const char *formatOff = "beeper -%s";
2331 const char *formatOn = "beeper %s";
2332 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
2333 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & beeperModeMask, mask & beeperModeMask ? formatOn : formatOff, beeperNameForTableIndex(i));
2334 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & beeperModeMask, mask & beeperModeMask ? formatOff : formatOn, beeperNameForTableIndex(i));
2338 static void cliBeeper(char *cmdline)
2340 uint32_t len = strlen(cmdline);
2341 uint8_t beeperCount = beeperTableEntryCount();
2342 uint32_t mask = getBeeperOffMask();
2344 if (len == 0) {
2345 cliPrintf("Disabled:");
2346 for (int32_t i = 0; ; i++) {
2347 if (i == beeperCount - 2) {
2348 if (mask == 0)
2349 cliPrint(" none");
2350 break;
2353 if (mask & beeperModeMaskForTableIndex(i))
2354 cliPrintf(" %s", beeperNameForTableIndex(i));
2356 cliPrintLinefeed();
2357 } else if (strncasecmp(cmdline, "list", len) == 0) {
2358 cliPrint("Available:");
2359 for (uint32_t i = 0; i < beeperCount; i++)
2360 cliPrintf(" %s", beeperNameForTableIndex(i));
2361 cliPrintLinefeed();
2362 return;
2363 } else {
2364 bool remove = false;
2365 if (cmdline[0] == '-') {
2366 remove = true; // this is for beeper OFF condition
2367 cmdline++;
2368 len--;
2371 for (uint32_t i = 0; ; i++) {
2372 if (i == beeperCount) {
2373 cliPrintErrorLinef("Invalid name");
2374 break;
2376 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
2377 if (remove) { // beeper off
2378 if (i == BEEPER_ALL-1)
2379 beeperOffSetAll(beeperCount-2);
2380 else
2381 if (i == BEEPER_PREFERENCE-1)
2382 setBeeperOffMask(getPreferredBeeperOffMask());
2383 else {
2384 beeperOffSet(beeperModeMaskForTableIndex(i));
2386 cliPrint("Disabled");
2388 else { // beeper on
2389 if (i == BEEPER_ALL-1)
2390 beeperOffClearAll();
2391 else
2392 if (i == BEEPER_PREFERENCE-1)
2393 setPreferredBeeperOffMask(getBeeperOffMask());
2394 else {
2395 beeperOffClear(beeperModeMaskForTableIndex(i));
2397 cliPrint("Enabled");
2399 cliPrintLinef(" %s", beeperNameForTableIndex(i));
2400 break;
2405 #endif
2407 void cliFrSkyBind(char *cmdline){
2408 UNUSED(cmdline);
2409 switch (rxConfig()->rx_spi_protocol) {
2410 #ifdef USE_RX_FRSKY_SPI
2411 case RX_SPI_FRSKY_D:
2412 case RX_SPI_FRSKY_X:
2413 frSkySpiBind();
2415 cliPrint("Binding...");
2417 break;
2418 #endif
2419 default:
2420 cliPrint("Not supported.");
2422 break;
2426 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
2428 bool equalsDefault = true;
2429 char buf[16];
2430 char bufDefault[16];
2431 uint32_t i;
2432 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2433 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
2434 if (defaultRxConfig) {
2435 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
2436 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
2439 buf[i] = '\0';
2441 const char *formatMap = "map %s";
2442 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
2443 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
2447 static void cliMap(char *cmdline)
2449 uint32_t i;
2450 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
2452 uint32_t len = strlen(cmdline);
2453 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
2455 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2456 buf[i] = toupper((unsigned char)cmdline[i]);
2458 buf[i] = '\0';
2460 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2461 buf[i] = toupper((unsigned char)cmdline[i]);
2463 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
2464 continue;
2466 cliShowParseError();
2467 return;
2469 parseRcChannels(buf, rxConfigMutable());
2470 } else if (len > 0) {
2471 cliShowParseError();
2472 return;
2475 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2476 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
2479 buf[i] = '\0';
2480 cliPrintLinef("map %s", buf);
2483 static char *skipSpace(char *buffer)
2485 while (*(buffer) == ' ') {
2486 buffer++;
2489 return buffer;
2492 static char *checkCommand(char *cmdLine, const char *command)
2494 if (!strncasecmp(cmdLine, command, strlen(command)) // command names match
2495 && (isspace((unsigned)cmdLine[strlen(command)]) || cmdLine[strlen(command)] == 0)) {
2496 return skipSpace(cmdLine + strlen(command) + 1);
2497 } else {
2498 return 0;
2502 static void cliRebootEx(bool bootLoader)
2504 cliPrint("\r\nRebooting");
2505 bufWriterFlush(cliWriter);
2506 waitForSerialPortToFinishTransmitting(cliPort);
2507 stopPwmAllMotors();
2508 if (bootLoader) {
2509 systemResetToBootloader();
2510 return;
2512 systemReset();
2515 static void cliReboot(void)
2517 cliRebootEx(false);
2520 static void cliBootloader(char *cmdLine)
2522 UNUSED(cmdLine);
2524 cliPrintHashLine("restarting in bootloader mode");
2525 cliRebootEx(true);
2528 static void cliExit(char *cmdline)
2530 UNUSED(cmdline);
2532 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2533 bufWriterFlush(cliWriter);
2535 *cliBuffer = '\0';
2536 bufferIndex = 0;
2537 cliMode = 0;
2538 // incase a motor was left running during motortest, clear it here
2539 mixerResetDisarmedMotors();
2540 cliReboot();
2542 cliWriter = NULL;
2545 #ifdef USE_GPS
2546 static void cliGpsPassthrough(char *cmdline)
2548 UNUSED(cmdline);
2550 gpsEnablePassthrough(cliPort);
2552 #endif
2554 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
2555 static void cliPrintGyroRegisters(uint8_t whichSensor)
2557 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
2558 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
2559 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
2562 static void cliDumpGyroRegisters(char *cmdline)
2564 #ifdef USE_DUAL_GYRO
2565 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
2566 cliPrintLinef("\r\n# Gyro 1");
2567 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
2569 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
2570 cliPrintLinef("\r\n# Gyro 2");
2571 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
2573 #else
2574 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
2575 #endif // USE_DUAL_GYRO
2576 UNUSED(cmdline);
2578 #endif
2581 static int parseOutputIndex(char *pch, bool allowAllEscs) {
2582 int outputIndex = atoi(pch);
2583 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
2584 cliPrintLinef("Using output %d.", outputIndex);
2585 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
2586 cliPrintLinef("Using all outputs.");
2587 } else {
2588 cliPrintErrorLinef("Invalid output number. Range: 0 %d.", getMotorCount() - 1);
2590 return -1;
2593 return outputIndex;
2596 #if defined(USE_DSHOT)
2597 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2599 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
2600 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
2601 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
2603 enum {
2604 ESC_INFO_KISS_V1,
2605 ESC_INFO_KISS_V2,
2606 ESC_INFO_BLHELI32
2609 #define ESC_INFO_VERSION_POSITION 12
2611 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
2613 bool escInfoReceived = false;
2614 if (bytesRead > ESC_INFO_VERSION_POSITION) {
2615 uint8_t escInfoVersion;
2616 uint8_t frameLength;
2617 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
2618 escInfoVersion = ESC_INFO_BLHELI32;
2619 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
2620 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
2621 escInfoVersion = ESC_INFO_KISS_V2;
2622 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
2623 } else {
2624 escInfoVersion = ESC_INFO_KISS_V1;
2625 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
2628 if (bytesRead == frameLength) {
2629 escInfoReceived = true;
2631 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
2632 uint8_t firmwareVersion = 0;
2633 uint8_t firmwareSubVersion = 0;
2634 uint8_t escType = 0;
2635 switch (escInfoVersion) {
2636 case ESC_INFO_KISS_V1:
2637 firmwareVersion = escInfoBuffer[12];
2638 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
2639 escType = (escInfoBuffer[13] & 0xe0) >> 5;
2641 break;
2642 case ESC_INFO_KISS_V2:
2643 firmwareVersion = escInfoBuffer[13];
2644 firmwareSubVersion = escInfoBuffer[14];
2645 escType = escInfoBuffer[15];
2647 break;
2648 case ESC_INFO_BLHELI32:
2649 firmwareVersion = escInfoBuffer[13];
2650 firmwareSubVersion = escInfoBuffer[14];
2651 escType = escInfoBuffer[15];
2653 break;
2656 cliPrint("ESC Type: ");
2657 switch (escInfoVersion) {
2658 case ESC_INFO_KISS_V1:
2659 case ESC_INFO_KISS_V2:
2660 switch (escType) {
2661 case 1:
2662 cliPrintLine("KISS8A");
2664 break;
2665 case 2:
2666 cliPrintLine("KISS16A");
2668 break;
2669 case 3:
2670 cliPrintLine("KISS24A");
2672 break;
2673 case 5:
2674 cliPrintLine("KISS Ultralite");
2676 break;
2677 default:
2678 cliPrintLine("unknown");
2680 break;
2683 break;
2684 case ESC_INFO_BLHELI32:
2686 char *escType = (char *)(escInfoBuffer + 31);
2687 escType[32] = 0;
2688 cliPrintLine(escType);
2691 break;
2694 cliPrint("MCU Serial No: 0x");
2695 for (int i = 0; i < 12; i++) {
2696 if (i && (i % 3 == 0)) {
2697 cliPrint("-");
2699 cliPrintf("%02x", escInfoBuffer[i]);
2701 cliPrintLinefeed();
2703 switch (escInfoVersion) {
2704 case ESC_INFO_KISS_V1:
2705 case ESC_INFO_KISS_V2:
2706 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
2708 break;
2709 case ESC_INFO_BLHELI32:
2710 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
2712 break;
2714 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
2715 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
2716 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
2717 if (escInfoVersion == ESC_INFO_BLHELI32) {
2718 uint8_t setting = escInfoBuffer[18];
2719 cliPrint("Low voltage Limit: ");
2720 switch (setting) {
2721 case 0:
2722 cliPrintLine("off");
2724 break;
2725 case 255:
2726 cliPrintLine("unsupported");
2728 break;
2729 default:
2730 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
2732 break;
2735 setting = escInfoBuffer[19];
2736 cliPrint("Current Limit: ");
2737 switch (setting) {
2738 case 0:
2739 cliPrintLine("off");
2741 break;
2742 case 255:
2743 cliPrintLine("unsupported");
2745 break;
2746 default:
2747 cliPrintLinef("%d", setting);
2749 break;
2752 for (int i = 0; i < 4; i++) {
2753 setting = escInfoBuffer[i + 20];
2754 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
2758 } else {
2759 cliPrintErrorLinef("Checksum Error.");
2764 if (!escInfoReceived) {
2765 cliPrintLine("No Info.");
2769 static void executeEscInfoCommand(uint8_t escIndex)
2771 cliPrintLinef("Info for ESC %d:", escIndex);
2773 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
2775 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
2777 pwmWriteDshotCommand(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO);
2779 delay(10);
2781 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
2783 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
2785 static void cliDshotProg(char *cmdline)
2787 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
2788 cliShowParseError();
2790 return;
2793 char *saveptr;
2794 char *pch = strtok_r(cmdline, " ", &saveptr);
2795 int pos = 0;
2796 int escIndex = 0;
2797 bool firstCommand = true;
2798 while (pch != NULL) {
2799 switch (pos) {
2800 case 0:
2801 escIndex = parseOutputIndex(pch, true);
2802 if (escIndex == -1) {
2803 return;
2806 break;
2807 default:
2809 int command = atoi(pch);
2810 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
2811 if (firstCommand) {
2812 pwmDisableMotors();
2814 if (command == DSHOT_CMD_ESC_INFO) {
2815 delay(5); // Wait for potential ESC telemetry transmission to finish
2816 } else {
2817 delay(1);
2820 firstCommand = false;
2823 if (command != DSHOT_CMD_ESC_INFO) {
2824 pwmWriteDshotCommand(escIndex, getMotorCount(), command);
2825 } else {
2826 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2827 if (feature(FEATURE_ESC_SENSOR)) {
2828 if (escIndex != ALL_MOTORS) {
2829 executeEscInfoCommand(escIndex);
2830 } else {
2831 for (uint8_t i = 0; i < getMotorCount(); i++) {
2832 executeEscInfoCommand(i);
2835 } else
2836 #endif
2838 cliPrintLine("Not supported.");
2842 cliPrintLinef("Command Sent: %d", command);
2844 if (command <= 5) {
2845 delay(20); // wait for sound output to finish
2847 } else {
2848 cliPrintErrorLinef("Invalid command. Range: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
2852 break;
2855 pos++;
2856 pch = strtok_r(NULL, " ", &saveptr);
2859 pwmEnableMotors();
2861 #endif // USE_DSHOT
2863 #ifdef USE_ESCSERIAL
2864 static void cliEscPassthrough(char *cmdline)
2866 if (isEmpty(cmdline)) {
2867 cliShowParseError();
2869 return;
2872 char *saveptr;
2873 char *pch = strtok_r(cmdline, " ", &saveptr);
2874 int pos = 0;
2875 uint8_t mode = 0;
2876 int escIndex = 0;
2877 while (pch != NULL) {
2878 switch (pos) {
2879 case 0:
2880 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
2881 mode = PROTOCOL_SIMONK;
2882 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
2883 mode = PROTOCOL_BLHELI;
2884 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
2885 mode = PROTOCOL_KISS;
2886 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
2887 mode = PROTOCOL_KISSALL;
2888 } else {
2889 cliShowParseError();
2891 return;
2893 break;
2894 case 1:
2895 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
2896 if (escIndex == -1) {
2897 return;
2900 break;
2901 default:
2902 cliShowParseError();
2904 return;
2906 break;
2909 pos++;
2910 pch = strtok_r(NULL, " ", &saveptr);
2913 escEnablePassthrough(cliPort, escIndex, mode);
2915 #endif
2917 #ifndef USE_QUAD_MIXER_ONLY
2918 static void cliMixer(char *cmdline)
2920 int len;
2922 len = strlen(cmdline);
2924 if (len == 0) {
2925 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
2926 return;
2927 } else if (strncasecmp(cmdline, "list", len) == 0) {
2928 cliPrint("Available:");
2929 for (uint32_t i = 0; ; i++) {
2930 if (mixerNames[i] == NULL)
2931 break;
2932 cliPrintf(" %s", mixerNames[i]);
2934 cliPrintLinefeed();
2935 return;
2938 for (uint32_t i = 0; ; i++) {
2939 if (mixerNames[i] == NULL) {
2940 cliPrintErrorLinef("Invalid name");
2941 return;
2943 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
2944 mixerConfigMutable()->mixerMode = i + 1;
2945 break;
2949 cliMixer("");
2951 #endif
2953 static void cliMotor(char *cmdline)
2955 if (isEmpty(cmdline)) {
2956 cliShowParseError();
2958 return;
2961 int motorIndex = 0;
2962 int motorValue = 0;
2964 char *saveptr;
2965 char *pch = strtok_r(cmdline, " ", &saveptr);
2966 int index = 0;
2967 while (pch != NULL) {
2968 switch (index) {
2969 case 0:
2970 motorIndex = parseOutputIndex(pch, true);
2971 if (motorIndex == -1) {
2972 return;
2975 break;
2976 case 1:
2977 motorValue = atoi(pch);
2979 break;
2981 index++;
2982 pch = strtok_r(NULL, " ", &saveptr);
2985 if (index == 2) {
2986 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
2987 cliShowArgumentRangeError("value", 1000, 2000);
2988 } else {
2989 uint32_t motorOutputValue = convertExternalToMotor(motorValue);
2991 if (motorIndex != ALL_MOTORS) {
2992 motor_disarmed[motorIndex] = motorOutputValue;
2994 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
2995 } else {
2996 for (int i = 0; i < getMotorCount(); i++) {
2997 motor_disarmed[i] = motorOutputValue;
3000 cliPrintLinef("all motors: %d", motorOutputValue);
3003 } else {
3004 cliShowParseError();
3008 #ifndef MINIMAL_CLI
3009 static void cliPlaySound(char *cmdline)
3011 int i;
3012 const char *name;
3013 static int lastSoundIdx = -1;
3015 if (isEmpty(cmdline)) {
3016 i = lastSoundIdx + 1; //next sound index
3017 if ((name=beeperNameForTableIndex(i)) == NULL) {
3018 while (true) { //no name for index; try next one
3019 if (++i >= beeperTableEntryCount())
3020 i = 0; //if end then wrap around to first entry
3021 if ((name=beeperNameForTableIndex(i)) != NULL)
3022 break; //if name OK then play sound below
3023 if (i == lastSoundIdx + 1) { //prevent infinite loop
3024 cliPrintErrorLinef("Error playing sound");
3025 return;
3029 } else { //index value was given
3030 i = atoi(cmdline);
3031 if ((name=beeperNameForTableIndex(i)) == NULL) {
3032 cliPrintLinef("No sound for index %d", i);
3033 return;
3036 lastSoundIdx = i;
3037 beeperSilence();
3038 cliPrintLinef("Playing sound %d: %s", i, name);
3039 beeper(beeperModeForTableIndex(i));
3041 #endif
3043 static void cliProfile(char *cmdline)
3045 if (isEmpty(cmdline)) {
3046 cliPrintLinef("profile %d", getCurrentPidProfileIndex());
3047 return;
3048 } else {
3049 const int i = atoi(cmdline);
3050 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3051 systemConfigMutable()->pidProfileIndex = i;
3052 cliProfile("");
3057 static void cliRateProfile(char *cmdline)
3059 if (isEmpty(cmdline)) {
3060 cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
3061 return;
3062 } else {
3063 const int i = atoi(cmdline);
3064 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
3065 changeControlRateProfile(i);
3066 cliRateProfile("");
3071 static void cliDumpPidProfile(uint8_t pidProfileIndex, uint8_t dumpMask)
3073 if (pidProfileIndex >= MAX_PROFILE_COUNT) {
3074 // Faulty values
3075 return;
3077 changePidProfile(pidProfileIndex);
3078 cliPrintHashLine("profile");
3079 cliProfile("");
3080 cliPrintLinefeed();
3081 dumpAllValues(PROFILE_VALUE, dumpMask);
3084 static void cliDumpRateProfile(uint8_t rateProfileIndex, uint8_t dumpMask)
3086 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
3087 // Faulty values
3088 return;
3090 changeControlRateProfile(rateProfileIndex);
3091 cliPrintHashLine("rateprofile");
3092 cliRateProfile("");
3093 cliPrintLinefeed();
3094 dumpAllValues(PROFILE_RATE_VALUE, dumpMask);
3097 static void cliSave(char *cmdline)
3099 UNUSED(cmdline);
3101 cliPrintHashLine("saving");
3102 writeEEPROM();
3103 cliReboot();
3106 static void cliDefaults(char *cmdline)
3108 bool saveConfigs;
3110 if (isEmpty(cmdline)) {
3111 saveConfigs = true;
3112 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
3113 saveConfigs = false;
3114 } else {
3115 return;
3118 cliPrintHashLine("resetting to defaults");
3119 resetConfigs();
3120 if (saveConfigs) {
3121 cliSave(NULL);
3125 void cliPrintVarDefault(const clivalue_t *value)
3127 const pgRegistry_t *pg = pgFind(value->pgn);
3128 if (pg) {
3129 const char *defaultFormat = "Default value: ";
3130 const int valueOffset = getValueOffset(value);
3131 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
3132 if (!equalsDefault) {
3133 cliPrintf(defaultFormat, value->name);
3134 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
3135 cliPrintLinefeed();
3140 STATIC_UNIT_TESTED void cliGet(char *cmdline)
3142 const clivalue_t *val;
3143 int matchedCommands = 0;
3145 backupAndResetConfigs();
3147 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3148 if (strcasestr(valueTable[i].name, cmdline)) {
3149 val = &valueTable[i];
3150 if (matchedCommands > 0) {
3151 cliPrintLinefeed();
3153 cliPrintf("%s = ", valueTable[i].name);
3154 cliPrintVar(val, 0);
3155 cliPrintLinefeed();
3156 cliPrintVarRange(val);
3157 cliPrintVarDefault(val);
3158 matchedCommands++;
3162 restoreConfigs();
3164 if (matchedCommands) {
3165 return;
3168 cliPrintErrorLinef("Invalid name");
3171 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
3173 while (*(bufEnd - 1) == ' ') {
3174 bufEnd--;
3177 return bufEnd - bufBegin;
3180 STATIC_UNIT_TESTED void cliSet(char *cmdline)
3182 const uint32_t len = strlen(cmdline);
3183 char *eqptr;
3185 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
3186 cliPrintLine("Current settings: ");
3188 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3189 const clivalue_t *val = &valueTable[i];
3190 cliPrintf("%s = ", valueTable[i].name);
3191 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3192 cliPrintLinefeed();
3194 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
3195 // has equals
3197 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
3199 // skip the '=' and any ' ' characters
3200 eqptr++;
3201 eqptr = skipSpace(eqptr);
3203 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3204 const clivalue_t *val = &valueTable[i];
3206 // ensure exact match when setting to prevent setting variables with shorter names
3207 if (strncasecmp(cmdline, val->name, strlen(val->name)) == 0 && variableNameLength == strlen(val->name)) {
3209 bool valueChanged = false;
3210 int16_t value = 0;
3211 switch (val->type & VALUE_MODE_MASK) {
3212 case MODE_DIRECT: {
3213 int16_t value = atoi(eqptr);
3215 if (value >= val->config.minmax.min && value <= val->config.minmax.max) {
3216 cliSetVar(val, value);
3217 valueChanged = true;
3221 break;
3222 case MODE_LOOKUP:
3223 case MODE_BITSET: {
3224 int tableIndex;
3225 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
3226 tableIndex = TABLE_OFF_ON;
3227 } else {
3228 tableIndex = val->config.lookup.tableIndex;
3230 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
3231 bool matched = false;
3232 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
3233 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
3235 if (matched) {
3236 value = tableValueIndex;
3238 cliSetVar(val, value);
3239 valueChanged = true;
3244 break;
3246 case MODE_ARRAY: {
3247 const uint8_t arrayLength = val->config.array.length;
3248 char *valPtr = eqptr;
3250 int i = 0;
3251 while (i < arrayLength && valPtr != NULL) {
3252 // skip spaces
3253 valPtr = skipSpace(valPtr);
3255 // process substring starting at valPtr
3256 // note: no need to copy substrings for atoi()
3257 // it stops at the first character that cannot be converted...
3258 switch (val->type & VALUE_TYPE_MASK) {
3259 default:
3260 case VAR_UINT8:
3262 // fetch data pointer
3263 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
3264 // store value
3265 *data = (uint8_t)atoi((const char*) valPtr);
3268 break;
3269 case VAR_INT8:
3271 // fetch data pointer
3272 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
3273 // store value
3274 *data = (int8_t)atoi((const char*) valPtr);
3277 break;
3278 case VAR_UINT16:
3280 // fetch data pointer
3281 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
3282 // store value
3283 *data = (uint16_t)atoi((const char*) valPtr);
3286 break;
3287 case VAR_INT16:
3289 // fetch data pointer
3290 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
3291 // store value
3292 *data = (int16_t)atoi((const char*) valPtr);
3295 break;
3298 // find next comma (or end of string)
3299 valPtr = strchr(valPtr, ',') + 1;
3301 i++;
3305 // mark as changed
3306 valueChanged = true;
3308 break;
3312 if (valueChanged) {
3313 cliPrintf("%s set to ", val->name);
3314 cliPrintVar(val, 0);
3315 } else {
3316 cliPrintErrorLinef("Invalid value");
3317 cliPrintVarRange(val);
3320 return;
3323 cliPrintErrorLinef("Invalid name");
3324 } else {
3325 // no equals, check for matching variables.
3326 cliGet(cmdline);
3330 static void cliStatus(char *cmdline)
3332 UNUSED(cmdline);
3334 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3336 #ifdef USE_RTC_TIME
3337 char buf[FORMATTED_DATE_TIME_BUFSIZE];
3338 dateTime_t dt;
3339 if (rtcGetDateTime(&dt)) {
3340 dateTimeFormatLocal(buf, &dt);
3341 cliPrintLinef("Current Time: %s", buf);
3343 #endif
3345 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
3347 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3349 #ifdef USE_ADC_INTERNAL
3350 uint16_t vrefintMv = getVrefMv();
3351 int16_t coretemp = getCoreTemperatureCelsius();
3352 cliPrintf(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
3353 #endif
3355 #if defined(USE_SENSOR_NAMES)
3356 const uint32_t detectedSensorsMask = sensorsMask();
3357 for (uint32_t i = 0; ; i++) {
3358 if (sensorTypeNames[i] == NULL) {
3359 break;
3361 const uint32_t mask = (1 << i);
3362 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3363 const uint8_t sensorHardwareIndex = detectedSensors[i];
3364 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3365 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3366 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
3367 cliPrintf(".%c", acc.dev.revisionCode);
3371 #endif /* USE_SENSOR_NAMES */
3372 cliPrintLinefeed();
3374 #ifdef USE_SDCARD
3375 cliSdInfo(NULL);
3376 #endif
3378 #ifdef USE_I2C
3379 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3380 #else
3381 const uint16_t i2cErrorCounter = 0;
3382 #endif
3384 #ifdef STACK_CHECK
3385 cliPrintf("Stack used: %d, ", stackUsedSize());
3386 #endif
3387 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
3388 #ifdef EEPROM_IN_RAM
3389 #define CONFIG_SIZE EEPROM_SIZE
3390 #else
3391 #define CONFIG_SIZE (&__config_end - &__config_start)
3392 #endif
3393 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), CONFIG_SIZE);
3395 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
3396 const int rxRate = currentRxRefreshRate == 0 ? 0 : (int)(1000000.0f / ((float)currentRxRefreshRate));
3397 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3398 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
3399 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
3400 cliPrint("Arming disable flags:");
3401 armingDisableFlags_e flags = getArmingDisableFlags();
3402 while (flags) {
3403 const int bitpos = ffs(flags) - 1;
3404 flags &= ~(1 << bitpos);
3405 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
3407 cliPrintLinefeed();
3410 #ifndef SKIP_TASK_STATISTICS
3411 static void cliTasks(char *cmdline)
3413 UNUSED(cmdline);
3414 int maxLoadSum = 0;
3415 int averageLoadSum = 0;
3417 #ifndef MINIMAL_CLI
3418 if (systemConfig()->task_statistics) {
3419 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
3420 } else {
3421 cliPrintLine("Task list");
3423 #endif
3424 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3425 cfTaskInfo_t taskInfo;
3426 getTaskInfo(taskId, &taskInfo);
3427 if (taskInfo.isEnabled) {
3428 int taskFrequency;
3429 int subTaskFrequency = 0;
3430 if (taskId == TASK_GYROPID) {
3431 subTaskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3432 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
3433 if (pidConfig()->pid_process_denom > 1) {
3434 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3435 } else {
3436 taskFrequency = subTaskFrequency;
3437 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
3439 } else {
3440 taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3441 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3443 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3444 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3445 if (taskId != TASK_SERIAL) {
3446 maxLoadSum += maxLoad;
3447 averageLoadSum += averageLoad;
3449 if (systemConfig()->task_statistics) {
3450 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
3451 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
3452 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
3453 } else {
3454 cliPrintLinef("%6d", taskFrequency);
3456 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
3457 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
3461 if (systemConfig()->task_statistics) {
3462 cfCheckFuncInfo_t checkFuncInfo;
3463 getCheckFuncInfo(&checkFuncInfo);
3464 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
3465 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3468 #endif
3470 static void cliVersion(char *cmdline)
3472 UNUSED(cmdline);
3474 cliPrintLinef("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
3475 FC_FIRMWARE_NAME,
3476 targetName,
3477 systemConfig()->boardIdentifier,
3478 FC_VERSION_STRING,
3479 buildDate,
3480 buildTime,
3481 shortGitRevision,
3482 MSP_API_VERSION_STRING
3486 #if defined(USE_RESOURCE_MGMT)
3488 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
3490 typedef struct {
3491 const uint8_t owner;
3492 pgn_t pgn;
3493 uint8_t stride;
3494 uint8_t offset;
3495 const uint8_t maxIndex;
3496 } cliResourceValue_t;
3498 // Handy macros for keeping the table tidy.
3499 // DEFS : Single entry
3500 // DEFA : Array of uint8_t (stride = 1)
3501 // DEFW : Wider stride case; array of structs.
3503 #define DEFS(owner, pgn, type, member) \
3504 { owner, pgn, 0, offsetof(type, member), 0 }
3506 #define DEFA(owner, pgn, type, member, max) \
3507 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
3509 #define DEFW(owner, pgn, type, member, max) \
3510 { owner, pgn, sizeof(type), offsetof(type, member), max }
3512 const cliResourceValue_t resourceTable[] = {
3513 #ifdef USE_BEEPER
3514 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
3515 #endif
3516 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
3517 #ifdef USE_SERVOS
3518 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
3519 #endif
3520 #if defined(USE_PWM) || defined(USE_PPM)
3521 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
3522 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
3523 #endif
3524 #ifdef USE_RANGEFINDER_HCSR04
3525 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
3526 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
3527 #endif
3528 #ifdef USE_LED_STRIP
3529 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
3530 #endif
3531 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
3532 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
3533 #ifdef USE_INVERTER
3534 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
3535 #endif
3536 #ifdef USE_I2C
3537 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
3538 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
3539 #endif
3540 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
3541 #ifdef USE_SPEKTRUM_BIND
3542 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
3543 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
3544 #endif
3545 #ifdef USE_TRANSPONDER
3546 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
3547 #endif
3548 #ifdef USE_SPI
3549 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
3550 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
3551 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
3552 #endif
3553 #ifdef USE_ESCSERIAL
3554 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
3555 #endif
3556 #ifdef USE_CAMERA_CONTROL
3557 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
3558 #endif
3559 #ifdef USE_ADC
3560 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
3561 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
3562 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
3563 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
3564 #endif
3565 #ifdef USE_BARO
3566 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
3567 #endif
3568 #ifdef USE_MAG
3569 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
3570 #ifdef USE_MAG_DATA_READY_SIGNAL
3571 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
3572 #endif
3573 #endif
3574 #ifdef USE_SDCARD
3575 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
3576 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
3577 #endif
3578 #ifdef USE_PINIO
3579 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
3580 #endif
3581 #if defined(USE_USB_MSC)
3582 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
3583 #endif
3584 #ifdef USE_FLASH
3585 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
3586 #endif
3587 #ifdef USE_MAX7456
3588 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
3589 #endif
3590 #ifdef USE_SPI
3591 DEFA( OWNER_SPI_PREINIT_IPU, PG_SPI_PREINIT_IPU_CONFIG, spiCs_t, csnTag, SPI_PREINIT_IPU_COUNT ),
3592 DEFA( OWNER_SPI_PREINIT_OPU, PG_SPI_PREINIT_OPU_CONFIG, spiCs_t, csnTag, SPI_PREINIT_OPU_COUNT ),
3593 #endif
3596 #undef DEFS
3597 #undef DEFA
3598 #undef DEFW
3600 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
3602 const pgRegistry_t* rec = pgFind(value.pgn);
3603 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
3606 static void printResource(uint8_t dumpMask)
3608 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
3609 const char* owner = ownerNames[resourceTable[i].owner];
3610 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
3611 const void *currentConfig;
3612 const void *defaultConfig;
3613 if (configIsInCopy) {
3614 currentConfig = pg->copy;
3615 defaultConfig = pg->address;
3616 } else {
3617 currentConfig = pg->address;
3618 defaultConfig = NULL;
3621 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
3622 const ioTag_t ioTag = *((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
3623 const ioTag_t ioTagDefault = *((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
3625 bool equalsDefault = ioTag == ioTagDefault;
3626 const char *format = "resource %s %d %c%02d";
3627 const char *formatUnassigned = "resource %s %d NONE";
3628 if (!ioTagDefault) {
3629 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3630 } else {
3631 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
3633 if (!ioTag) {
3634 if (!(dumpMask & HIDE_UNUSED)) {
3635 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3637 } else {
3638 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
3644 static void printResourceOwner(uint8_t owner, uint8_t index)
3646 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
3648 if (resourceTable[owner].maxIndex > 0) {
3649 cliPrintf(" %d", RESOURCE_INDEX(index));
3653 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
3655 if (!newTag) {
3656 return;
3659 const char * format = "\r\nNOTE: %c%02d already assigned to ";
3660 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
3661 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
3662 ioTag_t *tag = getIoTag(resourceTable[r], i);
3663 if (*tag == newTag) {
3664 bool cleared = false;
3665 if (r == resourceIndex) {
3666 if (i == index) {
3667 continue;
3669 *tag = IO_TAG_NONE;
3670 cleared = true;
3673 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
3675 printResourceOwner(r, i);
3677 if (cleared) {
3678 cliPrintf(". ");
3679 printResourceOwner(r, i);
3680 cliPrintf(" disabled");
3683 cliPrintLine(".");
3689 static bool strToPin(char *pch, ioTag_t *tag)
3691 if (strcasecmp(pch, "NONE") == 0) {
3692 *tag = IO_TAG_NONE;
3693 return true;
3694 } else {
3695 unsigned pin = 0;
3696 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
3698 if (port < 8) {
3699 pch++;
3700 pin = atoi(pch);
3701 if (pin < 16) {
3702 *tag = DEFIO_TAG_MAKE(port, pin);
3703 return true;
3707 return false;
3710 static void cliResource(char *cmdline)
3712 int len = strlen(cmdline);
3714 if (len == 0) {
3715 printResource(DUMP_MASTER | HIDE_UNUSED);
3717 return;
3718 } else if (strncasecmp(cmdline, "list", len) == 0) {
3719 #ifdef MINIMAL_CLI
3720 cliPrintLine("IO");
3721 #else
3722 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
3723 cliRepeat('-', 20);
3724 #endif
3725 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3726 const char* owner;
3727 owner = ownerNames[ioRecs[i].owner];
3729 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
3730 if (ioRecs[i].index > 0) {
3731 cliPrintf(" %d", ioRecs[i].index);
3733 cliPrintLinefeed();
3736 #ifndef MINIMAL_CLI
3737 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
3738 #endif
3740 return;
3743 uint8_t resourceIndex = 0;
3744 int index = 0;
3745 char *pch = NULL;
3746 char *saveptr;
3748 pch = strtok_r(cmdline, " ", &saveptr);
3749 for (resourceIndex = 0; ; resourceIndex++) {
3750 if (resourceIndex >= ARRAYLEN(resourceTable)) {
3751 cliPrintErrorLinef("Invalid");
3752 return;
3755 if (strncasecmp(pch, ownerNames[resourceTable[resourceIndex].owner], len) == 0) {
3756 break;
3760 pch = strtok_r(NULL, " ", &saveptr);
3761 index = atoi(pch);
3763 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
3764 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
3765 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
3766 return;
3768 index -= 1;
3770 pch = strtok_r(NULL, " ", &saveptr);
3773 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
3775 if (strlen(pch) > 0) {
3776 if (strToPin(pch, tag)) {
3777 if (*tag == IO_TAG_NONE) {
3778 #ifdef MINIMAL_CLI
3779 cliPrintLine("Freed");
3780 #else
3781 cliPrintLine("Resource is freed");
3782 #endif
3783 return;
3784 } else {
3785 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
3786 if (rec) {
3787 resourceCheck(resourceIndex, index, *tag);
3788 #ifdef MINIMAL_CLI
3789 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
3790 #else
3791 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
3792 #endif
3793 } else {
3794 cliShowParseError();
3796 return;
3801 cliShowParseError();
3804 static void printDma(void)
3806 cliPrintLinefeed();
3808 #ifdef MINIMAL_CLI
3809 cliPrintLine("DMA:");
3810 #else
3811 cliPrintLine("Currently active DMA:");
3812 cliRepeat('-', 20);
3813 #endif
3814 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
3815 const char* owner;
3816 owner = ownerNames[dmaGetOwner(i)];
3818 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
3819 uint8_t resourceIndex = dmaGetResourceIndex(i);
3820 if (resourceIndex > 0) {
3821 cliPrintLinef(" %s %d", owner, resourceIndex);
3822 } else {
3823 cliPrintLinef(" %s", owner);
3828 static void cliDma(char* cmdLine)
3830 UNUSED(cmdLine);
3831 printDma();
3833 #endif /* USE_RESOURCE_MGMT */
3835 #ifdef USE_TIMER_MGMT
3837 static void printTimer(uint8_t dumpMask)
3839 cliPrintLine("# examples: ");
3840 const char *format = "timer %c%02d %d";
3841 cliPrint("#");
3842 cliPrintLinef(format, 'A', 1, 1);
3844 cliPrint("#");
3845 cliPrintLinef(format, 'A', 1, 0);
3847 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
3849 const ioTag_t ioTag = timerIOConfig(i)->ioTag;
3850 const uint8_t timerIndex = timerIOConfig(i)->index;
3852 if (!ioTag) {
3853 continue;
3856 if (timerIndex != 0 && !(dumpMask & HIDE_UNUSED)) {
3857 cliDumpPrintLinef(dumpMask, false, format,
3858 IO_GPIOPortIdxByTag(ioTag) + 'A',
3859 IO_GPIOPinIdxByTag(ioTag),
3860 timerIndex
3866 static void cliTimer(char *cmdline)
3868 int len = strlen(cmdline);
3870 if (len == 0) {
3871 printTimer(DUMP_MASTER | HIDE_UNUSED);
3872 return;
3873 } else if (strncasecmp(cmdline, "list", len) == 0) {
3874 printTimer(DUMP_MASTER);
3875 return;
3878 char *pch = NULL;
3879 char *saveptr;
3880 int timerIOIndex = -1;
3882 ioTag_t ioTag = 0;
3883 pch = strtok_r(cmdline, " ", &saveptr);
3884 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
3885 goto error;
3888 /* find existing entry, or go for next available */
3889 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
3890 if (timerIOConfig(i)->ioTag == ioTag) {
3891 timerIOIndex = i;
3892 break;
3895 /* first available empty slot */
3896 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
3897 timerIOIndex = i;
3901 if (timerIOIndex < 0) {
3902 cliPrintErrorLinef("Index out of range.");
3903 return;
3906 uint8_t timerIndex = 0;
3907 pch = strtok_r(NULL, " ", &saveptr);
3908 if (pch) {
3909 if (strcasecmp(pch, "list") == 0) {
3910 /* output the list of available options */
3911 uint8_t index = 1;
3912 for (unsigned i = 0; i < USABLE_TIMER_CHANNEL_COUNT; i++) {
3913 if (timerHardware[i].tag == ioTag) {
3914 cliPrintLinef("# %d. TIM%d CH%d",
3915 index,
3916 timerGetTIMNumber(timerHardware[i].tim),
3917 CC_INDEX_FROM_CHANNEL(timerHardware[i].channel)
3919 index++;
3922 return;
3923 } else if (strcasecmp(pch, "none") == 0) {
3924 goto success;
3925 } else {
3926 timerIndex = atoi(pch);
3928 } else {
3929 goto error;
3932 success:
3933 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == 0 ? IO_TAG_NONE : ioTag;
3934 timerIOConfigMutable(timerIOIndex)->index = timerIndex;
3936 cliPrintLine("Success");
3937 return;
3939 error:
3940 cliShowParseError();
3942 #endif
3944 static void printConfig(char *cmdline, bool doDiff)
3946 uint8_t dumpMask = DUMP_MASTER;
3947 char *options;
3948 if ((options = checkCommand(cmdline, "master"))) {
3949 dumpMask = DUMP_MASTER; // only
3950 } else if ((options = checkCommand(cmdline, "profile"))) {
3951 dumpMask = DUMP_PROFILE; // only
3952 } else if ((options = checkCommand(cmdline, "rates"))) {
3953 dumpMask = DUMP_RATES; // only
3954 } else if ((options = checkCommand(cmdline, "all"))) {
3955 dumpMask = DUMP_ALL; // all profiles and rates
3956 } else {
3957 options = cmdline;
3960 if (doDiff) {
3961 dumpMask = dumpMask | DO_DIFF;
3964 backupAndResetConfigs();
3965 if (checkCommand(options, "defaults")) {
3966 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
3969 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
3970 cliPrintHashLine("version");
3971 cliVersion(NULL);
3973 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
3974 cliPrintHashLine("reset configuration to default settings");
3975 cliPrint("defaults nosave");
3976 cliPrintLinefeed();
3979 cliPrintHashLine("name");
3980 printName(dumpMask, &pilotConfig_Copy);
3982 #ifdef USE_RESOURCE_MGMT
3983 cliPrintHashLine("resources");
3984 printResource(dumpMask);
3985 #endif
3987 #ifndef USE_QUAD_MIXER_ONLY
3988 cliPrintHashLine("mixer");
3989 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
3990 const char *formatMixer = "mixer %s";
3991 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
3992 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
3994 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
3996 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0));
3998 #ifdef USE_SERVOS
3999 cliPrintHashLine("servo");
4000 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
4002 cliPrintHashLine("servo mix");
4003 // print custom servo mixer if exists
4004 cliDumpPrintLinef(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n");
4005 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
4006 #endif
4007 #endif
4009 cliPrintHashLine("feature");
4010 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
4012 #ifdef USE_BEEPER
4013 cliPrintHashLine("beeper");
4014 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
4015 #endif
4017 cliPrintHashLine("map");
4018 printMap(dumpMask, &rxConfig_Copy, rxConfig());
4020 cliPrintHashLine("serial");
4021 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
4023 #ifdef USE_LED_STRIP
4024 cliPrintHashLine("led");
4025 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
4027 cliPrintHashLine("color");
4028 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
4030 cliPrintHashLine("mode_color");
4031 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
4032 #endif
4034 cliPrintHashLine("aux");
4035 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
4037 cliPrintHashLine("adjrange");
4038 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
4040 cliPrintHashLine("rxrange");
4041 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
4043 #ifdef USE_VTX_CONTROL
4044 cliPrintHashLine("vtx");
4045 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig());
4046 #endif
4048 cliPrintHashLine("rxfail");
4049 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0));
4051 cliPrintHashLine("master");
4052 dumpAllValues(MASTER_VALUE, dumpMask);
4054 if (dumpMask & DUMP_ALL) {
4055 const uint8_t pidProfileIndexSave = systemConfig_Copy.pidProfileIndex;
4056 for (uint32_t pidProfileIndex = 0; pidProfileIndex < MAX_PROFILE_COUNT; pidProfileIndex++) {
4057 cliDumpPidProfile(pidProfileIndex, dumpMask);
4059 changePidProfile(pidProfileIndexSave);
4060 cliPrintHashLine("restore original profile selection");
4061 cliProfile("");
4063 const uint8_t controlRateProfileIndexSave = systemConfig_Copy.activeRateProfile;
4064 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
4065 cliDumpRateProfile(rateIndex, dumpMask);
4067 changeControlRateProfile(controlRateProfileIndexSave);
4068 cliPrintHashLine("restore original rateprofile selection");
4069 cliRateProfile("");
4071 cliPrintHashLine("save configuration");
4072 cliPrint("save");
4073 } else {
4074 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
4076 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
4080 if (dumpMask & DUMP_PROFILE) {
4081 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
4084 if (dumpMask & DUMP_RATES) {
4085 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
4087 // restore configs from copies
4088 restoreConfigs();
4091 static void cliDump(char *cmdline)
4093 printConfig(cmdline, false);
4096 static void cliDiff(char *cmdline)
4098 printConfig(cmdline, true);
4101 #ifdef USE_USB_MSC
4102 static void cliMsc(char *cmdline)
4104 UNUSED(cmdline);
4106 if (false
4107 #ifdef USE_SDCARD
4108 || sdcard_isFunctional()
4109 #endif
4110 #ifdef USE_FLASHFS
4111 || flashfsGetSize() > 0
4112 #endif
4114 cliPrintHashLine("restarting in mass storage mode");
4115 cliPrint("\r\nRebooting");
4116 bufWriterFlush(cliWriter);
4117 delay(1000);
4118 waitForSerialPortToFinishTransmitting(cliPort);
4119 stopPwmAllMotors();
4120 if (mpuResetFn) {
4121 mpuResetFn();
4124 #ifdef STM32F7
4125 *((__IO uint32_t*) BKPSRAM_BASE + 16) = MSC_MAGIC;
4126 #elif defined(STM32F4)
4127 *((uint32_t *)0x2001FFF0) = MSC_MAGIC;
4128 #endif
4130 __disable_irq();
4131 NVIC_SystemReset();
4132 } else {
4133 cliPrint("\r\nStorage not present or failed to initialize!");
4134 bufWriterFlush(cliWriter);
4137 #endif
4139 typedef struct {
4140 const char *name;
4141 #ifndef MINIMAL_CLI
4142 const char *description;
4143 const char *args;
4144 #endif
4145 void (*func)(char *cmdline);
4146 } clicmd_t;
4148 #ifndef MINIMAL_CLI
4149 #define CLI_COMMAND_DEF(name, description, args, method) \
4151 name , \
4152 description , \
4153 args , \
4154 method \
4156 #else
4157 #define CLI_COMMAND_DEF(name, description, args, method) \
4159 name, \
4160 method \
4162 #endif
4164 static void cliHelp(char *cmdline);
4166 // should be sorted a..z for bsearch()
4167 const clicmd_t cmdTable[] = {
4168 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
4169 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
4170 #ifdef USE_BEEPER
4171 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
4172 "\t<+|->[name]", cliBeeper),
4173 #endif
4174 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL, cliBootloader),
4175 #ifdef USE_LED_STRIP
4176 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
4177 #endif
4178 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults),
4179 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|all] {defaults}", cliDiff),
4180 #ifdef USE_RESOURCE_MGMT
4181 CLI_COMMAND_DEF("dma", "list dma utilisation", NULL, cliDma),
4182 #endif
4183 #ifdef USE_DSHOT
4184 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
4185 #endif
4186 CLI_COMMAND_DEF("dump", "dump configuration",
4187 "[master|profile|rates|all] {defaults}", cliDump),
4188 #ifdef USE_ESCSERIAL
4189 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
4190 #endif
4191 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
4192 CLI_COMMAND_DEF("feature", "configure features",
4193 "list\r\n"
4194 "\t<+|->[name]", cliFeature),
4195 #ifdef USE_FLASHFS
4196 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
4197 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
4198 #ifdef USE_FLASH_TOOLS
4199 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
4200 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
4201 #endif
4202 #endif
4203 #ifdef USE_RX_FRSKY_SPI
4204 CLI_COMMAND_DEF("frsky_bind", "initiate binding for FrSky SPI RX", NULL, cliFrSkyBind),
4205 #endif
4206 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
4207 #ifdef USE_GPS
4208 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
4209 #endif
4210 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
4211 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
4212 #endif
4213 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
4214 #ifdef USE_LED_STRIP
4215 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
4216 #endif
4217 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
4218 #ifndef USE_QUAD_MIXER_ONLY
4219 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
4220 #endif
4221 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
4222 #ifdef USE_LED_STRIP
4223 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
4224 #endif
4225 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
4226 #ifdef USE_USB_MSC
4227 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
4228 #endif
4229 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
4230 #ifndef MINIMAL_CLI
4231 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
4232 #endif
4233 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
4234 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
4235 #ifdef USE_RESOURCE_MGMT
4236 CLI_COMMAND_DEF("resource", "show/set resources", NULL, cliResource),
4237 #endif
4238 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
4239 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
4240 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
4241 #ifdef USE_SDCARD
4242 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
4243 #endif
4244 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
4245 #ifndef SKIP_SERIAL_PASSTHROUGH
4246 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [DTR PINIO]: passthrough to serial", cliSerialPassthrough),
4247 #endif
4248 #ifdef USE_SERVOS
4249 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
4250 #endif
4251 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
4252 #ifdef USE_SERVOS
4253 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
4254 "\treset\r\n"
4255 "\tload <mixer>\r\n"
4256 "\treverse <servo> <source> r|n", cliServoMix),
4257 #endif
4258 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
4259 #ifndef SKIP_TASK_STATISTICS
4260 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
4261 #endif
4262 #ifdef USE_TIMER_MGMT
4263 CLI_COMMAND_DEF("timer", "show timer configuration", NULL, cliTimer),
4264 #endif
4265 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
4266 #ifdef USE_VTX_CONTROL
4267 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
4268 #endif
4271 static void cliHelp(char *cmdline)
4273 UNUSED(cmdline);
4275 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
4276 cliPrint(cmdTable[i].name);
4277 #ifndef MINIMAL_CLI
4278 if (cmdTable[i].description) {
4279 cliPrintf(" - %s", cmdTable[i].description);
4281 if (cmdTable[i].args) {
4282 cliPrintf("\r\n\t%s", cmdTable[i].args);
4284 #endif
4285 cliPrintLinefeed();
4289 void cliProcess(void)
4291 if (!cliWriter) {
4292 return;
4295 // Be a little bit tricky. Flush the last inputs buffer, if any.
4296 bufWriterFlush(cliWriter);
4298 while (serialRxBytesWaiting(cliPort)) {
4299 uint8_t c = serialRead(cliPort);
4300 if (c == '\t' || c == '?') {
4301 // do tab completion
4302 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
4303 uint32_t i = bufferIndex;
4304 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4305 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
4306 continue;
4307 if (!pstart)
4308 pstart = cmd;
4309 pend = cmd;
4311 if (pstart) { /* Buffer matches one or more commands */
4312 for (; ; bufferIndex++) {
4313 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
4314 break;
4315 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
4316 /* Unambiguous -- append a space */
4317 cliBuffer[bufferIndex++] = ' ';
4318 cliBuffer[bufferIndex] = '\0';
4319 break;
4321 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
4324 if (!bufferIndex || pstart != pend) {
4325 /* Print list of ambiguous matches */
4326 cliPrint("\r\033[K");
4327 for (cmd = pstart; cmd <= pend; cmd++) {
4328 cliPrint(cmd->name);
4329 cliWrite('\t');
4331 cliPrompt();
4332 i = 0; /* Redraw prompt */
4334 for (; i < bufferIndex; i++)
4335 cliWrite(cliBuffer[i]);
4336 } else if (!bufferIndex && c == 4) { // CTRL-D
4337 cliExit(cliBuffer);
4338 return;
4339 } else if (c == 12) { // NewPage / CTRL-L
4340 // clear screen
4341 cliPrint("\033[2J\033[1;1H");
4342 cliPrompt();
4343 } else if (bufferIndex && (c == '\n' || c == '\r')) {
4344 // enter pressed
4345 cliPrintLinefeed();
4347 // Strip comment starting with # from line
4348 char *p = cliBuffer;
4349 p = strchr(p, '#');
4350 if (NULL != p) {
4351 bufferIndex = (uint32_t)(p - cliBuffer);
4354 // Strip trailing whitespace
4355 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
4356 bufferIndex--;
4359 // Process non-empty lines
4360 if (bufferIndex > 0) {
4361 cliBuffer[bufferIndex] = 0; // null terminate
4363 const clicmd_t *cmd;
4364 char *options;
4365 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4366 if ((options = checkCommand(cliBuffer, cmd->name))) {
4367 break;
4370 if (cmd < cmdTable + ARRAYLEN(cmdTable))
4371 cmd->func(options);
4372 else
4373 cliPrint("Unknown command, try 'help'");
4374 bufferIndex = 0;
4377 memset(cliBuffer, 0, sizeof(cliBuffer));
4379 // 'exit' will reset this flag, so we don't need to print prompt again
4380 if (!cliMode)
4381 return;
4383 cliPrompt();
4384 } else if (c == 127) {
4385 // backspace
4386 if (bufferIndex) {
4387 cliBuffer[--bufferIndex] = 0;
4388 cliPrint("\010 \010");
4390 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
4391 if (!bufferIndex && c == ' ')
4392 continue; // Ignore leading spaces
4393 cliBuffer[bufferIndex++] = c;
4394 cliWrite(c);
4399 void cliEnter(serialPort_t *serialPort)
4401 cliMode = 1;
4402 cliPort = serialPort;
4403 setPrintfSerialPort(cliPort);
4404 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
4406 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
4408 #ifndef MINIMAL_CLI
4409 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4410 #else
4411 cliPrintLine("\r\nCLI");
4412 #endif
4413 cliPrompt();
4415 setArmingDisabled(ARMING_DISABLED_CLI);
4418 void cliInit(const serialConfig_t *serialConfig)
4420 UNUSED(serialConfig);
4422 #endif // USE_CLI