Merge pull request #8671 from mikeller/fix_dshot_includes
[betaflight.git] / src / main / cli / cli.c
blob43063b4cbe9aeaab1995a30aa021dac8198234c6
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;
35 #ifdef USE_CLI
37 #include "blackbox/blackbox.h"
39 #include "build/build_config.h"
40 #include "build/debug.h"
41 #include "build/version.h"
43 #include "cli/settings.h"
45 #include "cms/cms.h"
47 #include "common/axis.h"
48 #include "common/color.h"
49 #include "common/maths.h"
50 #include "common/printf.h"
51 #include "common/printf_serial.h"
52 #include "common/strtol.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/dma_reqmap.h"
65 #include "drivers/dshot.h"
66 #include "drivers/dshot_command.h"
67 #include "drivers/dshot_dpwm.h"
68 #include "drivers/camera_control.h"
69 #include "drivers/compass/compass.h"
70 #include "drivers/display.h"
71 #include "drivers/dma.h"
72 #include "drivers/flash.h"
73 #include "drivers/inverter.h"
74 #include "drivers/io.h"
75 #include "drivers/io_impl.h"
76 #include "drivers/light_led.h"
77 #include "drivers/motor.h"
78 #include "drivers/rangefinder/rangefinder_hcsr04.h"
79 #include "drivers/sdcard.h"
80 #include "drivers/sensor.h"
81 #include "drivers/serial.h"
82 #include "drivers/serial_escserial.h"
83 #include "drivers/sound_beeper.h"
84 #include "drivers/stack_check.h"
85 #include "drivers/system.h"
86 #include "drivers/time.h"
87 #include "drivers/timer.h"
88 #include "drivers/transponder_ir.h"
89 #include "drivers/usb_msc.h"
90 #include "drivers/vtx_common.h"
91 #include "drivers/vtx_table.h"
93 #include "fc/board_info.h"
94 #include "fc/config.h"
95 #include "fc/controlrate_profile.h"
96 #include "fc/core.h"
97 #include "fc/rc.h"
98 #include "fc/rc_adjustments.h"
99 #include "fc/rc_controls.h"
100 #include "fc/runtime_config.h"
102 #include "flight/failsafe.h"
103 #include "flight/imu.h"
104 #include "flight/mixer.h"
105 #include "flight/pid.h"
106 #include "flight/position.h"
107 #include "flight/servos.h"
109 #include "io/asyncfatfs/asyncfatfs.h"
110 #include "io/beeper.h"
111 #include "io/flashfs.h"
112 #include "io/gimbal.h"
113 #include "io/gps.h"
114 #include "io/ledstrip.h"
115 #include "io/serial.h"
116 #include "io/transponder_ir.h"
117 #include "io/usb_msc.h"
118 #include "io/vtx_control.h"
119 #include "io/vtx.h"
121 #include "msp/msp.h"
122 #include "msp/msp_box.h"
123 #include "msp/msp_protocol.h"
125 #include "osd/osd.h"
127 #include "pg/adc.h"
128 #include "pg/beeper.h"
129 #include "pg/beeper_dev.h"
130 #include "pg/board.h"
131 #include "pg/bus_i2c.h"
132 #include "pg/bus_spi.h"
133 #include "pg/gyrodev.h"
134 #include "pg/max7456.h"
135 #include "pg/mco.h"
136 #include "pg/motor.h"
137 #include "pg/pinio.h"
138 #include "pg/pg.h"
139 #include "pg/pg_ids.h"
140 #include "pg/rx.h"
141 #include "pg/rx_spi_cc2500.h"
142 #include "pg/rx_spi.h"
143 #include "pg/rx_pwm.h"
144 #include "pg/serial_uart.h"
145 #include "pg/sdio.h"
146 #include "pg/timerio.h"
147 #include "pg/timerup.h"
148 #include "pg/usb.h"
149 #include "pg/vtx_table.h"
151 #include "rx/rx.h"
152 #include "rx/spektrum.h"
153 #include "rx/rx_spi_common.h"
154 #include "rx/srxl2.h"
156 #include "scheduler/scheduler.h"
158 #include "sensors/acceleration.h"
159 #include "sensors/adcinternal.h"
160 #include "sensors/barometer.h"
161 #include "sensors/battery.h"
162 #include "sensors/boardalignment.h"
163 #include "sensors/compass.h"
164 #include "sensors/esc_sensor.h"
165 #include "sensors/gyro.h"
166 #include "sensors/sensors.h"
168 #include "telemetry/frsky_hub.h"
169 #include "telemetry/telemetry.h"
171 #include "cli.h"
173 typedef struct serialPassthroughPort_e {
174 int id;
175 uint32_t baud;
176 unsigned mode;
177 serialPort_t *port;
178 } serialPassthroughPort_t;
180 static serialPort_t *cliPort;
182 #ifdef STM32F1
183 #define CLI_IN_BUFFER_SIZE 128
184 #else
185 // Space required to set array parameters
186 #define CLI_IN_BUFFER_SIZE 256
187 #endif
188 #define CLI_OUT_BUFFER_SIZE 64
190 static bufWriter_t *cliWriter;
191 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
193 static char cliBuffer[CLI_IN_BUFFER_SIZE];
194 static uint32_t bufferIndex = 0;
196 static bool configIsInCopy = false;
198 #define CURRENT_PROFILE_INDEX -1
199 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
200 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
202 static bool featureMaskIsCopied = false;
203 static uint32_t featureMaskCopy;
205 #ifdef USE_CLI_BATCH
206 static bool commandBatchActive = false;
207 static bool commandBatchError = false;
208 #endif
210 #if defined(USE_BOARD_INFO)
211 static bool boardInformationUpdated = false;
212 #if defined(USE_SIGNATURE)
213 static bool signatureUpdated = false;
214 #endif
215 #endif // USE_BOARD_INFO
217 static const char* const emptyName = "-";
218 static const char* const emptyString = "";
220 #ifndef USE_QUAD_MIXER_ONLY
221 // sync this with mixerMode_e
222 static const char * const mixerNames[] = {
223 "TRI", "QUADP", "QUADX", "BI",
224 "GIMBAL", "Y6", "HEX6",
225 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
226 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
227 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
228 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
230 #endif
232 // sync this with features_e
233 static const char * const featureNames[] = {
234 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
235 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
236 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
237 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
238 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
239 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
242 // sync this with rxFailsafeChannelMode_e
243 static const char rxFailsafeModeCharacters[] = "ahs";
245 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
246 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
247 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
250 #if defined(USE_SENSOR_NAMES)
251 // sync this with sensors_e
252 static const char * const sensorTypeNames[] = {
253 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
256 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
258 static const char * const *sensorHardwareNames[] = {
259 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
261 #endif // USE_SENSOR_NAMES
263 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
264 extern uint32_t readDoneCount;
265 extern uint32_t inputBuffer[GCR_TELEMETRY_INPUT_LEN];
266 extern uint32_t setDirectionMicros;
267 #endif
269 typedef enum dumpFlags_e {
270 DUMP_MASTER = (1 << 0),
271 DUMP_PROFILE = (1 << 1),
272 DUMP_RATES = (1 << 2),
273 DUMP_ALL = (1 << 3),
274 DO_DIFF = (1 << 4),
275 SHOW_DEFAULTS = (1 << 5),
276 HIDE_UNUSED = (1 << 6),
277 HARDWARE_ONLY = (1 << 7),
278 BARE = (1 << 8),
279 } dumpFlags_t;
281 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
283 typedef enum {
284 REBOOT_TARGET_FIRMWARE,
285 REBOOT_TARGET_BOOTLOADER_ROM,
286 REBOOT_TARGET_BOOTLOADER_FLASH,
287 } rebootTarget_e;
289 static void backupPgConfig(const pgRegistry_t *pg)
291 memcpy(pg->copy, pg->address, pg->size);
294 static void restorePgConfig(const pgRegistry_t *pg)
296 memcpy(pg->address, pg->copy, pg->size);
299 static void backupConfigs(void)
301 // make copies of configs to do differencing
302 PG_FOREACH(pg) {
303 backupPgConfig(pg);
306 configIsInCopy = true;
309 static void restoreConfigs(void)
311 PG_FOREACH(pg) {
312 restorePgConfig(pg);
315 configIsInCopy = false;
318 static void backupAndResetConfigs(void)
320 backupConfigs();
321 // reset all configs to defaults to do differencing
322 resetConfigs();
325 static void cliPrint(const char *str)
327 while (*str) {
328 bufWriterAppend(cliWriter, *str++);
330 bufWriterFlush(cliWriter);
333 static void cliPrintLinefeed(void)
335 cliPrint("\r\n");
338 static void cliPrintLine(const char *str)
340 cliPrint(str);
341 cliPrintLinefeed();
344 #ifdef MINIMAL_CLI
345 #define cliPrintHashLine(str)
346 #else
347 static void cliPrintHashLine(const char *str)
349 cliPrint("\r\n# ");
350 cliPrintLine(str);
352 #endif
354 static void cliPutp(void *p, char ch)
356 bufWriterAppend(p, ch);
359 static void cliPrintfva(const char *format, va_list va)
361 tfp_format(cliWriter, cliPutp, format, va);
362 bufWriterFlush(cliWriter);
365 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
367 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
368 va_list va;
369 va_start(va, format);
370 cliPrintfva(format, va);
371 va_end(va);
372 cliPrintLinefeed();
373 return true;
374 } else {
375 return false;
379 static void cliWrite(uint8_t ch)
381 bufWriterAppend(cliWriter, ch);
384 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
386 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
387 cliWrite('#');
389 va_list va;
390 va_start(va, format);
391 cliPrintfva(format, va);
392 va_end(va);
393 cliPrintLinefeed();
394 return true;
395 } else {
396 return false;
400 static void cliPrintf(const char *format, ...)
402 va_list va;
403 va_start(va, format);
404 cliPrintfva(format, va);
405 va_end(va);
409 static void cliPrintLinef(const char *format, ...)
411 va_list va;
412 va_start(va, format);
413 cliPrintfva(format, va);
414 va_end(va);
415 cliPrintLinefeed();
418 static void cliPrintErrorVa(const char *format, va_list va)
420 cliPrint("###ERROR: ");
421 cliPrintfva(format, va);
422 va_end(va);
423 cliPrint("###");
425 #ifdef USE_CLI_BATCH
426 if (commandBatchActive) {
427 commandBatchError = true;
429 #endif
432 static void cliPrintError(const char *format, ...)
434 va_list va;
435 va_start(va, format);
436 cliPrintErrorVa(format, va);
439 static void cliPrintErrorLinef(const char *format, ...)
441 va_list va;
442 va_start(va, format);
443 cliPrintErrorVa(format, va);
444 cliPrintLinefeed();
447 static void getMinMax(const clivalue_t *var, int *min, int *max)
449 switch (var->type & VALUE_TYPE_MASK) {
450 case VAR_UINT8:
451 case VAR_UINT16:
452 *min = var->config.minmaxUnsigned.min;
453 *max = var->config.minmaxUnsigned.max;
455 break;
456 default:
457 *min = var->config.minmax.min;
458 *max = var->config.minmax.max;
460 break;
464 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
466 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
467 for (int i = 0; i < var->config.array.length; i++) {
468 switch (var->type & VALUE_TYPE_MASK) {
469 default:
470 case VAR_UINT8:
471 // uint8_t array
472 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
473 break;
475 case VAR_INT8:
476 // int8_t array
477 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
478 break;
480 case VAR_UINT16:
481 // uin16_t array
482 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
483 break;
485 case VAR_INT16:
486 // int16_t array
487 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
488 break;
490 case VAR_UINT32:
491 // uin32_t array
492 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
493 break;
496 if (i < var->config.array.length - 1) {
497 cliPrint(",");
500 } else {
501 int value = 0;
503 switch (var->type & VALUE_TYPE_MASK) {
504 case VAR_UINT8:
505 value = *(uint8_t *)valuePointer;
507 break;
508 case VAR_INT8:
509 value = *(int8_t *)valuePointer;
511 break;
512 case VAR_UINT16:
513 value = *(uint16_t *)valuePointer;
515 break;
516 case VAR_INT16:
517 value = *(int16_t *)valuePointer;
519 break;
520 case VAR_UINT32:
521 value = *(uint32_t *)valuePointer;
523 break;
526 bool valueIsCorrupted = false;
527 switch (var->type & VALUE_MODE_MASK) {
528 case MODE_DIRECT:
529 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
530 cliPrintf("%u", (uint32_t)value);
531 if ((uint32_t)value > var->config.u32Max) {
532 valueIsCorrupted = true;
533 } else if (full) {
534 cliPrintf(" 0 %u", var->config.u32Max);
536 } else {
537 int min;
538 int max;
539 getMinMax(var, &min, &max);
541 cliPrintf("%d", value);
542 if ((value < min) || (value > max)) {
543 valueIsCorrupted = true;
544 } else if (full) {
545 cliPrintf(" %d %d", min, max);
548 break;
549 case MODE_LOOKUP:
550 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
551 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
552 } else {
553 valueIsCorrupted = true;
555 break;
556 case MODE_BITSET:
557 if (value & 1 << var->config.bitpos) {
558 cliPrintf("ON");
559 } else {
560 cliPrintf("OFF");
562 break;
563 case MODE_STRING:
564 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
565 break;
568 if (valueIsCorrupted) {
569 cliPrintLinefeed();
570 cliPrintError("CORRUPTED CONFIG: %s = %d", var->name, value);
576 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
578 bool result = true;
579 int elementCount = 1;
580 uint32_t mask = 0xffffffff;
582 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
583 elementCount = var->config.array.length;
585 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
586 mask = 1 << var->config.bitpos;
588 for (int i = 0; i < elementCount; i++) {
589 switch (var->type & VALUE_TYPE_MASK) {
590 case VAR_UINT8:
591 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
592 break;
594 case VAR_INT8:
595 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
596 break;
598 case VAR_UINT16:
599 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
600 break;
601 case VAR_INT16:
602 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
603 break;
604 case VAR_UINT32:
605 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
606 break;
610 return result;
613 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
615 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
616 cliPrintHashLine(headingStr);
617 return NULL;
618 } else {
619 return headingStr;
623 static uint8_t getPidProfileIndexToUse()
625 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
628 static uint8_t getRateProfileIndexToUse()
630 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
634 static uint16_t getValueOffset(const clivalue_t *value)
636 switch (value->type & VALUE_SECTION_MASK) {
637 case MASTER_VALUE:
638 case HARDWARE_VALUE:
639 return value->offset;
640 case PROFILE_VALUE:
641 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
642 case PROFILE_RATE_VALUE:
643 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
645 return 0;
648 void *cliGetValuePointer(const clivalue_t *value)
650 const pgRegistry_t* rec = pgFind(value->pgn);
651 if (configIsInCopy) {
652 return CONST_CAST(void *, rec->copy + getValueOffset(value));
653 } else {
654 return CONST_CAST(void *, rec->address + getValueOffset(value));
658 const void *cliGetDefaultPointer(const clivalue_t *value)
660 const pgRegistry_t* rec = pgFind(value->pgn);
661 return rec->address + getValueOffset(value);
664 static const char *dumpPgValue(const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
666 const pgRegistry_t *pg = pgFind(value->pgn);
667 #ifdef DEBUG
668 if (!pg) {
669 cliPrintLinef("VALUE %s ERROR", value->name);
670 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
672 #endif
674 const char *format = "set %s = ";
675 const char *defaultFormat = "#set %s = ";
676 const int valueOffset = getValueOffset(value);
677 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
679 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
680 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
681 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
682 cliPrintf(defaultFormat, value->name);
683 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
684 cliPrintLinefeed();
686 cliPrintf(format, value->name);
687 printValuePointer(value, pg->copy + valueOffset, false);
688 cliPrintLinefeed();
690 return headingStr;
693 static void dumpAllValues(uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
695 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
697 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
698 const clivalue_t *value = &valueTable[i];
699 bufWriterFlush(cliWriter);
700 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
701 headingStr = dumpPgValue(value, dumpMask, headingStr);
706 static void cliPrintVar(const clivalue_t *var, bool full)
708 const void *ptr = cliGetValuePointer(var);
710 printValuePointer(var, ptr, full);
713 static void cliPrintVarRange(const clivalue_t *var)
715 switch (var->type & VALUE_MODE_MASK) {
716 case (MODE_DIRECT): {
717 switch (var->type & VALUE_TYPE_MASK) {
718 case VAR_UINT32:
719 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
721 break;
722 case VAR_UINT8:
723 case VAR_UINT16:
724 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
726 break;
727 default:
728 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
730 break;
733 break;
734 case (MODE_LOOKUP): {
735 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
736 cliPrint("Allowed values: ");
737 bool firstEntry = true;
738 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
739 if (tableEntry->values[i]) {
740 if (!firstEntry) {
741 cliPrint(", ");
743 cliPrintf("%s", tableEntry->values[i]);
744 firstEntry = false;
747 cliPrintLinefeed();
749 break;
750 case (MODE_ARRAY): {
751 cliPrintLinef("Array length: %d", var->config.array.length);
753 break;
754 case (MODE_BITSET): {
755 cliPrintLinef("Allowed values: OFF, ON");
757 break;
761 static void cliSetVar(const clivalue_t *var, const uint32_t value)
763 void *ptr = cliGetValuePointer(var);
764 uint32_t workValue;
765 uint32_t mask;
767 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
768 switch (var->type & VALUE_TYPE_MASK) {
769 case VAR_UINT8:
770 mask = (1 << var->config.bitpos) & 0xff;
771 if (value) {
772 workValue = *(uint8_t *)ptr | mask;
773 } else {
774 workValue = *(uint8_t *)ptr & ~mask;
776 *(uint8_t *)ptr = workValue;
777 break;
779 case VAR_UINT16:
780 mask = (1 << var->config.bitpos) & 0xffff;
781 if (value) {
782 workValue = *(uint16_t *)ptr | mask;
783 } else {
784 workValue = *(uint16_t *)ptr & ~mask;
786 *(uint16_t *)ptr = workValue;
787 break;
789 case VAR_UINT32:
790 mask = 1 << var->config.bitpos;
791 if (value) {
792 workValue = *(uint32_t *)ptr | mask;
793 } else {
794 workValue = *(uint32_t *)ptr & ~mask;
796 *(uint32_t *)ptr = workValue;
797 break;
799 } else {
800 switch (var->type & VALUE_TYPE_MASK) {
801 case VAR_UINT8:
802 *(uint8_t *)ptr = value;
803 break;
805 case VAR_INT8:
806 *(int8_t *)ptr = value;
807 break;
809 case VAR_UINT16:
810 *(uint16_t *)ptr = value;
811 break;
813 case VAR_INT16:
814 *(int16_t *)ptr = value;
815 break;
817 case VAR_UINT32:
818 *(uint32_t *)ptr = value;
819 break;
824 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
825 static void cliRepeat(char ch, uint8_t len)
827 for (int i = 0; i < len; i++) {
828 bufWriterAppend(cliWriter, ch);
830 cliPrintLinefeed();
832 #endif
834 static void cliPrompt(void)
836 cliPrint("\r\n# ");
839 static void cliShowParseError(void)
841 cliPrintErrorLinef("PARSE ERROR");
844 static void cliShowArgumentRangeError(char *name, int min, int max)
846 cliPrintErrorLinef("%s NOT BETWEEN %d AND %d", name, min, max);
849 static const char *nextArg(const char *currentArg)
851 const char *ptr = strchr(currentArg, ' ');
852 while (ptr && *ptr == ' ') {
853 ptr++;
856 return ptr;
859 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
861 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
862 ptr = nextArg(ptr);
863 if (ptr) {
864 int val = atoi(ptr);
865 val = CHANNEL_VALUE_TO_STEP(val);
866 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
867 if (argIndex == 0) {
868 range->startStep = val;
869 } else {
870 range->endStep = val;
872 (*validArgumentCount)++;
877 return ptr;
880 // Check if a string's length is zero
881 static bool isEmpty(const char *string)
883 return (string == NULL || *string == '\0') ? true : false;
886 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
888 // print out rxConfig failsafe settings
889 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
890 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
891 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
892 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
893 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
894 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
895 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
896 if (requireValue) {
897 const char *format = "rxfail %u %c %d";
898 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
899 channel,
900 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
901 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
903 cliDumpPrintLinef(dumpMask, equalsDefault, format,
904 channel,
905 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
906 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
908 } else {
909 const char *format = "rxfail %u %c";
910 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
911 channel,
912 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
914 cliDumpPrintLinef(dumpMask, equalsDefault, format,
915 channel,
916 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
922 static void cliRxFailsafe(char *cmdline)
924 uint8_t channel;
925 char buf[3];
927 if (isEmpty(cmdline)) {
928 // print out rxConfig failsafe settings
929 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
930 cliRxFailsafe(itoa(channel, buf, 10));
932 } else {
933 const char *ptr = cmdline;
934 channel = atoi(ptr++);
935 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
937 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
939 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
940 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
941 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
943 ptr = nextArg(ptr);
944 if (ptr) {
945 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
946 if (p) {
947 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
948 mode = rxFailsafeModesTable[type][requestedMode];
949 } else {
950 mode = RX_FAILSAFE_MODE_INVALID;
952 if (mode == RX_FAILSAFE_MODE_INVALID) {
953 cliShowParseError();
954 return;
957 requireValue = mode == RX_FAILSAFE_MODE_SET;
959 ptr = nextArg(ptr);
960 if (ptr) {
961 if (!requireValue) {
962 cliShowParseError();
963 return;
965 uint16_t value = atoi(ptr);
966 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
967 if (value > MAX_RXFAIL_RANGE_STEP) {
968 cliPrintLine("Value out of range");
969 return;
972 channelFailsafeConfig->step = value;
973 } else if (requireValue) {
974 cliShowParseError();
975 return;
977 channelFailsafeConfig->mode = mode;
980 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
982 // double use of cliPrintf below
983 // 1. acknowledge interpretation on command,
984 // 2. query current setting on single item,
986 if (requireValue) {
987 cliPrintLinef("rxfail %u %c %d",
988 channel,
989 modeCharacter,
990 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
992 } else {
993 cliPrintLinef("rxfail %u %c",
994 channel,
995 modeCharacter
998 } else {
999 cliShowArgumentRangeError("CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1004 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1006 const char *format = "aux %u %u %u %u %u %u %u";
1007 // print out aux channel settings
1008 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1009 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1010 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1011 bool equalsDefault = false;
1012 if (defaultModeActivationConditions) {
1013 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1014 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1015 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1016 const box_t *box = findBoxByBoxId(macDefault->modeId);
1017 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1018 if (box) {
1019 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1021 box->permanentId,
1022 macDefault->auxChannelIndex,
1023 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1024 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1025 macDefault->modeLogic,
1026 linkedTo ? linkedTo->permanentId : 0
1030 const box_t *box = findBoxByBoxId(mac->modeId);
1031 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1032 if (box) {
1033 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1035 box->permanentId,
1036 mac->auxChannelIndex,
1037 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1038 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1039 mac->modeLogic,
1040 linkedTo ? linkedTo->permanentId : 0
1046 static void cliAux(char *cmdline)
1048 int i, val = 0;
1049 const char *ptr;
1051 if (isEmpty(cmdline)) {
1052 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1053 } else {
1054 ptr = cmdline;
1055 i = atoi(ptr++);
1056 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1057 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1058 uint8_t validArgumentCount = 0;
1059 ptr = nextArg(ptr);
1060 if (ptr) {
1061 val = atoi(ptr);
1062 const box_t *box = findBoxByPermanentId(val);
1063 if (box) {
1064 mac->modeId = box->boxId;
1065 validArgumentCount++;
1068 ptr = nextArg(ptr);
1069 if (ptr) {
1070 val = atoi(ptr);
1071 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1072 mac->auxChannelIndex = val;
1073 validArgumentCount++;
1076 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1077 ptr = nextArg(ptr);
1078 if (ptr) {
1079 val = atoi(ptr);
1080 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1081 mac->modeLogic = val;
1082 validArgumentCount++;
1085 ptr = nextArg(ptr);
1086 if (ptr) {
1087 val = atoi(ptr);
1088 const box_t *box = findBoxByPermanentId(val);
1089 if (box) {
1090 mac->linkedTo = box->boxId;
1091 validArgumentCount++;
1094 if (validArgumentCount == 4) { // for backwards compatibility
1095 mac->modeLogic = MODELOGIC_OR;
1096 mac->linkedTo = 0;
1097 } else if (validArgumentCount == 5) { // for backwards compatibility
1098 mac->linkedTo = 0;
1099 } else if (validArgumentCount != 6) {
1100 memset(mac, 0, sizeof(modeActivationCondition_t));
1102 analyzeModeActivationConditions();
1103 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1105 findBoxByBoxId(mac->modeId)->permanentId,
1106 mac->auxChannelIndex,
1107 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1108 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1109 mac->modeLogic,
1110 findBoxByBoxId(mac->linkedTo)->permanentId
1112 } else {
1113 cliShowArgumentRangeError("INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1118 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1120 const char *format = "serial %d %d %ld %ld %ld %ld";
1121 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1122 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
1123 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1124 continue;
1126 bool equalsDefault = false;
1127 if (serialConfigDefault) {
1128 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1129 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1130 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1131 serialConfigDefault->portConfigs[i].identifier,
1132 serialConfigDefault->portConfigs[i].functionMask,
1133 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1134 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1135 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1136 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1139 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1140 serialConfig->portConfigs[i].identifier,
1141 serialConfig->portConfigs[i].functionMask,
1142 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1143 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1144 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1145 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1150 static void cliSerial(char *cmdline)
1152 const char *format = "serial %d %d %ld %ld %ld %ld";
1153 if (isEmpty(cmdline)) {
1154 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1155 return;
1157 serialPortConfig_t portConfig;
1158 memset(&portConfig, 0 , sizeof(portConfig));
1160 serialPortConfig_t *currentConfig;
1162 uint8_t validArgumentCount = 0;
1164 const char *ptr = cmdline;
1166 int val = atoi(ptr++);
1167 currentConfig = serialFindPortConfiguration(val);
1168 if (currentConfig) {
1169 portConfig.identifier = val;
1170 validArgumentCount++;
1173 ptr = nextArg(ptr);
1174 if (ptr) {
1175 val = atoi(ptr);
1176 portConfig.functionMask = val & 0xFFFF;
1177 validArgumentCount++;
1180 for (int i = 0; i < 4; i ++) {
1181 ptr = nextArg(ptr);
1182 if (!ptr) {
1183 break;
1186 val = atoi(ptr);
1188 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1189 if (baudRates[baudRateIndex] != (uint32_t) val) {
1190 break;
1193 switch (i) {
1194 case 0:
1195 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1196 continue;
1198 portConfig.msp_baudrateIndex = baudRateIndex;
1199 break;
1200 case 1:
1201 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1202 continue;
1204 portConfig.gps_baudrateIndex = baudRateIndex;
1205 break;
1206 case 2:
1207 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1208 continue;
1210 portConfig.telemetry_baudrateIndex = baudRateIndex;
1211 break;
1212 case 3:
1213 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1214 continue;
1216 portConfig.blackbox_baudrateIndex = baudRateIndex;
1217 break;
1220 validArgumentCount++;
1223 if (validArgumentCount < 6) {
1224 cliShowParseError();
1225 return;
1228 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1230 cliDumpPrintLinef(0, false, format,
1231 portConfig.identifier,
1232 portConfig.functionMask,
1233 baudRates[portConfig.msp_baudrateIndex],
1234 baudRates[portConfig.gps_baudrateIndex],
1235 baudRates[portConfig.telemetry_baudrateIndex],
1236 baudRates[portConfig.blackbox_baudrateIndex]
1241 #if defined(USE_SERIAL_PASSTHROUGH)
1242 static void cbCtrlLine(void *context, uint16_t ctrl)
1244 #ifdef USE_PINIO
1245 int contextValue = (int)(long)context;
1246 if (contextValue) {
1247 pinioSet(contextValue - 1, !(ctrl & CTRL_LINE_STATE_DTR));
1248 } else
1249 #endif /* USE_PINIO */
1250 UNUSED(context);
1252 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1253 systemReset();
1257 static int cliParseSerialMode(const char *tok)
1259 int mode = 0;
1261 if (strcasestr(tok, "rx")) {
1262 mode |= MODE_RX;
1264 if (strcasestr(tok, "tx")) {
1265 mode |= MODE_TX;
1268 return mode;
1271 static void cliSerialPassthrough(char *cmdline)
1273 if (isEmpty(cmdline)) {
1274 cliShowParseError();
1275 return;
1278 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, NULL}, {cliPort->identifier, 0, 0, cliPort} };
1279 bool enableBaudCb = false;
1280 int port1PinioDtr = 0;
1281 bool port1ResetOnDtr = false;
1282 bool escSensorPassthrough = false;
1283 char *saveptr;
1284 char* tok = strtok_r(cmdline, " ", &saveptr);
1285 int index = 0;
1287 while (tok != NULL) {
1288 switch (index) {
1289 case 0:
1290 if (strcasestr(tok, "esc_sensor")) {
1291 escSensorPassthrough = true;
1292 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1293 ports[0].id = portConfig->identifier;
1294 } else {
1295 ports[0].id = atoi(tok);
1297 break;
1298 case 1:
1299 ports[0].baud = atoi(tok);
1300 break;
1301 case 2:
1302 ports[0].mode = cliParseSerialMode(tok);
1303 break;
1304 case 3:
1305 if (strncasecmp(tok, "reset", strlen(tok)) == 0) {
1306 port1ResetOnDtr = true;
1307 #ifdef USE_PINIO
1308 } else if (strncasecmp(tok, "none", strlen(tok)) == 0) {
1309 port1PinioDtr = 0;
1310 } else {
1311 port1PinioDtr = atoi(tok);
1312 if (port1PinioDtr < 0 || port1PinioDtr > PINIO_COUNT) {
1313 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1314 return ;
1316 #endif /* USE_PINIO */
1318 break;
1319 case 4:
1320 ports[1].id = atoi(tok);
1321 ports[1].port = NULL;
1322 break;
1323 case 5:
1324 ports[1].baud = atoi(tok);
1325 break;
1326 case 6:
1327 ports[1].mode = cliParseSerialMode(tok);
1328 break;
1330 index++;
1331 tok = strtok_r(NULL, " ", &saveptr);
1334 // Port checks
1335 if (ports[0].id == ports[1].id) {
1336 cliPrintLinef("Port1 and port2 are same");
1337 return ;
1340 for (int i = 0; i < 2; i++) {
1341 if (findSerialPortIndexByIdentifier(ports[i].id) == -1) {
1342 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1343 return ;
1344 } else {
1345 cliPrintLinef("Port%d: %d ", i + 1, ports[i].id);
1349 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1350 enableBaudCb = true;
1353 for (int i = 0; i < 2; i++) {
1354 serialPort_t **port = &(ports[i].port);
1355 if (*port != NULL) {
1356 continue;
1359 int portIndex = i + 1;
1360 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(ports[i].id);
1361 if (!portUsage || portUsage->serialPort == NULL) {
1362 bool isUseDefaultBaud = false;
1363 if (ports[i].baud == 0) {
1364 // Set default baud
1365 ports[i].baud = 57600;
1366 isUseDefaultBaud = true;
1369 if (!ports[i].mode) {
1370 ports[i].mode = MODE_RXTX;
1373 *port = openSerialPort(ports[i].id, FUNCTION_NONE, NULL, NULL,
1374 ports[i].baud, ports[i].mode,
1375 SERIAL_NOT_INVERTED);
1376 if (!*port) {
1377 cliPrintLinef("Port%d could not be opened.", portIndex);
1378 return;
1381 if (isUseDefaultBaud) {
1382 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex, ports[i].baud);
1383 } else {
1384 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex, ports[i].baud);
1386 } else {
1387 *port = portUsage->serialPort;
1388 // If the user supplied a mode, override the port's mode, otherwise
1389 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1390 // Set the baud rate if specified
1391 if (ports[i].baud) {
1392 cliPrintf("Port%d is already open, setting baud = %d.\n\r", portIndex, ports[i].baud);
1393 serialSetBaudRate(*port, ports[i].baud);
1394 } else {
1395 cliPrintf("Port%d is already open, baud = %d.\n\r", portIndex, (*port)->baudRate);
1398 if (ports[i].mode && (*port)->mode != ports[i].mode) {
1399 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1400 portIndex, (*port)->mode, ports[i].mode);
1401 serialSetMode(*port, ports[i].mode);
1404 // If this port has a rx callback associated we need to remove it now.
1405 // Otherwise no data will be pushed in the serial port buffer!
1406 if ((*port)->rxCallback) {
1407 (*port)->rxCallback = NULL;
1412 // If no baud rate is specified allow to be set via USB
1413 if (enableBaudCb) {
1414 cliPrintLine("Port1 baud rate change over USB enabled.");
1415 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1416 // baud rate over USB without setting it using the serialpassthrough command
1417 serialSetBaudRateCb(ports[0].port, serialSetBaudRate, ports[1].port);
1420 char *resetMessage = "";
1421 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1422 resetMessage = "or drop DTR ";
1425 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1427 if ((ports[1].id == SERIAL_PORT_USB_VCP) && (port1ResetOnDtr
1428 #ifdef USE_PINIO
1429 || port1PinioDtr
1430 #endif /* USE_PINIO */
1431 )) {
1432 // Register control line state callback
1433 serialSetCtrlLineStateCb(ports[0].port, cbCtrlLine, (void *)(intptr_t)(port1PinioDtr));
1436 // XXX Review ESC pass through under refactored motor handling
1437 #ifdef USE_PWM_OUTPUT
1438 if (escSensorPassthrough) {
1439 // pwmDisableMotors();
1440 motorDisable();
1441 delay(5);
1442 unsigned motorsCount = getMotorCount();
1443 for (unsigned i = 0; i < motorsCount; i++) {
1444 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1445 if (tag) {
1446 const timerHardware_t *timerHardware = timerGetByTag(tag);
1447 if (timerHardware) {
1448 IO_t io = IOGetByTag(tag);
1449 IOInit(io, OWNER_MOTOR, 0);
1450 IOConfigGPIO(io, IOCFG_OUT_PP);
1451 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1452 IOLo(io);
1453 } else {
1454 IOHi(io);
1460 #endif
1462 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1464 #endif
1466 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1468 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1469 // print out adjustment ranges channel settings
1470 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1471 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1472 const adjustmentRange_t *ar = &adjustmentRanges[i];
1473 bool equalsDefault = false;
1474 if (defaultAdjustmentRanges) {
1475 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1476 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1477 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1478 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1480 arDefault->auxChannelIndex,
1481 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1482 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1483 arDefault->adjustmentConfig,
1484 arDefault->auxSwitchChannelIndex,
1485 arDefault->adjustmentCenter,
1486 arDefault->adjustmentScale
1489 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1491 ar->auxChannelIndex,
1492 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1493 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1494 ar->adjustmentConfig,
1495 ar->auxSwitchChannelIndex,
1496 ar->adjustmentCenter,
1497 ar->adjustmentScale
1502 static void cliAdjustmentRange(char *cmdline)
1504 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1505 int i, val = 0;
1506 const char *ptr;
1508 if (isEmpty(cmdline)) {
1509 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1510 } else {
1511 ptr = cmdline;
1512 i = atoi(ptr++);
1513 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1514 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1515 uint8_t validArgumentCount = 0;
1517 ptr = nextArg(ptr);
1518 if (ptr) {
1519 val = atoi(ptr);
1520 // Was: slot
1521 // Keeping the parameter to retain backwards compatibility for the command format.
1522 validArgumentCount++;
1524 ptr = nextArg(ptr);
1525 if (ptr) {
1526 val = atoi(ptr);
1527 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1528 ar->auxChannelIndex = val;
1529 validArgumentCount++;
1533 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1535 ptr = nextArg(ptr);
1536 if (ptr) {
1537 val = atoi(ptr);
1538 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1539 ar->adjustmentConfig = val;
1540 validArgumentCount++;
1543 ptr = nextArg(ptr);
1544 if (ptr) {
1545 val = atoi(ptr);
1546 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1547 ar->auxSwitchChannelIndex = val;
1548 validArgumentCount++;
1552 if (validArgumentCount != 6) {
1553 memset(ar, 0, sizeof(adjustmentRange_t));
1554 cliShowParseError();
1555 return;
1558 // Optional arguments
1559 ar->adjustmentCenter = 0;
1560 ar->adjustmentScale = 0;
1562 ptr = nextArg(ptr);
1563 if (ptr) {
1564 val = atoi(ptr);
1565 ar->adjustmentCenter = val;
1566 validArgumentCount++;
1568 ptr = nextArg(ptr);
1569 if (ptr) {
1570 val = atoi(ptr);
1571 ar->adjustmentScale = val;
1572 validArgumentCount++;
1575 activeAdjustmentRangeReset();
1577 cliDumpPrintLinef(0, false, format,
1579 ar->auxChannelIndex,
1580 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1581 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1582 ar->adjustmentConfig,
1583 ar->auxSwitchChannelIndex,
1584 ar->adjustmentCenter,
1585 ar->adjustmentScale
1588 } else {
1589 cliShowArgumentRangeError("INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1594 #ifndef USE_QUAD_MIXER_ONLY
1595 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1597 const char *format = "mmix %d %s %s %s %s";
1598 char buf0[FTOA_BUFFER_LENGTH];
1599 char buf1[FTOA_BUFFER_LENGTH];
1600 char buf2[FTOA_BUFFER_LENGTH];
1601 char buf3[FTOA_BUFFER_LENGTH];
1602 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1603 if (customMotorMixer[i].throttle == 0.0f)
1604 break;
1605 const float thr = customMotorMixer[i].throttle;
1606 const float roll = customMotorMixer[i].roll;
1607 const float pitch = customMotorMixer[i].pitch;
1608 const float yaw = customMotorMixer[i].yaw;
1609 bool equalsDefault = false;
1610 if (defaultCustomMotorMixer) {
1611 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1612 const float rollDefault = defaultCustomMotorMixer[i].roll;
1613 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1614 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1615 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1617 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1618 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1620 ftoa(thrDefault, buf0),
1621 ftoa(rollDefault, buf1),
1622 ftoa(pitchDefault, buf2),
1623 ftoa(yawDefault, buf3));
1625 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1627 ftoa(thr, buf0),
1628 ftoa(roll, buf1),
1629 ftoa(pitch, buf2),
1630 ftoa(yaw, buf3));
1633 #endif // USE_QUAD_MIXER_ONLY
1635 static void cliMotorMix(char *cmdline)
1637 #ifdef USE_QUAD_MIXER_ONLY
1638 UNUSED(cmdline);
1639 #else
1640 int check = 0;
1641 uint8_t len;
1642 const char *ptr;
1644 if (isEmpty(cmdline)) {
1645 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1646 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1647 // erase custom mixer
1648 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1649 customMotorMixerMutable(i)->throttle = 0.0f;
1651 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1652 ptr = nextArg(cmdline);
1653 if (ptr) {
1654 len = strlen(ptr);
1655 for (uint32_t i = 0; ; i++) {
1656 if (mixerNames[i] == NULL) {
1657 cliPrintErrorLinef("INVALID NAME");
1658 break;
1660 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1661 mixerLoadMix(i, customMotorMixerMutable(0));
1662 cliPrintLinef("Loaded %s", mixerNames[i]);
1663 cliMotorMix("");
1664 break;
1668 } else {
1669 ptr = cmdline;
1670 uint32_t i = atoi(ptr); // get motor number
1671 if (i < MAX_SUPPORTED_MOTORS) {
1672 ptr = nextArg(ptr);
1673 if (ptr) {
1674 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1675 check++;
1677 ptr = nextArg(ptr);
1678 if (ptr) {
1679 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1680 check++;
1682 ptr = nextArg(ptr);
1683 if (ptr) {
1684 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1685 check++;
1687 ptr = nextArg(ptr);
1688 if (ptr) {
1689 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1690 check++;
1692 if (check != 4) {
1693 cliShowParseError();
1694 } else {
1695 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1697 } else {
1698 cliShowArgumentRangeError("INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1701 #endif
1704 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1706 const char *format = "rxrange %u %u %u";
1707 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1708 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1709 bool equalsDefault = false;
1710 if (defaultChannelRangeConfigs) {
1711 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1712 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1713 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1715 defaultChannelRangeConfigs[i].min,
1716 defaultChannelRangeConfigs[i].max
1719 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1721 channelRangeConfigs[i].min,
1722 channelRangeConfigs[i].max
1727 static void cliRxRange(char *cmdline)
1729 const char *format = "rxrange %u %u %u";
1730 int i, validArgumentCount = 0;
1731 const char *ptr;
1733 if (isEmpty(cmdline)) {
1734 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1735 } else if (strcasecmp(cmdline, "reset") == 0) {
1736 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1737 } else {
1738 ptr = cmdline;
1739 i = atoi(ptr);
1740 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1741 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1743 ptr = nextArg(ptr);
1744 if (ptr) {
1745 rangeMin = atoi(ptr);
1746 validArgumentCount++;
1749 ptr = nextArg(ptr);
1750 if (ptr) {
1751 rangeMax = atoi(ptr);
1752 validArgumentCount++;
1755 if (validArgumentCount != 2) {
1756 cliShowParseError();
1757 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1758 cliShowParseError();
1759 } else {
1760 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1761 channelRangeConfig->min = rangeMin;
1762 channelRangeConfig->max = rangeMax;
1763 cliDumpPrintLinef(0, false, format,
1765 channelRangeConfig->min,
1766 channelRangeConfig->max
1770 } else {
1771 cliShowArgumentRangeError("CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1776 #ifdef USE_LED_STRIP_STATUS_MODE
1777 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1779 const char *format = "led %u %s";
1780 char ledConfigBuffer[20];
1781 char ledConfigDefaultBuffer[20];
1782 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1783 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1784 ledConfig_t ledConfig = ledConfigs[i];
1785 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1786 bool equalsDefault = false;
1787 if (defaultLedConfigs) {
1788 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1789 equalsDefault = ledConfig == ledConfigDefault;
1790 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1791 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1792 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1794 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1798 static void cliLed(char *cmdline)
1800 const char *format = "led %u %s";
1801 char ledConfigBuffer[20];
1802 int i;
1803 const char *ptr;
1805 if (isEmpty(cmdline)) {
1806 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
1807 } else {
1808 ptr = cmdline;
1809 i = atoi(ptr);
1810 if (i < LED_MAX_STRIP_LENGTH) {
1811 ptr = nextArg(cmdline);
1812 if (parseLedStripConfig(i, ptr)) {
1813 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1814 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1815 } else {
1816 cliShowParseError();
1818 } else {
1819 cliShowArgumentRangeError("INDEX", 0, LED_MAX_STRIP_LENGTH - 1);
1824 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
1826 const char *format = "color %u %d,%u,%u";
1827 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1828 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1829 const hsvColor_t *color = &colors[i];
1830 bool equalsDefault = false;
1831 if (defaultColors) {
1832 const hsvColor_t *colorDefault = &defaultColors[i];
1833 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1834 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1835 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1837 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1841 static void cliColor(char *cmdline)
1843 const char *format = "color %u %d,%u,%u";
1844 if (isEmpty(cmdline)) {
1845 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
1846 } else {
1847 const char *ptr = cmdline;
1848 const int i = atoi(ptr);
1849 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1850 ptr = nextArg(cmdline);
1851 if (parseColor(i, ptr)) {
1852 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
1853 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1854 } else {
1855 cliShowParseError();
1857 } else {
1858 cliShowArgumentRangeError("INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1863 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
1865 const char *format = "mode_color %u %u %u";
1866 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1867 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1868 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1869 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
1870 bool equalsDefault = false;
1871 if (defaultLedStripConfig) {
1872 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1873 equalsDefault = colorIndex == colorIndexDefault;
1874 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1875 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1877 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1881 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1882 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
1883 bool equalsDefault = false;
1884 if (defaultLedStripConfig) {
1885 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1886 equalsDefault = colorIndex == colorIndexDefault;
1887 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1888 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1890 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1893 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
1894 bool equalsDefault = false;
1895 if (defaultLedStripConfig) {
1896 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1897 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1898 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1899 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1901 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1904 static void cliModeColor(char *cmdline)
1906 if (isEmpty(cmdline)) {
1907 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
1908 } else {
1909 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1910 int args[ARGS_COUNT];
1911 int argNo = 0;
1912 char *saveptr;
1913 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1914 while (ptr && argNo < ARGS_COUNT) {
1915 args[argNo++] = atoi(ptr);
1916 ptr = strtok_r(NULL, " ", &saveptr);
1919 if (ptr != NULL || argNo != ARGS_COUNT) {
1920 cliShowParseError();
1921 return;
1924 int modeIdx = args[MODE];
1925 int funIdx = args[FUNCTION];
1926 int color = args[COLOR];
1927 if (!setModeColor(modeIdx, funIdx, color)) {
1928 cliShowParseError();
1929 return;
1931 // values are validated
1932 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1935 #endif
1937 #ifdef USE_SERVOS
1938 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
1940 // print out servo settings
1941 const char *format = "servo %u %d %d %d %d %d";
1942 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1943 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1944 const servoParam_t *servoConf = &servoParams[i];
1945 bool equalsDefault = false;
1946 if (defaultServoParams) {
1947 const servoParam_t *defaultServoConf = &defaultServoParams[i];
1948 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
1949 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1950 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1952 defaultServoConf->min,
1953 defaultServoConf->max,
1954 defaultServoConf->middle,
1955 defaultServoConf->rate,
1956 defaultServoConf->forwardFromChannel
1959 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1961 servoConf->min,
1962 servoConf->max,
1963 servoConf->middle,
1964 servoConf->rate,
1965 servoConf->forwardFromChannel
1968 // print servo directions
1969 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1970 const char *format = "smix reverse %d %d r";
1971 const servoParam_t *servoConf = &servoParams[i];
1972 const servoParam_t *servoConfDefault = &defaultServoParams[i];
1973 if (defaultServoParams) {
1974 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
1975 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1976 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
1977 if (servoConfDefault->reversedSources & (1 << channel)) {
1978 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
1980 if (servoConf->reversedSources & (1 << channel)) {
1981 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
1984 } else {
1985 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1986 if (servoConf->reversedSources & (1 << channel)) {
1987 cliDumpPrintLinef(dumpMask, true, format, i , channel);
1994 static void cliServo(char *cmdline)
1996 const char *format = "servo %u %d %d %d %d %d";
1997 enum { SERVO_ARGUMENT_COUNT = 6 };
1998 int16_t arguments[SERVO_ARGUMENT_COUNT];
2000 servoParam_t *servo;
2002 int i;
2003 char *ptr;
2005 if (isEmpty(cmdline)) {
2006 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2007 } else {
2008 int validArgumentCount = 0;
2010 ptr = cmdline;
2012 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2014 // If command line doesn't fit the format, don't modify the config
2015 while (*ptr) {
2016 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2017 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2018 cliShowParseError();
2019 return;
2022 arguments[validArgumentCount++] = atoi(ptr);
2024 do {
2025 ptr++;
2026 } while (*ptr >= '0' && *ptr <= '9');
2027 } else if (*ptr == ' ') {
2028 ptr++;
2029 } else {
2030 cliShowParseError();
2031 return;
2035 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2037 i = arguments[INDEX];
2039 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2040 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2041 cliShowParseError();
2042 return;
2045 servo = servoParamsMutable(i);
2047 if (
2048 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
2049 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
2050 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2051 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
2052 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2053 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2055 cliShowParseError();
2056 return;
2059 servo->min = arguments[MIN];
2060 servo->max = arguments[MAX];
2061 servo->middle = arguments[MIDDLE];
2062 servo->rate = arguments[RATE];
2063 servo->forwardFromChannel = arguments[FORWARD];
2065 cliDumpPrintLinef(0, false, format,
2067 servo->min,
2068 servo->max,
2069 servo->middle,
2070 servo->rate,
2071 servo->forwardFromChannel
2076 #endif
2078 #ifdef USE_SERVOS
2079 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2081 const char *format = "smix %d %d %d %d %d %d %d %d";
2082 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2083 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2084 const servoMixer_t customServoMixer = customServoMixers[i];
2085 if (customServoMixer.rate == 0) {
2086 break;
2089 bool equalsDefault = false;
2090 if (defaultCustomServoMixers) {
2091 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2092 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2094 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2095 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2097 customServoMixerDefault.targetChannel,
2098 customServoMixerDefault.inputSource,
2099 customServoMixerDefault.rate,
2100 customServoMixerDefault.speed,
2101 customServoMixerDefault.min,
2102 customServoMixerDefault.max,
2103 customServoMixerDefault.box
2106 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2108 customServoMixer.targetChannel,
2109 customServoMixer.inputSource,
2110 customServoMixer.rate,
2111 customServoMixer.speed,
2112 customServoMixer.min,
2113 customServoMixer.max,
2114 customServoMixer.box
2119 static void cliServoMix(char *cmdline)
2121 int args[8], check = 0;
2122 int len = strlen(cmdline);
2124 if (len == 0) {
2125 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2126 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2127 // erase custom mixer
2128 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2129 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2130 servoParamsMutable(i)->reversedSources = 0;
2132 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2133 const char *ptr = nextArg(cmdline);
2134 if (ptr) {
2135 len = strlen(ptr);
2136 for (uint32_t i = 0; ; i++) {
2137 if (mixerNames[i] == NULL) {
2138 cliPrintErrorLinef("INVALID NAME");
2139 break;
2141 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2142 servoMixerLoadMix(i);
2143 cliPrintLinef("Loaded %s", mixerNames[i]);
2144 cliServoMix("");
2145 break;
2149 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2150 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2151 char *ptr = strchr(cmdline, ' ');
2153 if (ptr == NULL) {
2154 cliPrintf("s");
2155 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2156 cliPrintf("\ti%d", inputSource);
2157 cliPrintLinefeed();
2159 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2160 cliPrintf("%d", servoIndex);
2161 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2162 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2164 cliPrintLinefeed();
2166 return;
2169 char *saveptr;
2170 ptr = strtok_r(ptr, " ", &saveptr);
2171 while (ptr != NULL && check < ARGS_COUNT - 1) {
2172 args[check++] = atoi(ptr);
2173 ptr = strtok_r(NULL, " ", &saveptr);
2176 if (ptr == NULL || check != ARGS_COUNT - 1) {
2177 cliShowParseError();
2178 return;
2181 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2182 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2183 && (*ptr == 'r' || *ptr == 'n')) {
2184 if (*ptr == 'r') {
2185 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2186 } else {
2187 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2189 } else {
2190 cliShowParseError();
2191 return;
2194 cliServoMix("reverse");
2195 } else {
2196 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2197 char *saveptr;
2198 char *ptr = strtok_r(cmdline, " ", &saveptr);
2199 while (ptr != NULL && check < ARGS_COUNT) {
2200 args[check++] = atoi(ptr);
2201 ptr = strtok_r(NULL, " ", &saveptr);
2204 if (ptr != NULL || check != ARGS_COUNT) {
2205 cliShowParseError();
2206 return;
2209 int32_t i = args[RULE];
2210 if (i >= 0 && i < MAX_SERVO_RULES &&
2211 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2212 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2213 args[RATE] >= -100 && args[RATE] <= 100 &&
2214 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2215 args[MIN] >= 0 && args[MIN] <= 100 &&
2216 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2217 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2218 customServoMixersMutable(i)->targetChannel = args[TARGET];
2219 customServoMixersMutable(i)->inputSource = args[INPUT];
2220 customServoMixersMutable(i)->rate = args[RATE];
2221 customServoMixersMutable(i)->speed = args[SPEED];
2222 customServoMixersMutable(i)->min = args[MIN];
2223 customServoMixersMutable(i)->max = args[MAX];
2224 customServoMixersMutable(i)->box = args[BOX];
2225 cliServoMix("");
2226 } else {
2227 cliShowParseError();
2231 #endif
2233 #ifdef USE_SDCARD
2235 static void cliWriteBytes(const uint8_t *buffer, int count)
2237 while (count > 0) {
2238 cliWrite(*buffer);
2239 buffer++;
2240 count--;
2244 static void cliSdInfo(char *cmdline)
2246 UNUSED(cmdline);
2248 cliPrint("SD card: ");
2250 if (!sdcard_isInserted()) {
2251 cliPrintLine("None inserted");
2252 return;
2255 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2256 cliPrintLine("Startup failed");
2257 return;
2260 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2262 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2263 metadata->manufacturerID,
2264 metadata->numBlocks / 2, /* One block is half a kB */
2265 metadata->productionMonth,
2266 metadata->productionYear,
2267 metadata->productRevisionMajor,
2268 metadata->productRevisionMinor
2271 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2273 cliPrint("'\r\n" "Filesystem: ");
2275 switch (afatfs_getFilesystemState()) {
2276 case AFATFS_FILESYSTEM_STATE_READY:
2277 cliPrint("Ready");
2278 break;
2279 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2280 cliPrint("Initializing");
2281 break;
2282 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2283 case AFATFS_FILESYSTEM_STATE_FATAL:
2284 cliPrint("Fatal");
2286 switch (afatfs_getLastError()) {
2287 case AFATFS_ERROR_BAD_MBR:
2288 cliPrint(" - no FAT MBR partitions");
2289 break;
2290 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2291 cliPrint(" - bad FAT header");
2292 break;
2293 case AFATFS_ERROR_GENERIC:
2294 case AFATFS_ERROR_NONE:
2295 ; // Nothing more detailed to print
2296 break;
2298 break;
2300 cliPrintLinefeed();
2303 #endif
2305 #ifdef USE_FLASH_CHIP
2307 static void cliFlashInfo(char *cmdline)
2309 const flashGeometry_t *layout = flashGetGeometry();
2311 UNUSED(cmdline);
2313 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2314 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2316 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2317 const flashPartition_t *partition;
2318 if (index == 0) {
2319 cliPrintLine("Paritions:");
2321 partition = flashPartitionFindByIndex(index);
2322 if (!partition) {
2323 break;
2325 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2327 #ifdef USE_FLASHFS
2328 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2330 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2331 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2332 flashfsGetOffset()
2334 #endif
2338 static void cliFlashErase(char *cmdline)
2340 UNUSED(cmdline);
2342 if (!flashfsIsSupported()) {
2343 return;
2346 #ifndef MINIMAL_CLI
2347 uint32_t i = 0;
2348 cliPrintLine("Erasing, please wait ... ");
2349 #else
2350 cliPrintLine("Erasing,");
2351 #endif
2353 bufWriterFlush(cliWriter);
2354 flashfsEraseCompletely();
2356 while (!flashfsIsReady()) {
2357 #ifndef MINIMAL_CLI
2358 cliPrintf(".");
2359 if (i++ > 120) {
2360 i=0;
2361 cliPrintLinefeed();
2364 bufWriterFlush(cliWriter);
2365 #endif
2366 delay(100);
2368 beeper(BEEPER_BLACKBOX_ERASE);
2369 cliPrintLinefeed();
2370 cliPrintLine("Done.");
2373 #ifdef USE_FLASH_TOOLS
2375 static void cliFlashVerify(char *cmdline)
2377 UNUSED(cmdline);
2379 cliPrintLine("Verifying");
2380 if (flashfsVerifyEntireFlash()) {
2381 cliPrintLine("Success");
2382 } else {
2383 cliPrintLine("Failed");
2387 static void cliFlashWrite(char *cmdline)
2389 const uint32_t address = atoi(cmdline);
2390 const char *text = strchr(cmdline, ' ');
2392 if (!text) {
2393 cliShowParseError();
2394 } else {
2395 flashfsSeekAbs(address);
2396 flashfsWrite((uint8_t*)text, strlen(text), true);
2397 flashfsFlushSync();
2399 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2403 static void cliFlashRead(char *cmdline)
2405 uint32_t address = atoi(cmdline);
2407 const char *nextArg = strchr(cmdline, ' ');
2409 if (!nextArg) {
2410 cliShowParseError();
2411 } else {
2412 uint32_t length = atoi(nextArg);
2414 cliPrintLinef("Reading %u bytes at %u:", length, address);
2416 uint8_t buffer[32];
2417 while (length > 0) {
2418 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2420 for (int i = 0; i < bytesRead; i++) {
2421 cliWrite(buffer[i]);
2424 length -= bytesRead;
2425 address += bytesRead;
2427 if (bytesRead == 0) {
2428 //Assume we reached the end of the volume or something fatal happened
2429 break;
2432 cliPrintLinefeed();
2436 #endif
2437 #endif
2439 #ifdef USE_VTX_CONTROL
2440 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2442 // print out vtx channel settings
2443 const char *format = "vtx %u %u %u %u %u %u %u";
2444 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2445 bool equalsDefault = false;
2446 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2447 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2448 if (vtxConfigDefault) {
2449 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2450 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2451 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2452 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2454 cacDefault->auxChannelIndex,
2455 cacDefault->band,
2456 cacDefault->channel,
2457 cacDefault->power,
2458 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2459 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2462 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2464 cac->auxChannelIndex,
2465 cac->band,
2466 cac->channel,
2467 cac->power,
2468 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2469 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2474 static void cliVtx(char *cmdline)
2476 const char *format = "vtx %u %u %u %u %u %u %u";
2477 int i, val = 0;
2478 const char *ptr;
2480 if (isEmpty(cmdline)) {
2481 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2482 } else {
2483 ptr = cmdline;
2484 i = atoi(ptr++);
2485 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2486 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2487 uint8_t validArgumentCount = 0;
2488 ptr = nextArg(ptr);
2489 if (ptr) {
2490 val = atoi(ptr);
2491 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2492 cac->auxChannelIndex = val;
2493 validArgumentCount++;
2496 ptr = nextArg(ptr);
2497 if (ptr) {
2498 val = atoi(ptr);
2499 if (val >= 0 && val <= vtxTableBandCount) {
2500 cac->band = val;
2501 validArgumentCount++;
2504 ptr = nextArg(ptr);
2505 if (ptr) {
2506 val = atoi(ptr);
2507 if (val >= 0 && val <= vtxTableChannelCount) {
2508 cac->channel = val;
2509 validArgumentCount++;
2512 ptr = nextArg(ptr);
2513 if (ptr) {
2514 val = atoi(ptr);
2515 if (val >= 0 && val < vtxTablePowerLevels) {
2516 cac->power= val;
2517 validArgumentCount++;
2520 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2522 if (validArgumentCount != 6) {
2523 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2524 cliShowParseError();
2525 } else {
2526 cliDumpPrintLinef(0, false, format,
2528 cac->auxChannelIndex,
2529 cac->band,
2530 cac->channel,
2531 cac->power,
2532 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2533 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2536 } else {
2537 cliShowArgumentRangeError("INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2542 #endif // VTX_CONTROL
2544 #ifdef USE_VTX_TABLE
2546 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2548 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2549 char freqtmp[5 + 1];
2550 freqbuf[0] = 0;
2551 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2552 for (int channel = 0; channel < channels; channel++) {
2553 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2554 strcat(freqbuf, freqtmp);
2556 return freqbuf;
2559 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2561 char *fmt = "vtxtable band %d %s %c%s";
2562 bool equalsDefault = false;
2564 if (defaultConfig) {
2565 equalsDefault = true;
2566 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2567 equalsDefault = false;
2569 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2570 equalsDefault = false;
2572 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2573 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2574 equalsDefault = false;
2577 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2578 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2579 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2582 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2583 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2584 return headingStr;
2587 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2589 // (max 4 digit + 1 space) per level
2590 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2591 char pwrtmp[5 + 1];
2592 pwrbuf[0] = 0;
2593 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2594 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2595 strcat(pwrbuf, pwrtmp);
2597 return pwrbuf;
2600 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2602 char *fmt = "vtxtable powervalues%s";
2603 bool equalsDefault = false;
2604 if (defaultConfig) {
2605 equalsDefault = true;
2606 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2607 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2608 equalsDefault = false;
2611 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2612 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2613 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2616 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2617 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2618 return headingStr;
2621 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2623 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2624 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2625 pwrbuf[0] = 0;
2626 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2627 strcat(pwrbuf, " ");
2628 strcpy(pwrtmp, labels[pwrindex]);
2629 // trim trailing space
2630 char *sp;
2631 while ((sp = strchr(pwrtmp, ' '))) {
2632 *sp = 0;
2634 strcat(pwrbuf, pwrtmp);
2636 return pwrbuf;
2639 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2641 char *fmt = "vtxtable powerlabels%s";
2642 bool equalsDefault = false;
2643 if (defaultConfig) {
2644 equalsDefault = true;
2645 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2646 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2647 equalsDefault = false;
2650 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2651 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2652 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2655 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2656 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2657 return headingStr;
2660 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2662 bool equalsDefault;
2663 char *fmt;
2665 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2667 // bands
2668 equalsDefault = false;
2669 fmt = "vtxtable bands %d";
2670 if (defaultConfig) {
2671 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2672 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2673 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2675 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2677 // channels
2678 equalsDefault = false;
2679 fmt = "vtxtable channels %d";
2680 if (defaultConfig) {
2681 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2682 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2683 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2685 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2687 // band
2689 for (int band = 0; band < currentConfig->bands; band++) {
2690 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2693 // powerlevels
2695 equalsDefault = false;
2696 fmt = "vtxtable powerlevels %d";
2697 if (defaultConfig) {
2698 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2699 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2700 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2702 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2704 // powervalues
2706 // powerlabels
2707 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2708 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2711 static void cliVtxTable(char *cmdline)
2713 char *tok;
2714 char *saveptr;
2716 // Band number or nothing
2717 tok = strtok_r(cmdline, " ", &saveptr);
2719 if (!tok) {
2720 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2721 return;
2724 if (strcasecmp(tok, "bands") == 0) {
2725 tok = strtok_r(NULL, " ", &saveptr);
2726 int bands = atoi(tok);
2727 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2728 cliPrintErrorLinef("INVALID BAND COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_BANDS);
2729 return;
2731 if (bands < vtxTableConfigMutable()->bands) {
2732 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2733 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2736 vtxTableConfigMutable()->bands = bands;
2738 } else if (strcasecmp(tok, "channels") == 0) {
2739 tok = strtok_r(NULL, " ", &saveptr);
2741 int channels = atoi(tok);
2742 if (channels < 0 || channels > VTX_TABLE_MAX_BANDS) {
2743 cliPrintErrorLinef("INVALID CHANNEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_CHANNELS);
2744 return;
2746 if (channels < vtxTableConfigMutable()->channels) {
2747 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2748 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2751 vtxTableConfigMutable()->channels = channels;
2753 } else if (strcasecmp(tok, "powerlevels") == 0) {
2754 // Number of power levels
2755 tok = strtok_r(NULL, " ", &saveptr);
2756 if (tok) {
2757 int levels = atoi(tok);
2758 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2759 cliPrintErrorLinef("INVALID POWER LEVEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_POWER_LEVELS);
2760 } else {
2761 if (levels < vtxTableConfigMutable()->powerLevels) {
2762 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2763 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2765 vtxTableConfigMutable()->powerLevels = levels;
2767 } else {
2768 // XXX Show current level count?
2770 return;
2772 } else if (strcasecmp(tok, "powervalues") == 0) {
2773 // Power values
2774 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2775 int count;
2776 int levels = vtxTableConfigMutable()->powerLevels;
2778 memset(power, 0, sizeof(power));
2780 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2781 int value = atoi(tok);
2782 power[count] = value;
2785 // Check remaining tokens
2787 if (count < levels) {
2788 cliPrintErrorLinef("NOT ENOUGH VALUES (EXPECTED %d)", levels);
2789 return;
2790 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2791 cliPrintErrorLinef("TOO MANY VALUES (EXPECTED %d)", levels);
2792 return;
2795 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
2796 vtxTableConfigMutable()->powerValues[i] = power[i];
2799 } else if (strcasecmp(tok, "powerlabels") == 0) {
2800 // Power labels
2801 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
2802 int levels = vtxTableConfigMutable()->powerLevels;
2803 int count;
2804 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2805 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
2806 for (unsigned i = 0; i < strlen(label[count]); i++) {
2807 label[count][i] = toupper(label[count][i]);
2811 // Check remaining tokens
2813 if (count < levels) {
2814 cliPrintErrorLinef("NOT ENOUGH LABELS (EXPECTED %d)", levels);
2815 return;
2816 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2817 cliPrintErrorLinef("TOO MANY LABELS (EXPECTED %d)", levels);
2818 return;
2821 for (int i = 0; i < count; i++) {
2822 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
2824 } else if (strcasecmp(tok, "band") == 0) {
2826 int bands = vtxTableConfigMutable()->bands;
2828 tok = strtok_r(NULL, " ", &saveptr);
2829 if (!tok) {
2830 return;
2833 int band = atoi(tok);
2834 --band;
2836 if (band < 0 || band >= bands) {
2837 cliPrintErrorLinef("INVALID BAND NUMBER %s (EXPECTED 1-%d)", tok, bands);
2838 return;
2841 // Band name
2842 tok = strtok_r(NULL, " ", &saveptr);
2844 if (!tok) {
2845 return;
2848 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
2849 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
2850 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
2851 for (unsigned i = 0; i < strlen(bandname); i++) {
2852 bandname[i] = toupper(bandname[i]);
2855 // Band letter
2856 tok = strtok_r(NULL, " ", &saveptr);
2858 if (!tok) {
2859 return;
2862 char bandletter = toupper(tok[0]);
2864 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
2865 int channel = 0;
2866 int channels = vtxTableConfigMutable()->channels;
2867 bool isFactory = false;
2869 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
2870 if (channel == 0 && !isdigit(tok[0])) {
2871 channel -= 1;
2872 if (strcasecmp(tok, "FACTORY") == 0) {
2873 isFactory = true;
2874 } else if (strcasecmp(tok, "CUSTOM") == 0) {
2875 isFactory = false;
2876 } else {
2877 cliPrintErrorLinef("INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
2878 return;
2881 int freq = atoi(tok);
2882 if (freq < 0) {
2883 cliPrintErrorLinef("INVALID FREQUENCY %s", tok);
2884 return;
2886 bandfreq[channel] = freq;
2889 if (channel < channels) {
2890 cliPrintErrorLinef("NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
2891 return;
2892 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2893 cliPrintErrorLinef("TOO MANY FREQUENCIES (EXPECTED %d)", channels);
2894 return;
2897 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
2898 vtxTableConfigMutable()->bandLetters[band] = bandletter;
2900 for (int i = 0; i < channel; i++) {
2901 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
2903 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
2904 } else {
2905 // Bad subcommand
2906 cliPrintErrorLinef("INVALID SUBCOMMAND %s", tok);
2909 #endif // USE_VTX_TABLE
2911 static void printName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
2913 const bool equalsDefault = strlen(pilotConfig->name) == 0;
2914 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->name);
2917 static void cliName(char *cmdline)
2919 const unsigned int len = strlen(cmdline);
2920 bool updated = false;
2921 if (len > 0) {
2922 memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
2923 if (strncmp(cmdline, emptyName, len)) {
2924 strncpy(pilotConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
2926 updated = true;
2928 printName(DUMP_MASTER, pilotConfig());
2929 if (updated) {
2930 cliPrintLine("###WARNING: This command will be removed. Use 'set name = ' instead.###");
2934 #if defined(USE_BOARD_INFO)
2936 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
2938 static void printBoardName(dumpFlags_t dumpMask)
2940 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
2941 cliPrintLinef("board_name %s", getBoardName());
2945 static void cliBoardName(char *cmdline)
2947 const unsigned int len = strlen(cmdline);
2948 if (len > 0 && boardInformationIsSet() && (len != strlen(getBoardName()) || strncmp(getBoardName(), cmdline, len))) {
2949 cliPrintErrorLinef(ERROR_MESSAGE, "BOARD_NAME", getBoardName());
2950 } else {
2951 if (len > 0) {
2952 setBoardName(cmdline);
2953 boardInformationUpdated = true;
2955 printBoardName(DUMP_ALL);
2959 static void printManufacturerId(dumpFlags_t dumpMask)
2961 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
2962 cliPrintLinef("manufacturer_id %s", getManufacturerId());
2966 static void cliManufacturerId(char *cmdline)
2968 const unsigned int len = strlen(cmdline);
2969 if (len > 0 && boardInformationIsSet() && (len != strlen(getManufacturerId()) || strncmp(getManufacturerId(), cmdline, len))) {
2970 cliPrintErrorLinef(ERROR_MESSAGE, "MANUFACTURER_ID", getManufacturerId());
2971 } else {
2972 if (len > 0) {
2973 setManufacturerId(cmdline);
2974 boardInformationUpdated = true;
2976 printManufacturerId(DUMP_ALL);
2980 #if defined(USE_SIGNATURE)
2981 static void writeSignature(char *signatureStr, uint8_t *signature)
2983 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
2984 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
2988 static void cliSignature(char *cmdline)
2990 const int len = strlen(cmdline);
2992 uint8_t signature[SIGNATURE_LENGTH] = {0};
2993 if (len > 0) {
2994 if (len != 2 * SIGNATURE_LENGTH) {
2995 cliPrintErrorLinef("INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
2997 return;
3000 #define BLOCK_SIZE 2
3001 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3002 char temp[BLOCK_SIZE + 1];
3003 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3004 temp[BLOCK_SIZE] = '\0';
3005 char *end;
3006 unsigned result = strtoul(temp, &end, 16);
3007 if (end == &temp[BLOCK_SIZE]) {
3008 signature[i] = result;
3009 } else {
3010 cliPrintErrorLinef("INVALID CHARACTER FOUND: %c", end[0]);
3012 return;
3015 #undef BLOCK_SIZE
3018 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3019 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3020 writeSignature(signatureStr, getSignature());
3021 cliPrintErrorLinef(ERROR_MESSAGE, "SIGNATURE", signatureStr);
3022 } else {
3023 if (len > 0) {
3024 setSignature(signature);
3026 signatureUpdated = true;
3028 writeSignature(signatureStr, getSignature());
3029 } else if (signatureUpdated || signatureIsSet()) {
3030 writeSignature(signatureStr, getSignature());
3033 cliPrintLinef("signature %s", signatureStr);
3036 #endif
3038 #undef ERROR_MESSAGE
3040 #endif // USE_BOARD_INFO
3042 static void cliMcuId(char *cmdline)
3044 UNUSED(cmdline);
3046 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3049 static uint32_t getFeatureMask(const uint32_t featureMask)
3051 if (featureMaskIsCopied) {
3052 return featureMaskCopy;
3053 } else {
3054 return featureMask;
3058 static void printFeature(dumpFlags_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault, const char *headingStr)
3060 const uint32_t mask = getFeatureMask(featureConfig->enabledFeatures);
3061 const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
3062 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3063 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
3064 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3065 const char *format = "feature -%s";
3066 const bool equalsDefault = (~defaultMask | mask) & (1 << i);
3067 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3068 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
3069 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3072 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
3073 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3074 const char *format = "feature %s";
3075 if (defaultMask & (1 << i)) {
3076 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
3078 if (mask & (1 << i)) {
3079 const bool equalsDefault = (defaultMask | ~mask) & (1 << i);
3080 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3081 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3087 static void cliFeature(char *cmdline)
3089 uint32_t len = strlen(cmdline);
3090 const uint32_t mask = getFeatureMask(featureMask());
3091 if (len == 0) {
3092 cliPrint("Enabled: ");
3093 for (uint32_t i = 0; ; i++) {
3094 if (featureNames[i] == NULL) {
3095 break;
3097 if (mask & (1 << i)) {
3098 cliPrintf("%s ", featureNames[i]);
3101 cliPrintLinefeed();
3102 } else if (strncasecmp(cmdline, "list", len) == 0) {
3103 cliPrint("Available:");
3104 for (uint32_t i = 0; ; i++) {
3105 if (featureNames[i] == NULL)
3106 break;
3107 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
3108 cliPrintf(" %s", featureNames[i]);
3110 cliPrintLinefeed();
3111 return;
3112 } else {
3113 if (!featureMaskIsCopied) {
3114 featureMaskCopy = featureMask();
3115 featureMaskIsCopied = true;
3117 uint32_t feature;
3119 bool remove = false;
3120 if (cmdline[0] == '-') {
3121 // remove feature
3122 remove = true;
3123 cmdline++; // skip over -
3124 len--;
3127 for (uint32_t i = 0; ; i++) {
3128 if (featureNames[i] == NULL) {
3129 cliPrintErrorLinef("INVALID NAME");
3130 break;
3133 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
3134 feature = 1 << i;
3135 #ifndef USE_GPS
3136 if (feature & FEATURE_GPS) {
3137 cliPrintLine("unavailable");
3138 break;
3140 #endif
3141 #ifndef USE_RANGEFINDER
3142 if (feature & FEATURE_RANGEFINDER) {
3143 cliPrintLine("unavailable");
3144 break;
3146 #endif
3147 if (remove) {
3148 featureClear(feature, &featureMaskCopy);
3149 cliPrint("Disabled");
3150 } else {
3151 featureSet(feature, &featureMaskCopy);
3152 cliPrint("Enabled");
3154 cliPrintLinef(" %s", featureNames[i]);
3155 break;
3161 #if defined(USE_BEEPER)
3162 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3164 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3165 const uint8_t beeperCount = beeperTableEntryCount();
3166 for (int32_t i = 0; i < beeperCount - 1; i++) {
3167 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3168 const char *formatOff = "%s -%s";
3169 const char *formatOn = "%s %s";
3170 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3171 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3172 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3173 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3174 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3179 static void processBeeperCommand(char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3181 uint32_t len = strlen(cmdline);
3182 uint8_t beeperCount = beeperTableEntryCount();
3184 if (len == 0) {
3185 cliPrintf("Disabled:");
3186 for (int32_t i = 0; ; i++) {
3187 if (i == beeperCount - 1) {
3188 if (*offFlags == 0)
3189 cliPrint(" none");
3190 break;
3193 if (beeperModeMaskForTableIndex(i) & *offFlags)
3194 cliPrintf(" %s", beeperNameForTableIndex(i));
3196 cliPrintLinefeed();
3197 } else if (strncasecmp(cmdline, "list", len) == 0) {
3198 cliPrint("Available:");
3199 for (uint32_t i = 0; i < beeperCount; i++) {
3200 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3201 cliPrintf(" %s", beeperNameForTableIndex(i));
3204 cliPrintLinefeed();
3205 } else {
3206 bool remove = false;
3207 if (cmdline[0] == '-') {
3208 remove = true; // this is for beeper OFF condition
3209 cmdline++;
3210 len--;
3213 for (uint32_t i = 0; ; i++) {
3214 if (i == beeperCount) {
3215 cliPrintErrorLinef("INVALID NAME");
3216 break;
3218 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3219 if (remove) { // beeper off
3220 if (i == BEEPER_ALL - 1) {
3221 *offFlags = allowedFlags;
3222 } else {
3223 *offFlags |= beeperModeMaskForTableIndex(i);
3225 cliPrint("Disabled");
3227 else { // beeper on
3228 if (i == BEEPER_ALL - 1) {
3229 *offFlags = 0;
3230 } else {
3231 *offFlags &= ~beeperModeMaskForTableIndex(i);
3233 cliPrint("Enabled");
3235 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3236 break;
3242 #if defined(USE_DSHOT)
3243 static void cliBeacon(char *cmdline)
3245 processBeeperCommand(cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3247 #endif
3249 static void cliBeeper(char *cmdline)
3251 processBeeperCommand(cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3253 #endif
3255 #if defined(USE_RX_SPI) || defined (USE_SERIALRX_SRXL2)
3256 void cliRxBind(char *cmdline){
3257 UNUSED(cmdline);
3258 if (featureIsEnabled(FEATURE_RX_SERIAL)) {
3259 switch (rxConfig()->serialrx_provider) {
3260 default:
3261 cliPrint("Not supported.");
3262 break;
3263 #if defined(USE_SERIALRX_SRXL2)
3264 case SERIALRX_SRXL2:
3265 srxl2Bind();
3266 cliPrint("Binding SRXL2 receiver...");
3267 break;
3268 #endif
3271 #if defined(USE_RX_SPI)
3272 else if (featureIsEnabled(FEATURE_RX_SPI)) {
3273 switch (rxSpiConfig()->rx_spi_protocol) {
3274 default:
3275 cliPrint("Not supported.");
3276 break;
3277 #if defined(USE_RX_FRSKY_SPI)
3278 #if defined(USE_RX_FRSKY_SPI_D)
3279 case RX_SPI_FRSKY_D:
3280 #endif
3281 #if defined(USE_RX_FRSKY_SPI_X)
3282 case RX_SPI_FRSKY_X:
3283 case RX_SPI_FRSKY_X_LBT:
3284 #endif
3285 #endif // USE_RX_FRSKY_SPI
3286 #ifdef USE_RX_SFHSS_SPI
3287 case RX_SPI_SFHSS:
3288 #endif
3289 #ifdef USE_RX_FLYSKY
3290 case RX_SPI_A7105_FLYSKY:
3291 case RX_SPI_A7105_FLYSKY_2A:
3292 #endif
3293 #ifdef USE_RX_SPEKTRUM
3294 case RX_SPI_CYRF6936_DSM:
3295 #endif
3296 #if defined(USE_RX_FRSKY_SPI) || defined(USE_RX_SFHSS_SPI) || defined(USE_RX_FLYSKY) || defined(USE_RX_SPEKTRUM)
3297 rxSpiBind();
3298 cliPrint("Binding...");
3299 break;
3300 #endif
3304 #endif
3306 #endif
3308 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3310 bool equalsDefault = true;
3311 char buf[16];
3312 char bufDefault[16];
3313 uint32_t i;
3315 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3316 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3317 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3318 if (defaultRxConfig) {
3319 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3320 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3323 buf[i] = '\0';
3325 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3326 const char *formatMap = "map %s";
3327 if (defaultRxConfig) {
3328 bufDefault[i] = '\0';
3329 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3331 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3335 static void cliMap(char *cmdline)
3337 uint32_t i;
3338 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3340 uint32_t len = strlen(cmdline);
3341 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3343 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3344 buf[i] = toupper((unsigned char)cmdline[i]);
3346 buf[i] = '\0';
3348 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3349 buf[i] = toupper((unsigned char)cmdline[i]);
3351 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3352 continue;
3354 cliShowParseError();
3355 return;
3357 parseRcChannels(buf, rxConfigMutable());
3358 } else if (len > 0) {
3359 cliShowParseError();
3360 return;
3363 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3364 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3367 buf[i] = '\0';
3368 cliPrintLinef("map %s", buf);
3371 static char *skipSpace(char *buffer)
3373 while (*(buffer) == ' ') {
3374 buffer++;
3377 return buffer;
3380 static char *checkCommand(char *cmdline, const char *command)
3382 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3383 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3384 return skipSpace(cmdline + strlen(command) + 1);
3385 } else {
3386 return 0;
3390 static void cliRebootEx(rebootTarget_e rebootTarget)
3392 cliPrint("\r\nRebooting");
3393 bufWriterFlush(cliWriter);
3394 waitForSerialPortToFinishTransmitting(cliPort);
3395 motorShutdown();
3397 switch (rebootTarget) {
3398 case REBOOT_TARGET_BOOTLOADER_ROM:
3399 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3401 break;
3402 #if defined(USE_FLASH_BOOT_LOADER)
3403 case REBOOT_TARGET_BOOTLOADER_FLASH:
3404 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3406 break;
3407 #endif
3408 case REBOOT_TARGET_FIRMWARE:
3409 default:
3410 systemReset();
3412 break;
3416 static void cliReboot(void)
3418 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3421 static void cliBootloader(char *cmdline)
3423 rebootTarget_e rebootTarget;
3424 if (
3425 #if !defined(USE_FLASH_BOOT_LOADER)
3426 isEmpty(cmdline) ||
3427 #endif
3428 strncasecmp(cmdline, "rom", 3) == 0) {
3429 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3431 cliPrintHashLine("restarting in ROM bootloader mode");
3432 #if defined(USE_FLASH_BOOT_LOADER)
3433 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3434 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3436 cliPrintHashLine("restarting in flash bootloader mode");
3437 #endif
3438 } else {
3439 cliPrintErrorLinef("Invalid option");
3441 return;
3444 cliRebootEx(rebootTarget);
3447 static void cliExit(char *cmdline)
3449 UNUSED(cmdline);
3451 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3452 bufWriterFlush(cliWriter);
3454 *cliBuffer = '\0';
3455 bufferIndex = 0;
3456 cliMode = 0;
3457 // incase a motor was left running during motortest, clear it here
3458 mixerResetDisarmedMotors();
3459 cliReboot();
3461 cliWriter = NULL;
3464 #ifdef USE_GPS
3465 static void cliGpsPassthrough(char *cmdline)
3467 UNUSED(cmdline);
3469 gpsEnablePassthrough(cliPort);
3471 #endif
3473 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3474 static void cliPrintGyroRegisters(uint8_t whichSensor)
3476 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3477 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3478 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3481 static void cliDumpGyroRegisters(char *cmdline)
3483 #ifdef USE_MULTI_GYRO
3484 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3485 cliPrintLinef("\r\n# Gyro 1");
3486 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3488 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3489 cliPrintLinef("\r\n# Gyro 2");
3490 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3492 #else
3493 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3494 #endif
3495 UNUSED(cmdline);
3497 #endif
3500 static int parseOutputIndex(char *pch, bool allowAllEscs) {
3501 int outputIndex = atoi(pch);
3502 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3503 cliPrintLinef("Using output %d.", outputIndex);
3504 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3505 cliPrintLinef("Using all outputs.");
3506 } else {
3507 cliPrintErrorLinef("INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3509 return -1;
3512 return outputIndex;
3515 #if defined(USE_DSHOT)
3516 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3518 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3519 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3520 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3522 enum {
3523 ESC_INFO_KISS_V1,
3524 ESC_INFO_KISS_V2,
3525 ESC_INFO_BLHELI32
3528 #define ESC_INFO_VERSION_POSITION 12
3530 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
3532 bool escInfoReceived = false;
3533 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3534 uint8_t escInfoVersion;
3535 uint8_t frameLength;
3536 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3537 escInfoVersion = ESC_INFO_BLHELI32;
3538 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3539 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3540 escInfoVersion = ESC_INFO_KISS_V2;
3541 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3542 } else {
3543 escInfoVersion = ESC_INFO_KISS_V1;
3544 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3547 if (bytesRead == frameLength) {
3548 escInfoReceived = true;
3550 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3551 uint8_t firmwareVersion = 0;
3552 uint8_t firmwareSubVersion = 0;
3553 uint8_t escType = 0;
3554 switch (escInfoVersion) {
3555 case ESC_INFO_KISS_V1:
3556 firmwareVersion = escInfoBuffer[12];
3557 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3558 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3560 break;
3561 case ESC_INFO_KISS_V2:
3562 firmwareVersion = escInfoBuffer[13];
3563 firmwareSubVersion = escInfoBuffer[14];
3564 escType = escInfoBuffer[15];
3566 break;
3567 case ESC_INFO_BLHELI32:
3568 firmwareVersion = escInfoBuffer[13];
3569 firmwareSubVersion = escInfoBuffer[14];
3570 escType = escInfoBuffer[15];
3572 break;
3575 cliPrint("ESC Type: ");
3576 switch (escInfoVersion) {
3577 case ESC_INFO_KISS_V1:
3578 case ESC_INFO_KISS_V2:
3579 switch (escType) {
3580 case 1:
3581 cliPrintLine("KISS8A");
3583 break;
3584 case 2:
3585 cliPrintLine("KISS16A");
3587 break;
3588 case 3:
3589 cliPrintLine("KISS24A");
3591 break;
3592 case 5:
3593 cliPrintLine("KISS Ultralite");
3595 break;
3596 default:
3597 cliPrintLine("unknown");
3599 break;
3602 break;
3603 case ESC_INFO_BLHELI32:
3605 char *escType = (char *)(escInfoBuffer + 31);
3606 escType[32] = 0;
3607 cliPrintLine(escType);
3610 break;
3613 cliPrint("MCU Serial No: 0x");
3614 for (int i = 0; i < 12; i++) {
3615 if (i && (i % 3 == 0)) {
3616 cliPrint("-");
3618 cliPrintf("%02x", escInfoBuffer[i]);
3620 cliPrintLinefeed();
3622 switch (escInfoVersion) {
3623 case ESC_INFO_KISS_V1:
3624 case ESC_INFO_KISS_V2:
3625 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3627 break;
3628 case ESC_INFO_BLHELI32:
3629 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3631 break;
3633 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3634 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3635 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3636 if (escInfoVersion == ESC_INFO_BLHELI32) {
3637 uint8_t setting = escInfoBuffer[18];
3638 cliPrint("Low voltage Limit: ");
3639 switch (setting) {
3640 case 0:
3641 cliPrintLine("off");
3643 break;
3644 case 255:
3645 cliPrintLine("unsupported");
3647 break;
3648 default:
3649 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3651 break;
3654 setting = escInfoBuffer[19];
3655 cliPrint("Current Limit: ");
3656 switch (setting) {
3657 case 0:
3658 cliPrintLine("off");
3660 break;
3661 case 255:
3662 cliPrintLine("unsupported");
3664 break;
3665 default:
3666 cliPrintLinef("%d", setting);
3668 break;
3671 for (int i = 0; i < 4; i++) {
3672 setting = escInfoBuffer[i + 20];
3673 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3677 } else {
3678 cliPrintErrorLinef("CHECKSUM ERROR.");
3683 if (!escInfoReceived) {
3684 cliPrintLine("No Info.");
3688 static void executeEscInfoCommand(uint8_t escIndex)
3690 cliPrintLinef("Info for ESC %d:", escIndex);
3692 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3694 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3696 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, true);
3698 delay(10);
3700 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
3702 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3705 // XXX Review dshotprog command under refactored motor handling
3707 static void cliDshotProg(char *cmdline)
3709 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
3710 cliShowParseError();
3712 return;
3715 char *saveptr;
3716 char *pch = strtok_r(cmdline, " ", &saveptr);
3717 int pos = 0;
3718 int escIndex = 0;
3719 bool firstCommand = true;
3720 while (pch != NULL) {
3721 switch (pos) {
3722 case 0:
3723 escIndex = parseOutputIndex(pch, true);
3724 if (escIndex == -1) {
3725 return;
3728 break;
3729 default:
3731 int command = atoi(pch);
3732 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3733 if (firstCommand) {
3734 // pwmDisableMotors();
3735 motorDisable();
3737 if (command == DSHOT_CMD_ESC_INFO) {
3738 delay(5); // Wait for potential ESC telemetry transmission to finish
3739 } else {
3740 delay(1);
3743 firstCommand = false;
3746 if (command != DSHOT_CMD_ESC_INFO) {
3747 dshotCommandWrite(escIndex, getMotorCount(), command, true);
3748 } else {
3749 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3750 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3751 if (escIndex != ALL_MOTORS) {
3752 executeEscInfoCommand(escIndex);
3753 } else {
3754 for (uint8_t i = 0; i < getMotorCount(); i++) {
3755 executeEscInfoCommand(i);
3758 } else
3759 #endif
3761 cliPrintLine("Not supported.");
3765 cliPrintLinef("Command Sent: %d", command);
3767 } else {
3768 cliPrintErrorLinef("INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3772 break;
3775 pos++;
3776 pch = strtok_r(NULL, " ", &saveptr);
3779 motorEnable();
3781 #endif // USE_DSHOT
3783 #ifdef USE_ESCSERIAL
3784 static void cliEscPassthrough(char *cmdline)
3786 if (isEmpty(cmdline)) {
3787 cliShowParseError();
3789 return;
3792 char *saveptr;
3793 char *pch = strtok_r(cmdline, " ", &saveptr);
3794 int pos = 0;
3795 uint8_t mode = 0;
3796 int escIndex = 0;
3797 while (pch != NULL) {
3798 switch (pos) {
3799 case 0:
3800 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3801 mode = PROTOCOL_SIMONK;
3802 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3803 mode = PROTOCOL_BLHELI;
3804 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3805 mode = PROTOCOL_KISS;
3806 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3807 mode = PROTOCOL_KISSALL;
3808 } else {
3809 cliShowParseError();
3811 return;
3813 break;
3814 case 1:
3815 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
3816 if (escIndex == -1) {
3817 return;
3820 break;
3821 default:
3822 cliShowParseError();
3824 return;
3826 break;
3829 pos++;
3830 pch = strtok_r(NULL, " ", &saveptr);
3833 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
3834 cliPrintErrorLinef("Error starting ESC connection");
3837 #endif
3839 #ifndef USE_QUAD_MIXER_ONLY
3840 static void cliMixer(char *cmdline)
3842 int len;
3844 len = strlen(cmdline);
3846 if (len == 0) {
3847 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3848 return;
3849 } else if (strncasecmp(cmdline, "list", len) == 0) {
3850 cliPrint("Available:");
3851 for (uint32_t i = 0; ; i++) {
3852 if (mixerNames[i] == NULL)
3853 break;
3854 cliPrintf(" %s", mixerNames[i]);
3856 cliPrintLinefeed();
3857 return;
3860 for (uint32_t i = 0; ; i++) {
3861 if (mixerNames[i] == NULL) {
3862 cliPrintErrorLinef("INVALID NAME");
3863 return;
3865 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
3866 mixerConfigMutable()->mixerMode = i + 1;
3867 break;
3871 cliMixer("");
3873 #endif
3875 static void cliMotor(char *cmdline)
3877 if (isEmpty(cmdline)) {
3878 cliShowParseError();
3880 return;
3883 int motorIndex = 0;
3884 int motorValue = 0;
3886 char *saveptr;
3887 char *pch = strtok_r(cmdline, " ", &saveptr);
3888 int index = 0;
3889 while (pch != NULL) {
3890 switch (index) {
3891 case 0:
3892 motorIndex = parseOutputIndex(pch, true);
3893 if (motorIndex == -1) {
3894 return;
3897 break;
3898 case 1:
3899 motorValue = atoi(pch);
3901 break;
3903 index++;
3904 pch = strtok_r(NULL, " ", &saveptr);
3907 if (index == 2) {
3908 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
3909 cliShowArgumentRangeError("VALUE", 1000, 2000);
3910 } else {
3911 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
3913 if (motorIndex != ALL_MOTORS) {
3914 motor_disarmed[motorIndex] = motorOutputValue;
3916 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
3917 } else {
3918 for (int i = 0; i < getMotorCount(); i++) {
3919 motor_disarmed[i] = motorOutputValue;
3922 cliPrintLinef("all motors: %d", motorOutputValue);
3925 } else {
3926 cliShowParseError();
3930 #ifndef MINIMAL_CLI
3931 static void cliPlaySound(char *cmdline)
3933 int i;
3934 const char *name;
3935 static int lastSoundIdx = -1;
3937 if (isEmpty(cmdline)) {
3938 i = lastSoundIdx + 1; //next sound index
3939 if ((name=beeperNameForTableIndex(i)) == NULL) {
3940 while (true) { //no name for index; try next one
3941 if (++i >= beeperTableEntryCount())
3942 i = 0; //if end then wrap around to first entry
3943 if ((name=beeperNameForTableIndex(i)) != NULL)
3944 break; //if name OK then play sound below
3945 if (i == lastSoundIdx + 1) { //prevent infinite loop
3946 cliPrintErrorLinef("ERROR PLAYING SOUND");
3947 return;
3951 } else { //index value was given
3952 i = atoi(cmdline);
3953 if ((name=beeperNameForTableIndex(i)) == NULL) {
3954 cliPrintLinef("No sound for index %d", i);
3955 return;
3958 lastSoundIdx = i;
3959 beeperSilence();
3960 cliPrintLinef("Playing sound %d: %s", i, name);
3961 beeper(beeperModeForTableIndex(i));
3963 #endif
3965 static void cliProfile(char *cmdline)
3967 if (isEmpty(cmdline)) {
3968 cliPrintLinef("profile %d", getPidProfileIndexToUse());
3969 return;
3970 } else {
3971 const int i = atoi(cmdline);
3972 if (i >= 0 && i < PID_PROFILE_COUNT) {
3973 changePidProfile(i);
3974 cliProfile("");
3975 } else {
3976 cliPrintErrorLinef("PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
3981 static void cliRateProfile(char *cmdline)
3983 if (isEmpty(cmdline)) {
3984 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
3985 return;
3986 } else {
3987 const int i = atoi(cmdline);
3988 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
3989 changeControlRateProfile(i);
3990 cliRateProfile("");
3991 } else {
3992 cliPrintErrorLinef("RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
3997 static void cliDumpPidProfile(uint8_t pidProfileIndex, dumpFlags_t dumpMask)
3999 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4000 // Faulty values
4001 return;
4004 pidProfileIndexToUse = pidProfileIndex;
4006 cliPrintLinefeed();
4007 cliProfile("");
4009 char profileStr[10];
4010 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4011 dumpAllValues(PROFILE_VALUE, dumpMask, profileStr);
4013 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4016 static void cliDumpRateProfile(uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4018 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4019 // Faulty values
4020 return;
4023 rateProfileIndexToUse = rateProfileIndex;
4025 cliPrintLinefeed();
4026 cliRateProfile("");
4028 char rateProfileStr[14];
4029 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4030 dumpAllValues(PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4032 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4035 #ifdef USE_CLI_BATCH
4036 static void cliPrintCommandBatchWarning(const char *warning)
4038 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4039 if (warning) {
4040 cliPrintErrorLinef(warning);
4044 static void resetCommandBatch(void)
4046 commandBatchActive = false;
4047 commandBatchError = false;
4050 static void cliBatch(char *cmdline)
4052 if (strncasecmp(cmdline, "start", 5) == 0) {
4053 if (!commandBatchActive) {
4054 commandBatchActive = true;
4055 commandBatchError = false;
4057 cliPrintLine("Command batch started");
4058 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4059 if (commandBatchActive && commandBatchError) {
4060 cliPrintCommandBatchWarning(NULL);
4061 } else {
4062 cliPrintLine("Command batch ended");
4064 resetCommandBatch();
4065 } else {
4066 cliPrintErrorLinef("Invalid option");
4069 #endif
4071 static void cliSave(char *cmdline)
4073 UNUSED(cmdline);
4075 #ifdef USE_CLI_BATCH
4076 if (commandBatchActive && commandBatchError) {
4077 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
4078 resetCommandBatch();
4079 return;
4081 #endif
4083 cliPrintHashLine("saving");
4085 #if defined(USE_BOARD_INFO)
4086 if (boardInformationUpdated) {
4087 persistBoardInformation();
4089 #if defined(USE_SIGNATURE)
4090 if (signatureUpdated) {
4091 persistSignature();
4093 #endif
4094 #endif // USE_BOARD_INFO
4096 if (featureMaskIsCopied) {
4097 writeEEPROMWithFeatures(featureMaskCopy);
4098 } else {
4099 writeEEPROM();
4102 cliReboot();
4105 static void cliDefaults(char *cmdline)
4107 bool saveConfigs;
4109 if (isEmpty(cmdline)) {
4110 saveConfigs = true;
4111 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
4112 saveConfigs = false;
4113 } else {
4114 return;
4117 cliPrintHashLine("resetting to defaults");
4119 resetConfigs();
4121 #ifdef USE_CLI_BATCH
4122 // Reset only the error state and allow the batch active state to remain.
4123 // This way if a "defaults nosave" was issued after the "batch on" we'll
4124 // only reset the current error state but the batch will still be active
4125 // for subsequent commands.
4126 commandBatchError = false;
4127 #endif
4129 if (saveConfigs) {
4130 cliSave(NULL);
4134 void cliPrintVarDefault(const clivalue_t *value)
4136 const pgRegistry_t *pg = pgFind(value->pgn);
4137 if (pg) {
4138 const char *defaultFormat = "Default value: ";
4139 const int valueOffset = getValueOffset(value);
4140 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
4141 if (!equalsDefault) {
4142 cliPrintf(defaultFormat, value->name);
4143 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
4144 cliPrintLinefeed();
4149 STATIC_UNIT_TESTED void cliGet(char *cmdline)
4151 const clivalue_t *val;
4152 int matchedCommands = 0;
4154 pidProfileIndexToUse = getCurrentPidProfileIndex();
4155 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4157 backupAndResetConfigs();
4159 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4160 if (strcasestr(valueTable[i].name, cmdline)) {
4161 val = &valueTable[i];
4162 if (matchedCommands > 0) {
4163 cliPrintLinefeed();
4165 cliPrintf("%s = ", valueTable[i].name);
4166 cliPrintVar(val, 0);
4167 cliPrintLinefeed();
4168 switch (val->type & VALUE_SECTION_MASK) {
4169 case PROFILE_VALUE:
4170 cliProfile("");
4172 break;
4173 case PROFILE_RATE_VALUE:
4174 cliRateProfile("");
4176 break;
4177 default:
4179 break;
4181 cliPrintVarRange(val);
4182 cliPrintVarDefault(val);
4184 matchedCommands++;
4188 restoreConfigs();
4190 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4191 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4193 if (matchedCommands) {
4194 return;
4197 cliPrintErrorLinef("INVALID NAME");
4200 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
4202 while (*(bufEnd - 1) == ' ') {
4203 bufEnd--;
4206 return bufEnd - bufBegin;
4209 uint16_t cliGetSettingIndex(char *name, uint8_t length)
4211 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4212 const char *settingName = valueTable[i].name;
4214 // ensure exact match when setting to prevent setting variables with shorter names
4215 if (strncasecmp(name, settingName, strlen(settingName)) == 0 && length == strlen(settingName)) {
4216 return i;
4219 return valueTableEntryCount;
4222 STATIC_UNIT_TESTED void cliSet(char *cmdline)
4224 const uint32_t len = strlen(cmdline);
4225 char *eqptr;
4227 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4228 cliPrintLine("Current settings: ");
4230 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4231 const clivalue_t *val = &valueTable[i];
4232 cliPrintf("%s = ", valueTable[i].name);
4233 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4234 cliPrintLinefeed();
4236 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4237 // has equals
4239 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4241 // skip the '=' and any ' ' characters
4242 eqptr++;
4243 eqptr = skipSpace(eqptr);
4245 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4246 if (index >= valueTableEntryCount) {
4247 cliPrintErrorLinef("INVALID NAME");
4248 return;
4250 const clivalue_t *val = &valueTable[index];
4252 bool valueChanged = false;
4253 int16_t value = 0;
4254 switch (val->type & VALUE_MODE_MASK) {
4255 case MODE_DIRECT: {
4256 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4257 uint32_t value = strtoul(eqptr, NULL, 10);
4259 if (value <= val->config.u32Max) {
4260 cliSetVar(val, value);
4261 valueChanged = true;
4263 } else {
4264 int value = atoi(eqptr);
4266 int min;
4267 int max;
4268 getMinMax(val, &min, &max);
4270 if (value >= min && value <= max) {
4271 cliSetVar(val, value);
4272 valueChanged = true;
4277 break;
4278 case MODE_LOOKUP:
4279 case MODE_BITSET: {
4280 int tableIndex;
4281 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4282 tableIndex = TABLE_OFF_ON;
4283 } else {
4284 tableIndex = val->config.lookup.tableIndex;
4286 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4287 bool matched = false;
4288 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4289 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4291 if (matched) {
4292 value = tableValueIndex;
4294 cliSetVar(val, value);
4295 valueChanged = true;
4300 break;
4302 case MODE_ARRAY: {
4303 const uint8_t arrayLength = val->config.array.length;
4304 char *valPtr = eqptr;
4306 int i = 0;
4307 while (i < arrayLength && valPtr != NULL) {
4308 // skip spaces
4309 valPtr = skipSpace(valPtr);
4311 // process substring starting at valPtr
4312 // note: no need to copy substrings for atoi()
4313 // it stops at the first character that cannot be converted...
4314 switch (val->type & VALUE_TYPE_MASK) {
4315 default:
4316 case VAR_UINT8:
4318 // fetch data pointer
4319 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4320 // store value
4321 *data = (uint8_t)atoi((const char*) valPtr);
4324 break;
4325 case VAR_INT8:
4327 // fetch data pointer
4328 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4329 // store value
4330 *data = (int8_t)atoi((const char*) valPtr);
4333 break;
4334 case VAR_UINT16:
4336 // fetch data pointer
4337 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4338 // store value
4339 *data = (uint16_t)atoi((const char*) valPtr);
4342 break;
4343 case VAR_INT16:
4345 // fetch data pointer
4346 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4347 // store value
4348 *data = (int16_t)atoi((const char*) valPtr);
4351 break;
4352 case VAR_UINT32:
4354 // fetch data pointer
4355 uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
4356 // store value
4357 *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
4360 break;
4363 // find next comma (or end of string)
4364 valPtr = strchr(valPtr, ',') + 1;
4366 i++;
4370 // mark as changed
4371 valueChanged = true;
4373 break;
4374 case MODE_STRING: {
4375 char *valPtr = eqptr;
4376 valPtr = skipSpace(valPtr);
4378 const unsigned int len = strlen(valPtr);
4379 const uint8_t min = val->config.string.minlength;
4380 const uint8_t max = val->config.string.maxlength;
4381 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4382 strlen((char *)cliGetValuePointer(val)) == 0 ||
4383 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4385 if (updatable && len > 0 && len <= max) {
4386 memset((char *)cliGetValuePointer(val), 0, max);
4387 if (len >= min && strncmp(valPtr, emptyName, len)) {
4388 strncpy((char *)cliGetValuePointer(val), valPtr, len);
4390 valueChanged = true;
4391 } else {
4392 cliPrintErrorLinef("STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4395 break;
4398 if (valueChanged) {
4399 cliPrintf("%s set to ", val->name);
4400 cliPrintVar(val, 0);
4401 } else {
4402 cliPrintErrorLinef("INVALID VALUE");
4403 cliPrintVarRange(val);
4406 return;
4407 } else {
4408 // no equals, check for matching variables.
4409 cliGet(cmdline);
4413 static void cliStatus(char *cmdline)
4415 UNUSED(cmdline);
4417 // MCU type, clock, vrefint, core temperature
4419 cliPrintf("MCU %s Clock=%dMHz", MCU_TYPE_NAME, (SystemCoreClock / 1000000));
4421 #ifdef STM32F4
4422 // Only F4 is capable of switching between HSE/HSI (for now)
4423 int sysclkSource = SystemSYSCLKSource();
4425 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4426 const char *PLLSource[] = { "-HSI", "-HSE" };
4428 int pllSource;
4430 if (sysclkSource >= 2) {
4431 pllSource = SystemPLLSource();
4434 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4435 #endif
4437 #ifdef USE_ADC_INTERNAL
4438 uint16_t vrefintMv = getVrefMv();
4439 int16_t coretemp = getCoreTemperatureCelsius();
4440 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4441 #else
4442 cliPrintLinefeed();
4443 #endif
4445 // Stack and config sizes and usages
4447 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4448 #ifdef STACK_CHECK
4449 cliPrintf(", Stack used: %d", stackUsedSize());
4450 #endif
4451 cliPrintLinefeed();
4453 cliPrintLinef("Config size: %d, Max available config: %d", getEEPROMConfigSize(), getEEPROMStorageSize());
4455 // Sensors
4457 #if defined(USE_SENSOR_NAMES)
4458 const uint32_t detectedSensorsMask = sensorsMask();
4459 for (uint32_t i = 0; ; i++) {
4460 if (sensorTypeNames[i] == NULL) {
4461 break;
4463 const uint32_t mask = (1 << i);
4464 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4465 const uint8_t sensorHardwareIndex = detectedSensors[i];
4466 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4467 if (i) {
4468 cliPrint(", ");
4470 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4471 #if defined(USE_ACC)
4472 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4473 cliPrintf(".%c", acc.dev.revisionCode);
4475 #endif
4478 cliPrintLinefeed();
4479 #endif /* USE_SENSOR_NAMES */
4481 // Uptime and wall clock
4483 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4485 #ifdef USE_RTC_TIME
4486 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4487 dateTime_t dt;
4488 if (rtcGetDateTime(&dt)) {
4489 dateTimeFormatLocal(buf, &dt);
4490 cliPrintf(", Current Time: %s", buf);
4492 #endif
4493 cliPrintLinefeed();
4495 // Run status
4497 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
4498 const int rxRate = currentRxRefreshRate == 0 ? 0 : (int)(1000000.0f / ((float)currentRxRefreshRate));
4499 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
4500 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4501 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
4503 // Battery meter
4505 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4507 // Other devices and status
4509 #ifdef USE_I2C
4510 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4511 #else
4512 const uint16_t i2cErrorCounter = 0;
4513 #endif
4514 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4516 #ifdef USE_SDCARD
4517 cliSdInfo(NULL);
4518 #endif
4520 cliPrint("Arming disable flags:");
4521 armingDisableFlags_e flags = getArmingDisableFlags();
4522 while (flags) {
4523 const int bitpos = ffs(flags) - 1;
4524 flags &= ~(1 << bitpos);
4525 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4527 cliPrintLinefeed();
4530 #if defined(USE_TASK_STATISTICS)
4531 static void cliTasks(char *cmdline)
4533 UNUSED(cmdline);
4534 int maxLoadSum = 0;
4535 int averageLoadSum = 0;
4537 #ifndef MINIMAL_CLI
4538 if (systemConfig()->task_statistics) {
4539 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4540 } else {
4541 cliPrintLine("Task list");
4543 #endif
4544 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4545 cfTaskInfo_t taskInfo;
4546 getTaskInfo(taskId, &taskInfo);
4547 if (taskInfo.isEnabled) {
4548 int taskFrequency;
4549 int subTaskFrequency = 0;
4550 if (taskId == TASK_GYROPID) {
4551 subTaskFrequency = taskInfo.movingAverageCycleTime == 0.0f ? 0.0f : (int)(1000000.0f / (taskInfo.movingAverageCycleTime));
4552 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
4553 if (pidConfig()->pid_process_denom > 1) {
4554 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4555 } else {
4556 taskFrequency = subTaskFrequency;
4557 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
4559 } else {
4560 taskFrequency = taskInfo.averageDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.averageDeltaTime));
4561 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4563 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
4564 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
4565 if (taskId != TASK_SERIAL) {
4566 maxLoadSum += maxLoad;
4567 averageLoadSum += averageLoad;
4569 if (systemConfig()->task_statistics) {
4570 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4571 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
4572 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
4573 } else {
4574 cliPrintLinef("%6d", taskFrequency);
4576 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
4577 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
4580 schedulerResetTaskMaxExecutionTime(taskId);
4583 if (systemConfig()->task_statistics) {
4584 cfCheckFuncInfo_t checkFuncInfo;
4585 getCheckFuncInfo(&checkFuncInfo);
4586 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
4587 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
4590 #endif
4592 static void cliVersion(char *cmdline)
4594 UNUSED(cmdline);
4596 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4597 FC_FIRMWARE_NAME,
4598 targetName,
4599 systemConfig()->boardIdentifier,
4600 FC_VERSION_STRING,
4601 buildDate,
4602 buildTime,
4603 shortGitRevision,
4604 MSP_API_VERSION_STRING
4606 #ifdef FEATURE_CUT_LEVEL
4607 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL);
4608 #else
4609 cliPrintLinefeed();
4610 #endif
4613 #ifdef USE_RC_SMOOTHING_FILTER
4614 static void cliRcSmoothing(char *cmdline)
4616 UNUSED(cmdline);
4617 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
4618 cliPrint("# RC Smoothing Type: ");
4619 if (rxConfig()->rc_smoothing_type == RC_SMOOTHING_TYPE_FILTER) {
4620 cliPrintLine("FILTER");
4621 uint16_t avgRxFrameMs = rcSmoothingData->averageFrameTimeUs;
4622 if (rcSmoothingAutoCalculate()) {
4623 cliPrint("# Detected RX frame rate: ");
4624 if (avgRxFrameMs == 0) {
4625 cliPrintLine("NO SIGNAL");
4626 } else {
4627 cliPrintLinef("%d.%dms", avgRxFrameMs / 1000, avgRxFrameMs % 1000);
4630 cliPrint("# Input filter type: ");
4631 cliPrintLinef(lookupTables[TABLE_RC_SMOOTHING_INPUT_TYPE].values[rcSmoothingData->inputFilterType]);
4632 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingData->inputCutoffFrequency);
4633 if (rcSmoothingData->inputCutoffSetting == 0) {
4634 cliPrintLine("(auto)");
4635 } else {
4636 cliPrintLine("(manual)");
4638 cliPrint("# Derivative filter type: ");
4639 cliPrintLinef(lookupTables[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE].values[rcSmoothingData->derivativeFilterType]);
4640 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingData->derivativeCutoffFrequency);
4641 if (rcSmoothingData->derivativeFilterType == RC_SMOOTHING_DERIVATIVE_OFF) {
4642 cliPrintLine("off)");
4643 } else {
4644 if (rcSmoothingData->derivativeCutoffSetting == 0) {
4645 cliPrintLine("auto)");
4646 } else {
4647 cliPrintLine("manual)");
4650 } else {
4651 cliPrintLine("INTERPOLATION");
4654 #endif // USE_RC_SMOOTHING_FILTER
4656 #if defined(USE_RESOURCE_MGMT)
4658 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
4660 typedef struct {
4661 const uint8_t owner;
4662 pgn_t pgn;
4663 uint8_t stride;
4664 uint8_t offset;
4665 const uint8_t maxIndex;
4666 } cliResourceValue_t;
4668 // Handy macros for keeping the table tidy.
4669 // DEFS : Single entry
4670 // DEFA : Array of uint8_t (stride = 1)
4671 // DEFW : Wider stride case; array of structs.
4673 #define DEFS(owner, pgn, type, member) \
4674 { owner, pgn, 0, offsetof(type, member), 0 }
4676 #define DEFA(owner, pgn, type, member, max) \
4677 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
4679 #define DEFW(owner, pgn, type, member, max) \
4680 { owner, pgn, sizeof(type), offsetof(type, member), max }
4682 const cliResourceValue_t resourceTable[] = {
4683 #ifdef USE_BEEPER
4684 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
4685 #endif
4686 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
4687 #ifdef USE_SERVOS
4688 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
4689 #endif
4690 #if defined(USE_PPM)
4691 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
4692 #endif
4693 #if defined(USE_PWM)
4694 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
4695 #endif
4696 #ifdef USE_RANGEFINDER_HCSR04
4697 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
4698 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
4699 #endif
4700 #ifdef USE_LED_STRIP
4701 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
4702 #endif
4703 #ifdef USE_UART
4704 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
4705 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
4706 #endif
4707 #ifdef USE_INVERTER
4708 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
4709 #endif
4710 #ifdef USE_I2C
4711 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
4712 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
4713 #endif
4714 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
4715 #ifdef USE_SPEKTRUM_BIND
4716 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
4717 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
4718 #endif
4719 #ifdef USE_TRANSPONDER
4720 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
4721 #endif
4722 #ifdef USE_SPI
4723 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
4724 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
4725 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
4726 #endif
4727 #ifdef USE_ESCSERIAL
4728 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
4729 #endif
4730 #ifdef USE_CAMERA_CONTROL
4731 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
4732 #endif
4733 #ifdef USE_ADC
4734 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
4735 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
4736 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
4737 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
4738 #endif
4739 #ifdef USE_BARO
4740 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
4741 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
4742 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
4743 #endif
4744 #ifdef USE_MAG
4745 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
4746 #ifdef USE_MAG_DATA_READY_SIGNAL
4747 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
4748 #endif
4749 #endif
4750 #ifdef USE_SDCARD_SPI
4751 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
4752 #endif
4753 #ifdef USE_SDCARD
4754 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
4755 #endif
4756 #ifdef USE_PINIO
4757 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
4758 #endif
4759 #if defined(USE_USB_MSC)
4760 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
4761 #endif
4762 #ifdef USE_FLASH_CHIP
4763 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
4764 #endif
4765 #ifdef USE_MAX7456
4766 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
4767 #endif
4768 #ifdef USE_RX_SPI
4769 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
4770 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
4771 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
4772 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
4773 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
4774 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
4775 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
4776 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
4777 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
4778 #endif
4779 #endif
4780 #endif
4781 #ifdef USE_GYRO_EXTI
4782 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
4783 #endif
4784 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
4785 #ifdef USE_USB_DETECT
4786 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
4787 #endif
4788 #ifdef USE_VTX_RTC6705
4789 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
4790 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
4791 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
4792 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
4793 #endif
4796 #undef DEFS
4797 #undef DEFA
4798 #undef DEFW
4800 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
4802 const pgRegistry_t* rec = pgFind(value.pgn);
4803 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
4806 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
4808 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
4809 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
4810 const char* owner = ownerNames[resourceTable[i].owner];
4811 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
4812 const void *currentConfig;
4813 const void *defaultConfig;
4814 if (configIsInCopy) {
4815 currentConfig = pg->copy;
4816 defaultConfig = pg->address;
4817 } else {
4818 currentConfig = pg->address;
4819 defaultConfig = NULL;
4822 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
4823 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
4824 ioTag_t ioTagDefault = NULL;
4825 if (defaultConfig) {
4826 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
4829 const bool equalsDefault = ioTag == ioTagDefault;
4830 const char *format = "resource %s %d %c%02d";
4831 const char *formatUnassigned = "resource %s %d NONE";
4832 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
4833 if (ioTagDefault) {
4834 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
4835 } else if (defaultConfig) {
4836 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
4838 if (ioTag) {
4839 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
4840 } else if (!(dumpMask & HIDE_UNUSED)) {
4841 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
4847 static void printResourceOwner(uint8_t owner, uint8_t index)
4849 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
4851 if (resourceTable[owner].maxIndex > 0) {
4852 cliPrintf(" %d", RESOURCE_INDEX(index));
4856 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
4858 if (!newTag) {
4859 return;
4862 const char * format = "\r\nNOTE: %c%02d already assigned to ";
4863 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
4864 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
4865 ioTag_t *tag = getIoTag(resourceTable[r], i);
4866 if (*tag == newTag) {
4867 bool cleared = false;
4868 if (r == resourceIndex) {
4869 if (i == index) {
4870 continue;
4872 *tag = IO_TAG_NONE;
4873 cleared = true;
4876 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
4878 printResourceOwner(r, i);
4880 if (cleared) {
4881 cliPrintf(". ");
4882 printResourceOwner(r, i);
4883 cliPrintf(" disabled");
4886 cliPrintLine(".");
4892 static bool strToPin(char *pch, ioTag_t *tag)
4894 if (strcasecmp(pch, "NONE") == 0) {
4895 *tag = IO_TAG_NONE;
4896 return true;
4897 } else {
4898 unsigned pin = 0;
4899 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
4901 if (port < 8) {
4902 pch++;
4903 pin = atoi(pch);
4904 if (pin < 16) {
4905 *tag = DEFIO_TAG_MAKE(port, pin);
4906 return true;
4910 return false;
4913 #ifdef USE_DMA
4914 static void printDma(void)
4916 cliPrintLinefeed();
4918 #ifdef MINIMAL_CLI
4919 cliPrintLine("DMA:");
4920 #else
4921 cliPrintLine("Currently active DMA:");
4922 cliRepeat('-', 20);
4923 #endif
4924 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
4925 const char* owner;
4926 owner = ownerNames[dmaGetOwner(i)];
4928 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
4929 uint8_t resourceIndex = dmaGetResourceIndex(i);
4930 if (resourceIndex > 0) {
4931 cliPrintLinef(" %s %d", owner, resourceIndex);
4932 } else {
4933 cliPrintLinef(" %s", owner);
4937 #endif
4939 #ifdef USE_DMA_SPEC
4941 typedef struct dmaoptEntry_s {
4942 char *device;
4943 dmaPeripheral_e peripheral;
4944 pgn_t pgn;
4945 uint8_t stride;
4946 uint8_t offset;
4947 uint8_t maxIndex;
4948 uint32_t presenceMask;
4949 } dmaoptEntry_t;
4951 #define MASK_IGNORED (0)
4953 // Handy macros for keeping the table tidy.
4954 // DEFS : Single entry
4955 // DEFA : Array of uint8_t (stride = 1)
4956 // DEFW : Wider stride case; array of structs.
4958 #define DEFS(device, peripheral, pgn, type, member) \
4959 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
4961 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
4962 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
4964 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
4965 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
4967 dmaoptEntry_t dmaoptEntryTable[] = {
4968 DEFW("SPI_TX", DMA_PERIPH_SPI_TX, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
4969 DEFW("SPI_RX", DMA_PERIPH_SPI_RX, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
4970 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
4971 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
4972 DEFW("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
4973 DEFW("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
4974 #ifdef STM32H7
4975 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
4976 #endif
4979 #undef DEFS
4980 #undef DEFA
4981 #undef DEFW
4983 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
4984 #define DMA_OPT_STRING_BUFSIZE 5
4986 #ifdef STM32H7
4987 #define DMA_CHANREQ_STRING "Request"
4988 #else
4989 #define DMA_CHANREQ_STRING "Channel"
4990 #endif
4992 #define DMASPEC_FORMAT_STRING "DMA%d Stream %d " DMA_CHANREQ_STRING " %d"
4994 static void optToString(int optval, char *buf)
4996 if (optval == DMA_OPT_UNUSED) {
4997 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
4998 } else {
4999 tfp_sprintf(buf, "%d", optval);
5003 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5005 if (dmaopt != DMA_OPT_UNUSED) {
5006 printValue(dumpMask, equalsDefault,
5007 "dma %s %d %d",
5008 entry->device, DMA_OPT_UI_INDEX(index), dmaopt);
5010 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5011 dmaCode_t dmaCode = 0;
5012 if (dmaChannelSpec) {
5013 dmaCode = dmaChannelSpec->code;
5015 printValue(dumpMask, equalsDefault,
5016 "# %s %d: " DMASPEC_FORMAT_STRING,
5017 entry->device, DMA_OPT_UI_INDEX(index), DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5018 } else if (!(dumpMask & HIDE_UNUSED)) {
5019 printValue(dumpMask, equalsDefault,
5020 "dma %s %d NONE",
5021 entry->device, DMA_OPT_UI_INDEX(index));
5025 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5027 const pgRegistry_t* pg = pgFind(entry->pgn);
5028 const void *currentConfig;
5029 const void *defaultConfig;
5031 if (configIsInCopy) {
5032 currentConfig = pg->copy;
5033 defaultConfig = pg->address;
5034 } else {
5035 currentConfig = pg->address;
5036 defaultConfig = NULL;
5039 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5040 dmaoptValue_t defaultOpt;
5042 if (defaultConfig) {
5043 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5044 } else {
5045 defaultOpt = DMA_OPT_UNUSED;
5048 bool equalsDefault = currentOpt == defaultOpt;
5049 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5051 if (defaultConfig) {
5052 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5055 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5056 return headingStr;
5059 #if defined(USE_TIMER_MGMT)
5060 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5062 const char *format = "dma pin %c%02d %d";
5064 if (dmaopt != DMA_OPT_UNUSED) {
5065 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5066 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5067 dmaopt
5070 if (printDetails) {
5071 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5072 dmaCode_t dmaCode = 0;
5073 if (dmaChannelSpec) {
5074 dmaCode = dmaChannelSpec->code;
5075 printValue(dumpMask, false,
5076 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5077 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5078 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5082 } else if (!(dumpMask & HIDE_UNUSED)) {
5083 printValue(dumpMask, equalsDefault,
5084 "dma pin %c%02d NONE",
5085 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5090 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5092 const ioTag_t ioTag = currentConfig[index].ioTag;
5094 if (!ioTag) {
5095 return headingStr;
5098 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5099 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5101 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5102 bool equalsDefault = defaultDmaopt == dmaopt;
5103 if (defaultConfig) {
5104 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5105 if (defaultConfig[i].ioTag == ioTag) {
5106 defaultDmaopt = defaultConfig[i].dmaopt;
5108 // We need to check timer as well here to get 'default' DMA options for non-default timers printed, because setting the timer resets the DMA option.
5109 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5111 tagsInUse[index] = true;
5113 break;
5118 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5120 if (defaultConfig) {
5121 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5124 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5125 return headingStr;
5127 #endif
5129 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5131 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5132 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5133 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5134 for (int index = 0; index < entry->maxIndex; index++) {
5135 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5139 #if defined(USE_TIMER_MGMT)
5140 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5141 const timerIOConfig_t *currentConfig;
5142 const timerIOConfig_t *defaultConfig;
5144 if (configIsInCopy) {
5145 currentConfig = (timerIOConfig_t *)pg->copy;
5146 defaultConfig = (timerIOConfig_t *)pg->address;
5147 } else {
5148 currentConfig = (timerIOConfig_t *)pg->address;
5149 defaultConfig = NULL;
5152 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5153 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5154 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5157 if (defaultConfig) {
5158 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5159 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5160 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5161 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5162 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5164 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5168 #endif
5171 static void cliDmaopt(char *cmdline)
5173 char *pch = NULL;
5174 char *saveptr;
5176 // Peripheral name or command option
5177 pch = strtok_r(cmdline, " ", &saveptr);
5178 if (!pch) {
5179 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5181 return;
5182 } else if (strcasecmp(pch, "list") == 0) {
5183 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5185 return;
5188 dmaoptEntry_t *entry = NULL;
5189 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5190 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5191 entry = &dmaoptEntryTable[i];
5195 if (!entry && strcasecmp(pch, "pin") != 0) {
5196 cliPrintErrorLinef("BAD DEVICE: %s", pch);
5197 return;
5200 // Index
5201 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5203 int index = 0;
5204 dmaoptValue_t *optaddr = NULL;
5206 ioTag_t ioTag = IO_TAG_NONE;
5207 #if defined(USE_TIMER_MGMT)
5208 timerIOConfig_t *timerIoConfig = NULL;
5209 #endif
5210 const timerHardware_t *timer = NULL;
5211 pch = strtok_r(NULL, " ", &saveptr);
5212 if (entry) {
5213 index = atoi(pch) - 1;
5214 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5215 cliPrintErrorLinef("BAD INDEX: '%s'", pch ? pch : "");
5216 return;
5219 const pgRegistry_t* pg = pgFind(entry->pgn);
5220 const void *currentConfig;
5221 if (configIsInCopy) {
5222 currentConfig = pg->copy;
5223 } else {
5224 currentConfig = pg->address;
5226 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5227 orgval = *optaddr;
5228 } else {
5229 // It's a pin
5230 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5231 cliPrintErrorLinef("INVALID PIN: '%s'", pch ? pch : "");
5233 return;
5236 orgval = dmaoptByTag(ioTag);
5237 #if defined(USE_TIMER_MGMT)
5238 timerIoConfig = timerIoConfigByTag(ioTag);
5239 #endif
5240 timer = timerGetByTag(ioTag);
5243 // opt or list
5244 pch = strtok_r(NULL, " ", &saveptr);
5245 if (!pch) {
5246 if (entry) {
5247 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5249 #if defined(USE_TIMER_MGMT)
5250 else {
5251 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5253 #endif
5255 return;
5256 } else if (strcasecmp(pch, "list") == 0) {
5257 // Show possible opts
5258 const dmaChannelSpec_t *dmaChannelSpec;
5259 if (entry) {
5260 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5261 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5263 } else {
5264 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5265 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5269 return;
5270 } else if (pch) {
5271 int optval;
5272 if (strcasecmp(pch, "none") == 0) {
5273 optval = DMA_OPT_UNUSED;
5274 } else {
5275 optval = atoi(pch);
5277 if (entry) {
5278 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5279 cliPrintErrorLinef("INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5281 return;
5283 } else {
5284 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5285 cliPrintErrorLinef("INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5287 return;
5292 char optvalString[DMA_OPT_STRING_BUFSIZE];
5293 optToString(optval, optvalString);
5295 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5296 optToString(orgval, orgvalString);
5298 if (optval != orgval) {
5299 if (entry) {
5300 *optaddr = optval;
5302 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5303 } else {
5304 #if defined(USE_TIMER_MGMT)
5305 timerIoConfig->dmaopt = optval;
5306 #endif
5308 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5310 } else {
5311 if (entry) {
5312 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5313 } else {
5314 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5319 #endif // USE_DMA_SPEC
5321 #ifdef USE_DMA
5322 static void cliDma(char* cmdline)
5324 int len = strlen(cmdline);
5325 if (len && strncasecmp(cmdline, "show", len) == 0) {
5326 printDma();
5328 return;
5331 #if defined(USE_DMA_SPEC)
5332 cliDmaopt(cmdline);
5333 #else
5334 cliShowParseError();
5335 #endif
5337 #endif
5339 static void cliResource(char *cmdline)
5341 char *pch = NULL;
5342 char *saveptr;
5344 pch = strtok_r(cmdline, " ", &saveptr);
5345 if (!pch) {
5346 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
5348 return;
5349 } else if (strcasecmp(pch, "show") == 0) {
5350 #ifdef MINIMAL_CLI
5351 cliPrintLine("IO");
5352 #else
5353 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
5354 cliRepeat('-', 20);
5355 #endif
5356 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
5357 const char* owner;
5358 owner = ownerNames[ioRecs[i].owner];
5360 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
5361 if (ioRecs[i].index > 0) {
5362 cliPrintf(" %d", ioRecs[i].index);
5364 cliPrintLinefeed();
5367 #if defined(USE_DMA)
5368 pch = strtok_r(NULL, " ", &saveptr);
5369 if (strcasecmp(pch, "all") == 0) {
5370 cliDma("show");
5372 #endif
5374 return;
5377 uint8_t resourceIndex = 0;
5378 int index = 0;
5379 for (resourceIndex = 0; ; resourceIndex++) {
5380 if (resourceIndex >= ARRAYLEN(resourceTable)) {
5381 cliPrintErrorLinef("INVALID RESOURCE NAME: '%s'", pch);
5382 return;
5385 const char * resourceName = ownerNames[resourceTable[resourceIndex].owner];
5386 if (strncasecmp(pch, resourceName, strlen(resourceName)) == 0) {
5387 break;
5391 pch = strtok_r(NULL, " ", &saveptr);
5392 index = atoi(pch);
5394 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
5395 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
5396 cliShowArgumentRangeError("INDEX", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
5397 return;
5399 index -= 1;
5401 pch = strtok_r(NULL, " ", &saveptr);
5404 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
5406 if (strlen(pch) > 0) {
5407 if (strToPin(pch, tag)) {
5408 if (*tag == IO_TAG_NONE) {
5409 #ifdef MINIMAL_CLI
5410 cliPrintLine("Freed");
5411 #else
5412 cliPrintLine("Resource is freed");
5413 #endif
5414 return;
5415 } else {
5416 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
5417 if (rec) {
5418 resourceCheck(resourceIndex, index, *tag);
5419 #ifdef MINIMAL_CLI
5420 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5421 #else
5422 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5423 #endif
5424 } else {
5425 cliShowParseError();
5427 return;
5432 cliShowParseError();
5435 #endif // USE_RESOURCE_MGMT
5437 #ifdef USE_TIMER_MGMT
5438 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5440 const char *format = "timer %c%02d AF%d";
5441 const char *emptyFormat = "timer %c%02d NONE";
5443 if (timerIndex > 0) {
5444 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5445 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5446 IO_GPIOPortIdxByTag(ioTag) + 'A',
5447 IO_GPIOPinIdxByTag(ioTag),
5448 timer->alternateFunction
5450 if (printDetails) {
5451 printValue(dumpMask, false,
5452 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5453 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5454 timerGetTIMNumber(timer->tim),
5455 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5456 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5457 timer->alternateFunction
5460 } else {
5461 printValue(dumpMask, equalsDefault, emptyFormat,
5462 IO_GPIOPortIdxByTag(ioTag) + 'A',
5463 IO_GPIOPinIdxByTag(ioTag)
5468 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5470 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5471 const timerIOConfig_t *currentConfig;
5472 const timerIOConfig_t *defaultConfig;
5474 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5475 if (configIsInCopy) {
5476 currentConfig = (timerIOConfig_t *)pg->copy;
5477 defaultConfig = (timerIOConfig_t *)pg->address;
5478 } else {
5479 currentConfig = (timerIOConfig_t *)pg->address;
5480 defaultConfig = NULL;
5483 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5484 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5485 const ioTag_t ioTag = currentConfig[i].ioTag;
5487 if (!ioTag) {
5488 continue;
5491 const uint8_t timerIndex = currentConfig[i].index;
5493 uint8_t defaultTimerIndex = 0;
5494 if (defaultConfig) {
5495 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5496 if (defaultConfig[i].ioTag == ioTag) {
5497 defaultTimerIndex = defaultConfig[i].index;
5498 tagsInUse[i] = true;
5500 break;
5505 const bool equalsDefault = defaultTimerIndex == timerIndex;
5506 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5507 if (defaultConfig && defaultTimerIndex) {
5508 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5511 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5514 if (defaultConfig) {
5515 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5516 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5517 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5518 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5520 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5526 #define TIMER_INDEX_UNDEFINED -1
5527 #define TIMER_AF_STRING_BUFSIZE 5
5529 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5531 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5532 if (!timer) {
5533 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5534 } else {
5535 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5539 static void cliTimer(char *cmdline)
5541 int len = strlen(cmdline);
5543 if (len == 0) {
5544 printTimer(DUMP_MASTER, NULL);
5546 return;
5547 } else if (strncasecmp(cmdline, "list", len) == 0) {
5548 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5550 return;
5551 } else if (strncasecmp(cmdline, "show", len) == 0) {
5552 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5554 return;
5557 char *pch = NULL;
5558 char *saveptr;
5560 ioTag_t ioTag = IO_TAG_NONE;
5561 pch = strtok_r(cmdline, " ", &saveptr);
5562 if (!pch || !strToPin(pch, &ioTag)) {
5563 cliShowParseError();
5565 return;
5566 } else if (!IOGetByTag(ioTag)) {
5567 cliPrintErrorLinef("PIN NOT USED ON BOARD.");
5569 return;
5572 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5573 bool isExistingTimerOpt = false;
5574 /* find existing entry, or go for next available */
5575 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5576 if (timerIOConfig(i)->ioTag == ioTag) {
5577 timerIOIndex = i;
5578 isExistingTimerOpt = true;
5580 break;
5583 /* first available empty slot */
5584 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5585 timerIOIndex = i;
5589 if (timerIOIndex < 0) {
5590 cliPrintErrorLinef("PIN TIMER MAP FULL.");
5592 return;
5595 pch = strtok_r(NULL, " ", &saveptr);
5596 if (pch) {
5597 int timerIndex;
5598 if (strcasecmp(pch, "list") == 0) {
5599 /* output the list of available options */
5600 const timerHardware_t *timer;
5601 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5602 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5603 timer->alternateFunction,
5604 timerGetTIMNumber(timer->tim),
5605 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5606 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
5610 return;
5611 } else if (strcasecmp(pch, "none") == 0) {
5612 timerIndex = TIMER_INDEX_UNDEFINED;
5613 } else if (strncasecmp(pch, "af", 2) == 0) {
5614 unsigned alternateFunction = atoi(&pch[2]);
5616 const timerHardware_t *timer;
5617 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5618 if (timer->alternateFunction == alternateFunction) {
5619 timerIndex = index;
5621 break;
5625 if (!timer) {
5626 cliPrintErrorLinef("INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5628 return;
5630 } else {
5631 timerIndex = atoi(pch);
5633 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex + 1);
5635 if (!timer) {
5636 cliPrintErrorLinef("INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5638 return;
5642 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
5643 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
5644 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
5645 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
5647 char optvalString[DMA_OPT_STRING_BUFSIZE];
5648 alternateFunctionToString(ioTag, timerIndex, optvalString);
5650 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5651 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
5653 if (timerIndex == oldTimerIndex) {
5654 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
5655 } else {
5656 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5659 return;
5660 } else {
5661 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
5663 return;
5666 #endif
5668 #ifdef USE_DSHOT_TELEMETRY
5669 static void cliDshotTelemetryInfo(char *cmdline)
5671 UNUSED(cmdline);
5673 if (useDshotTelemetry) {
5674 cliPrintLinef("Dshot reads: %u", readDoneCount);
5675 cliPrintLinef("Dshot invalid pkts: %u", dshotInvalidPacketCount);
5676 extern uint32_t setDirectionMicros;
5677 cliPrintLinef("Dshot irq micros: %u", setDirectionMicros);
5678 cliPrintLinefeed();
5680 #ifdef USE_DSHOT_TELEMETRY_STATS
5681 cliPrintLine("Motor RPM Invalid");
5682 cliPrintLine("===== ===== =======");
5683 #else
5684 cliPrintLine("Motor RPM");
5685 cliPrintLine("===== =====");
5686 #endif
5687 for (uint8_t i = 0; i < getMotorCount(); i++) {
5688 cliPrintf("%5d %5d ", i, (int)getDshotTelemetry(i));
5689 #ifdef USE_DSHOT_TELEMETRY_STATS
5690 if (isDshotMotorTelemetryActive(i)) {
5691 const int calcPercent = getDshotTelemetryMotorInvalidPercent(i);
5692 cliPrintLinef("%3d.%02d%%", calcPercent / 100, calcPercent % 100);
5693 } else {
5694 cliPrintLine("NO DATA");
5696 #else
5697 cliPrintLinefeed();
5698 #endif
5700 cliPrintLinefeed();
5702 const int len = MAX_GCR_EDGES;
5703 for (int i = 0; i < len; i++) {
5704 cliPrintf("%u ", (int)inputBuffer[i]);
5706 cliPrintLinefeed();
5707 for (int i = 1; i < len; i++) {
5708 cliPrintf("%u ", (int)(inputBuffer[i] - inputBuffer[i-1]));
5710 cliPrintLinefeed();
5711 } else {
5712 cliPrintLine("Dshot telemetry not enabled");
5715 #endif
5717 static void printConfig(char *cmdline, bool doDiff)
5719 dumpFlags_t dumpMask = DUMP_MASTER;
5720 char *options;
5721 if ((options = checkCommand(cmdline, "master"))) {
5722 dumpMask = DUMP_MASTER; // only
5723 } else if ((options = checkCommand(cmdline, "profile"))) {
5724 dumpMask = DUMP_PROFILE; // only
5725 } else if ((options = checkCommand(cmdline, "rates"))) {
5726 dumpMask = DUMP_RATES; // only
5727 } else if ((options = checkCommand(cmdline, "hardware"))) {
5728 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
5729 } else if ((options = checkCommand(cmdline, "all"))) {
5730 dumpMask = DUMP_ALL; // all profiles and rates
5731 } else {
5732 options = cmdline;
5735 if (doDiff) {
5736 dumpMask = dumpMask | DO_DIFF;
5739 if (checkCommand(options, "defaults")) {
5740 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
5741 } else if (checkCommand(options, "bare")) {
5742 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
5745 backupAndResetConfigs();
5747 #ifdef USE_CLI_BATCH
5748 bool batchModeEnabled = false;
5749 #endif
5750 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
5751 cliPrintHashLine("version");
5752 cliVersion(NULL);
5754 if (!(dumpMask & BARE)) {
5755 #ifdef USE_CLI_BATCH
5756 cliPrintHashLine("start the command batch");
5757 cliPrintLine("batch start");
5758 batchModeEnabled = true;
5759 #endif
5761 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
5762 cliPrintHashLine("reset configuration to default settings");
5763 cliPrintLine("defaults nosave");
5767 #if defined(USE_BOARD_INFO)
5768 cliPrintLinefeed();
5769 printBoardName(dumpMask);
5770 printManufacturerId(dumpMask);
5771 #endif
5773 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
5774 cliMcuId(NULL);
5775 #if defined(USE_SIGNATURE)
5776 cliSignature("");
5777 #endif
5780 if (!(dumpMask & HARDWARE_ONLY)) {
5781 printName(dumpMask, &pilotConfig_Copy);
5784 #ifdef USE_RESOURCE_MGMT
5785 printResource(dumpMask, "resources");
5786 #if defined(USE_TIMER_MGMT)
5787 printTimer(dumpMask, "timer");
5788 #endif
5789 #ifdef USE_DMA_SPEC
5790 printDmaopt(dumpMask, "dma");
5791 #endif
5792 #endif
5794 if (!(dumpMask & HARDWARE_ONLY)) {
5795 #ifndef USE_QUAD_MIXER_ONLY
5796 const char *mixerHeadingStr = "mixer";
5797 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
5798 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
5799 const char *formatMixer = "mixer %s";
5800 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
5801 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
5803 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
5805 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
5807 #ifdef USE_SERVOS
5808 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
5810 const char *servoMixHeadingStr = "servo mixer";
5811 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
5812 cliPrintHashLine(servoMixHeadingStr);
5813 cliPrintLine("smix reset\r\n");
5814 servoMixHeadingStr = NULL;
5816 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
5817 #endif
5818 #endif
5820 printFeature(dumpMask, &featureConfig_Copy, featureConfig(), "feature");
5822 #if defined(USE_BEEPER)
5823 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
5825 #if defined(USE_DSHOT)
5826 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
5827 #endif
5828 #endif // USE_BEEPER
5830 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
5832 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
5834 #ifdef USE_LED_STRIP_STATUS_MODE
5835 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
5837 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
5839 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
5840 #endif
5842 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
5844 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
5846 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
5848 #ifdef USE_VTX_CONTROL
5849 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
5850 #endif
5852 #ifdef USE_VTX_TABLE
5853 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
5854 #endif
5856 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
5859 if (dumpMask & HARDWARE_ONLY) {
5860 dumpAllValues(HARDWARE_VALUE, dumpMask, "master");
5861 } else {
5862 dumpAllValues(MASTER_VALUE, dumpMask, "master");
5864 if (dumpMask & DUMP_ALL) {
5865 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
5866 cliDumpPidProfile(pidProfileIndex, dumpMask);
5869 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
5871 if (!(dumpMask & BARE)) {
5872 cliPrintHashLine("restore original profile selection");
5874 cliProfile("");
5877 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
5879 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
5880 cliDumpRateProfile(rateIndex, dumpMask);
5883 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
5885 if (!(dumpMask & BARE)) {
5886 cliPrintHashLine("restore original rateprofile selection");
5888 cliRateProfile("");
5890 cliPrintHashLine("save configuration");
5891 cliPrint("save");
5892 #ifdef USE_CLI_BATCH
5893 batchModeEnabled = false;
5894 #endif
5897 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
5898 } else {
5899 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
5901 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
5904 } else if (dumpMask & DUMP_PROFILE) {
5905 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
5906 } else if (dumpMask & DUMP_RATES) {
5907 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
5910 #ifdef USE_CLI_BATCH
5911 if (batchModeEnabled) {
5912 cliPrintHashLine("end the command batch");
5913 cliPrintLine("batch end");
5915 #endif
5917 // restore configs from copies
5918 restoreConfigs();
5921 static void cliDump(char *cmdline)
5923 printConfig(cmdline, false);
5926 static void cliDiff(char *cmdline)
5928 printConfig(cmdline, true);
5931 #if defined(USE_USB_MSC)
5932 static void cliMsc(char *cmdline)
5934 if (mscCheckFilesystemReady()) {
5935 #ifdef USE_RTC_TIME
5936 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
5937 if (!isEmpty(cmdline)) {
5938 timezoneOffsetMinutes = atoi(cmdline);
5939 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
5940 cliPrintErrorLinef("INVALID TIMEZONE OFFSET");
5941 return;
5944 #else
5945 int timezoneOffsetMinutes = 0;
5946 UNUSED(cmdline);
5947 #endif
5948 cliPrintHashLine("Restarting in mass storage mode");
5949 cliPrint("\r\nRebooting");
5950 bufWriterFlush(cliWriter);
5951 waitForSerialPortToFinishTransmitting(cliPort);
5952 motorShutdown();
5954 systemResetToMsc(timezoneOffsetMinutes);
5955 } else {
5956 cliPrintHashLine("Storage not present or failed to initialize!");
5959 #endif
5962 typedef struct {
5963 const char *name;
5964 #ifndef MINIMAL_CLI
5965 const char *description;
5966 const char *args;
5967 #endif
5968 void (*func)(char *cmdline);
5969 } clicmd_t;
5971 #ifndef MINIMAL_CLI
5972 #define CLI_COMMAND_DEF(name, description, args, method) \
5974 name , \
5975 description , \
5976 args , \
5977 method \
5979 #else
5980 #define CLI_COMMAND_DEF(name, description, args, method) \
5982 name, \
5983 method \
5985 #endif
5987 static void cliHelp(char *cmdline);
5989 // should be sorted a..z for bsearch()
5990 const clicmd_t cmdTable[] = {
5991 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
5992 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
5993 #ifdef USE_CLI_BATCH
5994 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
5995 #endif
5996 #if defined(USE_BEEPER)
5997 #if defined(USE_DSHOT)
5998 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
5999 "\t<->[name]", cliBeacon),
6000 #endif
6001 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6002 "\t<->[name]", cliBeeper),
6003 #endif // USE_BEEPER
6004 #if defined(USE_RX_SPI) || defined (USE_SERIALRX_SRXL2)
6005 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
6006 #endif
6007 #if defined(USE_FLASH_BOOT_LOADER)
6008 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6009 #else
6010 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6011 #endif
6012 #if defined(USE_BOARD_INFO)
6013 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6014 #endif
6015 #ifdef USE_LED_STRIP_STATUS_MODE
6016 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6017 #endif
6018 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults),
6019 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6020 #ifdef USE_RESOURCE_MGMT
6022 #ifdef USE_DMA
6023 #ifdef USE_DMA_SPEC
6024 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6025 #else
6026 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6027 #endif
6028 #endif
6030 #endif
6031 #ifdef USE_DSHOT_TELEMETRY
6032 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6033 #endif
6034 #ifdef USE_DSHOT
6035 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6036 #endif
6037 CLI_COMMAND_DEF("dump", "dump configuration",
6038 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6039 #ifdef USE_ESCSERIAL
6040 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6041 #endif
6042 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
6043 CLI_COMMAND_DEF("feature", "configure features",
6044 "list\r\n"
6045 "\t<->[name]", cliFeature),
6046 #ifdef USE_FLASHFS
6047 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6048 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6049 #ifdef USE_FLASH_TOOLS
6050 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6051 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6052 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6053 #endif
6054 #endif
6055 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6056 #ifdef USE_GPS
6057 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6058 #endif
6059 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6060 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6061 #endif
6062 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
6063 #ifdef USE_LED_STRIP_STATUS_MODE
6064 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6065 #endif
6066 #if defined(USE_BOARD_INFO)
6067 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6068 #endif
6069 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6070 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6071 #ifndef USE_QUAD_MIXER_ONLY
6072 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6073 #endif
6074 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6075 #ifdef USE_LED_STRIP_STATUS_MODE
6076 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6077 #endif
6078 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6079 #ifdef USE_USB_MSC
6080 #ifdef USE_RTC_TIME
6081 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6082 #else
6083 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6084 #endif
6085 #endif
6086 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
6087 #ifndef MINIMAL_CLI
6088 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6089 #endif
6090 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6091 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6092 #ifdef USE_RC_SMOOTHING_FILTER
6093 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6094 #endif // USE_RC_SMOOTHING_FILTER
6095 #ifdef USE_RESOURCE_MGMT
6096 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6097 #endif
6098 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6099 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6100 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
6101 #ifdef USE_SDCARD
6102 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6103 #endif
6104 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6105 #if defined(USE_SERIAL_PASSTHROUGH)
6106 #if defined(USE_PINIO)
6107 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode>1] [none|<dtr pinio>|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6108 #else
6109 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6110 #endif
6111 #endif
6112 #ifdef USE_SERVOS
6113 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6114 #endif
6115 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6116 #if defined(USE_SIGNATURE)
6117 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6118 #endif
6119 #ifdef USE_SERVOS
6120 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6121 "\treset\r\n"
6122 "\tload <mixer>\r\n"
6123 "\treverse <servo> <source> r|n", cliServoMix),
6124 #endif
6125 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6126 #if defined(USE_TASK_STATISTICS)
6127 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6128 #endif
6129 #ifdef USE_TIMER_MGMT
6130 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6131 #endif
6132 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6133 #ifdef USE_VTX_CONTROL
6134 #ifdef MINIMAL_CLI
6135 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6136 #else
6137 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6138 #endif
6139 #endif
6140 #ifdef USE_VTX_TABLE
6141 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6142 #endif
6145 static void cliHelp(char *cmdline)
6147 UNUSED(cmdline);
6149 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6150 cliPrint(cmdTable[i].name);
6151 #ifndef MINIMAL_CLI
6152 if (cmdTable[i].description) {
6153 cliPrintf(" - %s", cmdTable[i].description);
6155 if (cmdTable[i].args) {
6156 cliPrintf("\r\n\t%s", cmdTable[i].args);
6158 #endif
6159 cliPrintLinefeed();
6163 void cliProcess(void)
6165 if (!cliWriter) {
6166 return;
6169 // Be a little bit tricky. Flush the last inputs buffer, if any.
6170 bufWriterFlush(cliWriter);
6172 while (serialRxBytesWaiting(cliPort)) {
6173 uint8_t c = serialRead(cliPort);
6174 if (c == '\t' || c == '?') {
6175 // do tab completion
6176 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6177 uint32_t i = bufferIndex;
6178 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6179 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
6180 continue;
6181 if (!pstart)
6182 pstart = cmd;
6183 pend = cmd;
6185 if (pstart) { /* Buffer matches one or more commands */
6186 for (; ; bufferIndex++) {
6187 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6188 break;
6189 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6190 /* Unambiguous -- append a space */
6191 cliBuffer[bufferIndex++] = ' ';
6192 cliBuffer[bufferIndex] = '\0';
6193 break;
6195 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6198 if (!bufferIndex || pstart != pend) {
6199 /* Print list of ambiguous matches */
6200 cliPrint("\r\033[K");
6201 for (cmd = pstart; cmd <= pend; cmd++) {
6202 cliPrint(cmd->name);
6203 cliWrite('\t');
6205 cliPrompt();
6206 i = 0; /* Redraw prompt */
6208 for (; i < bufferIndex; i++)
6209 cliWrite(cliBuffer[i]);
6210 } else if (!bufferIndex && c == 4) { // CTRL-D
6211 cliExit(cliBuffer);
6212 return;
6213 } else if (c == 12) { // NewPage / CTRL-L
6214 // clear screen
6215 cliPrint("\033[2J\033[1;1H");
6216 cliPrompt();
6217 } else if (bufferIndex && (c == '\n' || c == '\r')) {
6218 // enter pressed
6219 cliPrintLinefeed();
6221 // Strip comment starting with # from line
6222 char *p = cliBuffer;
6223 p = strchr(p, '#');
6224 if (NULL != p) {
6225 bufferIndex = (uint32_t)(p - cliBuffer);
6228 // Strip trailing whitespace
6229 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6230 bufferIndex--;
6233 // Process non-empty lines
6234 if (bufferIndex > 0) {
6235 cliBuffer[bufferIndex] = 0; // null terminate
6237 const clicmd_t *cmd;
6238 char *options;
6239 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6240 if ((options = checkCommand(cliBuffer, cmd->name))) {
6241 break;
6244 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6245 cmd->func(options);
6246 } else {
6247 cliPrintError("UNKNOWN COMMAND, TRY 'HELP'");
6249 bufferIndex = 0;
6252 memset(cliBuffer, 0, sizeof(cliBuffer));
6254 // 'exit' will reset this flag, so we don't need to print prompt again
6255 if (!cliMode)
6256 return;
6258 cliPrompt();
6259 } else if (c == 127) {
6260 // backspace
6261 if (bufferIndex) {
6262 cliBuffer[--bufferIndex] = 0;
6263 cliPrint("\010 \010");
6265 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6266 if (!bufferIndex && c == ' ')
6267 continue; // Ignore leading spaces
6268 cliBuffer[bufferIndex++] = c;
6269 cliWrite(c);
6274 void cliEnter(serialPort_t *serialPort)
6276 cliMode = 1;
6277 cliPort = serialPort;
6278 setPrintfSerialPort(cliPort);
6279 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6281 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
6283 #ifndef MINIMAL_CLI
6284 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6285 #else
6286 cliPrintLine("\r\nCLI");
6287 #endif
6288 setArmingDisabled(ARMING_DISABLED_CLI);
6290 cliPrompt();
6292 #ifdef USE_CLI_BATCH
6293 resetCommandBatch();
6294 #endif
6297 void cliInit(const serialConfig_t *serialConfig)
6299 UNUSED(serialConfig);
6301 #endif // USE_CLI