duplicate emptyline removal (#14027)
[betaflight.git] / src / main / cli / cli.c
blob1dff695952efe5cccf8d43bf4013faabff4a94fb
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 bool cliMode = false;
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.h"
58 #include "config/config_eeprom.h"
59 #include "config/feature.h"
60 #include "config/simplified_tuning.h"
62 #include "drivers/accgyro/accgyro.h"
63 #include "drivers/adc.h"
64 #include "drivers/buf_writer.h"
65 #include "drivers/bus_i2c.h"
66 #include "drivers/bus_spi.h"
67 #include "drivers/dma.h"
68 #include "drivers/dma_reqmap.h"
69 #include "drivers/dshot.h"
70 #include "drivers/dshot_command.h"
71 #include "drivers/dshot_dpwm.h"
72 #include "drivers/pwm_output_dshot_shared.h"
73 #include "drivers/camera_control_impl.h"
74 #include "drivers/compass/compass.h"
75 #include "drivers/display.h"
76 #include "drivers/dma.h"
77 #include "drivers/flash/flash.h"
78 #include "drivers/inverter.h"
79 #include "drivers/io.h"
80 #include "drivers/io_impl.h"
81 #include "drivers/light_led.h"
82 #include "drivers/motor.h"
83 #include "drivers/rangefinder/rangefinder_hcsr04.h"
84 #include "drivers/resource.h"
85 #include "drivers/sdcard.h"
86 #include "drivers/sensor.h"
87 #include "drivers/serial.h"
88 #include "drivers/serial_escserial.h"
89 #include "drivers/sound_beeper.h"
90 #include "drivers/stack_check.h"
91 #include "drivers/system.h"
92 #include "drivers/time.h"
93 #include "drivers/timer.h"
94 #include "drivers/transponder_ir.h"
95 #include "drivers/usb_msc.h"
96 #include "drivers/vtx_common.h"
97 #include "drivers/vtx_table.h"
99 #include "fc/board_info.h"
100 #include "fc/controlrate_profile.h"
101 #include "fc/core.h"
102 #include "fc/rc.h"
103 #include "fc/rc_adjustments.h"
104 #include "fc/rc_controls.h"
105 #include "fc/runtime_config.h"
107 #include "flight/autopilot.h"
108 #include "flight/failsafe.h"
109 #include "flight/imu.h"
110 #include "flight/mixer.h"
111 #include "flight/pid.h"
112 #include "flight/position.h"
113 #include "flight/servos.h"
115 #include "io/asyncfatfs/asyncfatfs.h"
116 #include "io/beeper.h"
117 #include "io/flashfs.h"
118 #include "io/gimbal.h"
119 #include "io/gps.h"
120 #include "io/ledstrip.h"
121 #include "io/serial.h"
122 #include "io/transponder_ir.h"
123 #include "io/usb_msc.h"
124 #include "io/vtx_control.h"
125 #include "io/vtx.h"
127 #include "msp/msp.h"
128 #include "msp/msp_box.h"
129 #include "msp/msp_protocol.h"
131 #include "osd/osd.h"
133 #include "pg/adc.h"
134 #include "pg/beeper.h"
135 #include "pg/beeper_dev.h"
136 #include "pg/board.h"
137 #include "pg/bus_i2c.h"
138 #include "pg/bus_spi.h"
139 #include "pg/gyrodev.h"
140 #include "pg/max7456.h"
141 #include "pg/mco.h"
142 #include "pg/motor.h"
143 #include "pg/pinio.h"
144 #include "pg/pin_pull_up_down.h"
145 #include "pg/pg.h"
146 #include "pg/pg_ids.h"
147 #include "pg/rx.h"
148 #include "pg/rx_pwm.h"
149 #include "pg/rx_spi_cc2500.h"
150 #include "pg/rx_spi_expresslrs.h"
151 #include "pg/serial_uart.h"
152 #include "pg/sdio.h"
153 #include "pg/timerio.h"
154 #include "pg/timerup.h"
155 #include "pg/usb.h"
156 #include "pg/vtx_table.h"
158 #include "rx/rx_bind.h"
159 #include "rx/rx_spi.h"
161 #include "scheduler/scheduler.h"
163 #include "sensors/acceleration.h"
164 #include "sensors/adcinternal.h"
165 #include "sensors/barometer.h"
166 #include "sensors/battery.h"
167 #include "sensors/boardalignment.h"
168 #include "sensors/compass.h"
169 #include "sensors/gyro.h"
170 #include "sensors/gyro_init.h"
171 #include "sensors/sensors.h"
173 #include "telemetry/frsky_hub.h"
174 #include "telemetry/telemetry.h"
176 #include "cli.h"
178 static serialPort_t *cliPort = NULL;
179 static bool cliInteractive = false;
180 static timeMs_t cliEntryTime = 0;
182 // Space required to set array parameters
183 #define CLI_IN_BUFFER_SIZE 256
184 #define CLI_OUT_BUFFER_SIZE 64
186 static bufWriter_t cliWriterDesc;
187 static bufWriter_t *cliWriter = NULL;
188 static bufWriter_t *cliErrorWriter = NULL;
189 static uint8_t cliWriteBuffer[CLI_OUT_BUFFER_SIZE];
191 static char cliBuffer[CLI_IN_BUFFER_SIZE];
192 static uint32_t bufferIndex = 0;
194 static bool configIsInCopy = false;
196 #define CURRENT_PROFILE_INDEX -1
197 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
198 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
200 #ifdef USE_CLI_BATCH
201 static bool commandBatchActive = false;
202 static bool commandBatchError = false;
203 #endif
205 #if defined(USE_BOARD_INFO)
206 static bool boardInformationUpdated = false;
207 #if defined(USE_SIGNATURE)
208 static bool signatureUpdated = false;
209 #endif
210 #endif // USE_BOARD_INFO
212 static const char* const emptyName = "-";
214 #define MAX_CHANGESET_ID_LENGTH 8
215 #define MAX_DATE_LENGTH 20
217 #define ERROR_INVALID_NAME "INVALID NAME: %s"
218 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
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 #define _R(_flag, _name) [LOG2(_flag)] = _name
234 static const char * const featureNames[] = {
235 _R(FEATURE_RX_PPM, "RX_PPM"),
236 _R(FEATURE_INFLIGHT_ACC_CAL, "INFLIGHT_ACC_CAL"),
237 _R(FEATURE_RX_SERIAL, "RX_SERIAL"),
238 _R(FEATURE_MOTOR_STOP, "MOTOR_STOP"),
239 _R(FEATURE_SERVO_TILT, "SERVO_TILT"),
240 _R(FEATURE_SOFTSERIAL, "SOFTSERIAL"),
241 _R(FEATURE_GPS, "GPS"),
242 _R(FEATURE_RANGEFINDER, "RANGEFINDER"),
243 _R(FEATURE_TELEMETRY, "TELEMETRY"),
244 _R(FEATURE_3D, "3D"),
245 _R(FEATURE_RX_PARALLEL_PWM, "RX_PARALLEL_PWM"),
246 _R(FEATURE_RSSI_ADC, "RSSI_ADC"),
247 _R(FEATURE_LED_STRIP, "LED_STRIP"),
248 _R(FEATURE_DASHBOARD, "DISPLAY"),
249 _R(FEATURE_OSD, "OSD"),
250 _R(FEATURE_CHANNEL_FORWARDING, "CHANNEL_FORWARDING"),
251 _R(FEATURE_TRANSPONDER, "TRANSPONDER"),
252 _R(FEATURE_AIRMODE, "AIRMODE"),
253 _R(FEATURE_RX_SPI, "RX_SPI"),
254 _R(FEATURE_ESC_SENSOR, "ESC_SENSOR"),
255 _R(FEATURE_ANTI_GRAVITY, "ANTI_GRAVITY"),
257 #undef _R
259 // sync this with rxFailsafeChannelMode_e
260 static const char rxFailsafeModeCharacters[] = "ahs";
262 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
263 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
264 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
267 #if defined(USE_SENSOR_NAMES)
268 // sync this with sensors_e
269 static const char *const sensorTypeNames[] = {
270 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
273 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
275 static const char * const *sensorHardwareNames[] = {
276 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
278 #endif // USE_SENSOR_NAMES
280 // Needs to be aligned with mcuTypeId_e
281 static const char *mcuTypeNames[] = {
282 "SIMULATOR",
283 "F40X",
284 "F411",
285 "F446",
286 "F722",
287 "F745",
288 "F746",
289 "F765",
290 "H750",
291 "H743 (Rev Unknown)",
292 "H743 (Rev.Y)",
293 "H743 (Rev.X)",
294 "H743 (Rev.V)",
295 "H7A3",
296 "H723/H725",
297 "G474",
298 "H730",
299 "AT32F435",
300 "APM32F405",
301 "APM32F407",
304 static const char *configurationStates[] = {
305 [CONFIGURATION_STATE_UNCONFIGURED] = "UNCONFIGURED",
306 [CONFIGURATION_STATE_CONFIGURED] = "CONFIGURED"
309 typedef enum dumpFlags_e {
310 DUMP_MASTER = (1 << 0),
311 DUMP_PROFILE = (1 << 1),
312 DUMP_RATES = (1 << 2),
313 DUMP_ALL = (1 << 3),
314 DO_DIFF = (1 << 4),
315 SHOW_DEFAULTS = (1 << 5),
316 HIDE_UNUSED = (1 << 6),
317 HARDWARE_ONLY = (1 << 7),
318 BARE = (1 << 8),
319 } dumpFlags_t;
321 static void cliExit(const bool reboot);
322 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
324 typedef enum {
325 REBOOT_TARGET_FIRMWARE,
326 REBOOT_TARGET_BOOTLOADER_ROM,
327 REBOOT_TARGET_BOOTLOADER_FLASH,
328 } rebootTarget_e;
330 typedef struct serialPassthroughPort_s {
331 serialPortIdentifier_e id;
332 uint32_t baud;
333 portMode_e mode;
334 portOptions_e options;
335 serialPort_t *port;
336 } serialPassthroughPort_t;
338 static void cliClearInputBuffer(void)
340 memset(cliBuffer, 0, sizeof(cliBuffer));
341 bufferIndex = 0;
344 static void cliWriterFlushInternal(bufWriter_t *writer)
346 if (writer) {
347 bufWriterFlush(writer);
351 static void cliPrintInternal(bufWriter_t *writer, const char *str)
353 if (writer) {
354 while (*str) {
355 bufWriterAppend(writer, *str++);
357 cliWriterFlushInternal(writer);
361 static void cliWriterFlush(void)
363 cliWriterFlushInternal(cliWriter);
366 void cliPrint(const char *str)
368 cliPrintInternal(cliWriter, str);
371 void cliPrintLinefeed(void)
373 cliPrint("\r\n");
376 void cliPrintLine(const char *str)
378 cliPrint(str);
379 cliPrintLinefeed();
382 #ifdef MINIMAL_CLI
383 #define cliPrintHashLine(str)
384 #else
385 static void cliPrintHashLine(const char *str)
387 cliPrint("\r\n# ");
388 cliPrintLine(str);
390 #endif
392 static void cliPutp(void *p, char ch)
394 bufWriterAppend(p, ch);
397 static void cliPrintfva(const char *format, va_list va)
399 if (cliWriter) {
400 tfp_format(cliWriter, cliPutp, format, va);
401 cliWriterFlush();
405 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
407 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
408 va_list va;
409 va_start(va, format);
410 cliPrintfva(format, va);
411 va_end(va);
412 cliPrintLinefeed();
413 return true;
414 } else {
415 return false;
419 static void cliWrite(uint8_t ch)
421 if (cliWriter) {
422 bufWriterAppend(cliWriter, ch);
426 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
428 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
429 cliWrite('#');
431 va_list va;
432 va_start(va, format);
433 cliPrintfva(format, va);
434 va_end(va);
435 cliPrintLinefeed();
436 return true;
437 } else {
438 return false;
442 void cliPrintf(const char *format, ...)
444 va_list va;
445 va_start(va, format);
446 cliPrintfva(format, va);
447 va_end(va);
450 void cliPrintLinef(const char *format, ...)
452 va_list va;
453 va_start(va, format);
454 cliPrintfva(format, va);
455 va_end(va);
456 cliPrintLinefeed();
459 static void cliPrintErrorVa(const char *cmdName, const char *format, va_list va)
461 if (cliErrorWriter) {
462 cliPrintInternal(cliErrorWriter, "###ERROR IN ");
463 cliPrintInternal(cliErrorWriter, cmdName);
464 cliPrintInternal(cliErrorWriter, ": ");
466 tfp_format(cliErrorWriter, cliPutp, format, va);
467 va_end(va);
469 cliPrintInternal(cliErrorWriter, "###");
472 #ifdef USE_CLI_BATCH
473 if (commandBatchActive) {
474 commandBatchError = true;
476 #endif
479 static void cliPrintError(const char *cmdName, const char *format, ...)
481 va_list va;
482 va_start(va, format);
483 cliPrintErrorVa(cmdName, format, va);
484 va_end(va);
486 if (!cliWriter) {
487 // Supply our own linefeed in case we are printing inside a custom defaults operation
488 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
489 // instead of expecting the directly following command to supply the line feed.
490 cliPrintInternal(cliErrorWriter, "\r\n");
494 static void cliPrintErrorLinef(const char *cmdName, const char *format, ...)
496 va_list va;
497 va_start(va, format);
498 cliPrintErrorVa(cmdName, format, va);
499 va_end(va);
500 cliPrintInternal(cliErrorWriter, "\r\n");
503 static void getMinMax(const clivalue_t *var, int *min, int *max)
505 switch (var->type & VALUE_TYPE_MASK) {
506 case VAR_UINT8:
507 case VAR_UINT16:
508 *min = var->config.minmaxUnsigned.min;
509 *max = var->config.minmaxUnsigned.max;
511 break;
512 default:
513 *min = var->config.minmax.min;
514 *max = var->config.minmax.max;
516 break;
520 static void printValuePointer(const char *cmdName, const clivalue_t *var, const void *valuePointer, bool full)
522 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
523 for (int i = 0; i < var->config.array.length; i++) {
524 switch (var->type & VALUE_TYPE_MASK) {
525 default:
526 case VAR_UINT8:
527 // uint8_t array
528 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
529 break;
531 case VAR_INT8:
532 // int8_t array
533 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
534 break;
536 case VAR_UINT16:
537 // uin16_t array
538 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
539 break;
541 case VAR_INT16:
542 // int16_t array
543 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
544 break;
546 case VAR_UINT32:
547 // uin32_t array
548 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
549 break;
551 case VAR_INT32:
552 // in32_t array
553 cliPrintf("%d", ((int32_t *)valuePointer)[i]);
554 break;
557 if (i < var->config.array.length - 1) {
558 cliPrint(",");
561 } else {
562 int value = 0;
564 switch (var->type & VALUE_TYPE_MASK) {
565 case VAR_UINT8:
566 value = *(uint8_t *)valuePointer;
568 break;
569 case VAR_INT8:
570 value = *(int8_t *)valuePointer;
572 break;
573 case VAR_UINT16:
574 value = *(uint16_t *)valuePointer;
576 break;
577 case VAR_INT16:
578 value = *(int16_t *)valuePointer;
580 break;
581 case VAR_UINT32:
582 value = *(uint32_t *)valuePointer;
584 break;
585 case VAR_INT32:
586 value = *(int32_t *)valuePointer;
588 break;
591 bool valueIsCorrupted = false;
592 switch (var->type & VALUE_MODE_MASK) {
593 case MODE_DIRECT:
594 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
595 cliPrintf("%u", (uint32_t)value);
596 if ((uint32_t)value > var->config.u32Max) {
597 valueIsCorrupted = true;
598 } else if (full) {
599 cliPrintf(" 0 %u", var->config.u32Max);
601 } else if ((var->type & VALUE_TYPE_MASK) == VAR_INT32) {
602 cliPrintf("%d", (int32_t)value);
603 if ((int32_t)value > var->config.d32Max || (int32_t)value < -var->config.d32Max) {
604 valueIsCorrupted = true;
605 } else if (full) {
606 cliPrintf(" 0 %u", var->config.u32Max);
608 } else {
609 int min;
610 int max;
611 getMinMax(var, &min, &max);
613 cliPrintf("%d", value);
614 if ((value < min) || (value > max)) {
615 valueIsCorrupted = true;
616 } else if (full) {
617 cliPrintf(" %d %d", min, max);
620 break;
621 case MODE_LOOKUP:
622 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
623 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
624 } else {
625 valueIsCorrupted = true;
627 break;
628 case MODE_BITSET:
629 if (value & 1 << var->config.bitpos) {
630 cliPrintf("ON");
631 } else {
632 cliPrintf("OFF");
634 break;
635 case MODE_STRING:
636 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
637 break;
640 if (valueIsCorrupted) {
641 cliPrintLinefeed();
642 cliPrintError(cmdName, "CORRUPTED CONFIG: %s = %d", var->name, value);
647 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
649 bool result = true;
650 int elementCount = 1;
651 uint32_t mask = 0xffffffff;
653 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
654 elementCount = var->config.array.length;
656 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
657 mask = 1 << var->config.bitpos;
659 for (int i = 0; i < elementCount; i++) {
660 switch (var->type & VALUE_TYPE_MASK) {
661 case VAR_UINT8:
662 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
663 break;
665 case VAR_INT8:
666 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
667 break;
669 case VAR_UINT16:
670 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
671 break;
672 case VAR_INT16:
673 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
674 break;
675 case VAR_UINT32:
676 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
677 break;
678 case VAR_INT32:
679 result = result && (((int32_t *)ptr)[i] & mask) == (((int32_t *)ptrDefault)[i] & mask);
680 break;
684 return result;
687 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
689 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
690 cliPrintHashLine(headingStr);
691 return NULL;
692 } else {
693 return headingStr;
697 static void backupPgConfig(const pgRegistry_t *pg)
699 memcpy(pg->copy, pg->address, pg->size);
702 static void restorePgConfig(const pgRegistry_t *pg, uint16_t notToRestoreGroupId)
704 if (!notToRestoreGroupId || pgN(pg) != notToRestoreGroupId) {
705 memcpy(pg->address, pg->copy, pg->size);
709 static void backupConfigs(void)
711 if (configIsInCopy) {
712 return;
715 PG_FOREACH(pg) {
716 backupPgConfig(pg);
719 configIsInCopy = true;
722 static void restoreConfigs(uint16_t notToRestoreGroupId)
724 if (!configIsInCopy) {
725 return;
728 PG_FOREACH(pg) {
729 restorePgConfig(pg, notToRestoreGroupId);
732 configIsInCopy = false;
735 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
736 static bool isReadingConfigFromCopy(void)
738 return configIsInCopy;
740 #endif
742 static bool isWritingConfigToCopy(void)
744 return configIsInCopy;
747 static void backupAndResetConfigs(void)
749 backupConfigs();
750 // reset all configs to defaults to do differencing
751 resetConfig();
754 static uint8_t getPidProfileIndexToUse(void)
756 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
759 static uint8_t getRateProfileIndexToUse(void)
761 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
764 static uint16_t getValueOffset(const clivalue_t *value)
766 switch (value->type & VALUE_SECTION_MASK) {
767 case MASTER_VALUE:
768 case HARDWARE_VALUE:
769 return value->offset;
770 case PROFILE_VALUE:
771 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
772 case PROFILE_RATE_VALUE:
773 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
775 return 0;
778 STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
780 const pgRegistry_t* rec = pgFind(value->pgn);
781 if (isWritingConfigToCopy()) {
782 return CONST_CAST(void *, rec->copy + getValueOffset(value));
783 } else {
784 return CONST_CAST(void *, rec->address + getValueOffset(value));
788 static const char *dumpPgValue(const char *cmdName, const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
790 const pgRegistry_t *pg = pgFind(value->pgn);
791 #ifdef DEBUG
792 if (!pg) {
793 cliPrintLinef("VALUE %s ERROR", value->name);
794 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
796 #endif
798 const char *format = "set %s = ";
799 const char *defaultFormat = "#set %s = ";
800 const int valueOffset = getValueOffset(value);
801 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
803 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
804 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
805 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
806 cliPrintf(defaultFormat, value->name);
807 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
808 cliPrintLinefeed();
810 cliPrintf(format, value->name);
811 printValuePointer(cmdName, value, pg->copy + valueOffset, false);
812 cliPrintLinefeed();
814 return headingStr;
817 static void dumpAllValues(const char *cmdName, uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
819 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
821 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
822 const clivalue_t *value = &valueTable[i];
823 cliWriterFlush();
824 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
825 headingStr = dumpPgValue(cmdName, value, dumpMask, headingStr);
830 static void cliPrintVar(const char *cmdName, const clivalue_t *var, bool full)
832 const void *ptr = cliGetValuePointer(var);
834 printValuePointer(cmdName, var, ptr, full);
837 static void cliPrintVarRange(const clivalue_t *var)
839 switch (var->type & VALUE_MODE_MASK) {
840 case (MODE_DIRECT): {
841 switch (var->type & VALUE_TYPE_MASK) {
842 case VAR_UINT32:
843 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
845 break;
846 case VAR_INT32:
847 cliPrintLinef("Allowed range: %d - %d", -var->config.d32Max, var->config.d32Max);
849 break;
850 case VAR_UINT8:
851 case VAR_UINT16:
852 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
854 break;
855 default:
856 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
858 break;
861 break;
862 case (MODE_LOOKUP): {
863 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
864 cliPrint("Allowed values: ");
865 bool firstEntry = true;
866 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
867 if (tableEntry->values[i]) {
868 if (!firstEntry) {
869 cliPrint(", ");
871 cliPrintf("%s", tableEntry->values[i]);
872 firstEntry = false;
875 cliPrintLinefeed();
877 break;
878 case (MODE_ARRAY): {
879 cliPrintLinef("Array length: %d", var->config.array.length);
881 break;
882 case (MODE_STRING): {
883 cliPrintLinef("String length: %d - %d", var->config.string.minlength, var->config.string.maxlength);
885 break;
886 case (MODE_BITSET): {
887 cliPrintLinef("Allowed values: OFF, ON");
889 break;
893 static void cliSetVar(const clivalue_t *var, const uint32_t value)
895 void *ptr = cliGetValuePointer(var);
896 uint32_t workValue;
897 uint32_t mask;
899 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
900 switch (var->type & VALUE_TYPE_MASK) {
901 case VAR_UINT8:
902 mask = (1 << var->config.bitpos) & 0xff;
903 if (value) {
904 workValue = *(uint8_t *)ptr | mask;
905 } else {
906 workValue = *(uint8_t *)ptr & ~mask;
908 *(uint8_t *)ptr = workValue;
909 break;
911 case VAR_UINT16:
912 mask = (1 << var->config.bitpos) & 0xffff;
913 if (value) {
914 workValue = *(uint16_t *)ptr | mask;
915 } else {
916 workValue = *(uint16_t *)ptr & ~mask;
918 *(uint16_t *)ptr = workValue;
919 break;
921 case VAR_UINT32:
922 mask = 1 << var->config.bitpos;
923 if (value) {
924 workValue = *(uint32_t *)ptr | mask;
925 } else {
926 workValue = *(uint32_t *)ptr & ~mask;
928 *(uint32_t *)ptr = workValue;
929 break;
931 case VAR_INT32:
932 mask = 1 << var->config.bitpos;
933 if (value) {
934 workValue = *(int32_t *)ptr | mask;
935 } else {
936 workValue = *(int32_t *)ptr & ~mask;
938 *(int32_t *)ptr = workValue;
939 break;
941 } else {
942 switch (var->type & VALUE_TYPE_MASK) {
943 case VAR_UINT8:
944 *(uint8_t *)ptr = value;
945 break;
947 case VAR_INT8:
948 *(int8_t *)ptr = value;
949 break;
951 case VAR_UINT16:
952 *(uint16_t *)ptr = value;
953 break;
955 case VAR_INT16:
956 *(int16_t *)ptr = value;
957 break;
959 case VAR_UINT32:
960 *(uint32_t *)ptr = value;
961 break;
963 case VAR_INT32:
964 *(int32_t *)ptr = value;
965 break;
970 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
971 static void cliRepeat(char ch, uint8_t len)
973 if (cliWriter) {
974 for (int i = 0; i < len; i++) {
975 bufWriterAppend(cliWriter, ch);
977 cliPrintLinefeed();
980 #endif
982 static void cliPrompt(void)
984 cliPrint("\r\n# ");
987 static void cliShowParseError(const char *cmdName)
989 cliPrintErrorLinef(cmdName, "PARSING FAILED");
992 static void cliShowInvalidArgumentCountError(const char *cmdName)
994 cliPrintErrorLinef(cmdName, "INVALID ARGUMENT COUNT");
997 static void cliShowArgumentRangeError(const char *cmdName, char *name, int min, int max)
999 if (name) {
1000 cliPrintErrorLinef(cmdName, "%s NOT BETWEEN %d AND %d", name, min, max);
1001 } else {
1002 cliPrintErrorLinef(cmdName, "ARGUMENT OUT OF RANGE");
1006 static const char *nextArg(const char *currentArg)
1008 const char *ptr = strchr(currentArg, ' ');
1009 while (ptr && *ptr == ' ') {
1010 ptr++;
1013 return ptr;
1016 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
1018 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
1019 ptr = nextArg(ptr);
1020 if (ptr) {
1021 int val = atoi(ptr);
1022 val = CHANNEL_VALUE_TO_STEP(val);
1023 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
1024 if (argIndex == 0) {
1025 range->startStep = val;
1026 } else {
1027 range->endStep = val;
1029 (*validArgumentCount)++;
1034 return ptr;
1037 // Check if a string's length is zero
1038 static bool isEmpty(const char *string)
1040 return (string == NULL || *string == '\0') ? true : false;
1043 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
1045 // print out rxConfig failsafe settings
1046 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1047 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1048 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
1049 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
1050 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
1051 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1052 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1053 if (requireValue) {
1054 const char *format = "rxfail %u %c %d";
1055 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1056 channel,
1057 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
1058 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
1060 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1061 channel,
1062 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
1063 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1065 } else {
1066 const char *format = "rxfail %u %c";
1067 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1068 channel,
1069 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
1071 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1072 channel,
1073 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
1079 static void cliRxFailsafe(const char *cmdName, char *cmdline)
1081 uint8_t channel;
1082 char buf[3];
1084 if (isEmpty(cmdline)) {
1085 // print out rxConfig failsafe settings
1086 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1087 cliRxFailsafe(cmdName, itoa(channel, buf, 10));
1089 } else {
1090 const char *ptr = cmdline;
1091 channel = atoi(ptr++);
1092 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
1094 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
1096 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
1097 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
1098 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1100 ptr = nextArg(ptr);
1101 if (ptr) {
1102 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
1103 if (p) {
1104 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
1105 mode = rxFailsafeModesTable[type][requestedMode];
1106 } else {
1107 mode = RX_FAILSAFE_MODE_INVALID;
1109 if (mode == RX_FAILSAFE_MODE_INVALID) {
1110 cliShowParseError(cmdName);
1111 return;
1114 requireValue = mode == RX_FAILSAFE_MODE_SET;
1116 ptr = nextArg(ptr);
1117 if (ptr) {
1118 if (!requireValue) {
1119 cliShowParseError(cmdName);
1120 return;
1122 uint16_t value = atoi(ptr);
1123 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
1124 if (value > MAX_RXFAIL_RANGE_STEP) {
1125 cliPrintErrorLinef(cmdName, "value out of range: %d", value);
1126 return;
1129 channelFailsafeConfig->step = value;
1130 } else if (requireValue) {
1131 cliShowInvalidArgumentCountError(cmdName);
1132 return;
1134 channelFailsafeConfig->mode = mode;
1137 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
1139 // double use of cliPrintf below
1140 // 1. acknowledge interpretation on command,
1141 // 2. query current setting on single item,
1143 if (requireValue) {
1144 cliPrintLinef("rxfail %u %c %d",
1145 channel,
1146 modeCharacter,
1147 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1149 } else {
1150 cliPrintLinef("rxfail %u %c",
1151 channel,
1152 modeCharacter
1155 } else {
1156 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1161 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1163 const char *format = "aux %u %u %u %u %u %u %u";
1164 // print out aux channel settings
1165 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1166 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1167 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1168 bool equalsDefault = false;
1169 if (defaultModeActivationConditions) {
1170 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1171 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1172 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1173 const box_t *box = findBoxByBoxId(macDefault->modeId);
1174 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1175 if (box) {
1176 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1178 box->permanentId,
1179 macDefault->auxChannelIndex,
1180 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1181 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1182 macDefault->modeLogic,
1183 linkedTo ? linkedTo->permanentId : 0
1187 const box_t *box = findBoxByBoxId(mac->modeId);
1188 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1189 if (box) {
1190 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1192 box->permanentId,
1193 mac->auxChannelIndex,
1194 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1195 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1196 mac->modeLogic,
1197 linkedTo ? linkedTo->permanentId : 0
1203 static void cliAux(const char *cmdName, char *cmdline)
1205 int i, val = 0;
1206 const char *ptr;
1208 if (isEmpty(cmdline)) {
1209 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1210 } else {
1211 ptr = cmdline;
1212 i = atoi(ptr++);
1213 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1214 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1215 uint8_t validArgumentCount = 0;
1216 ptr = nextArg(ptr);
1217 if (ptr) {
1218 val = atoi(ptr);
1219 const box_t *box = findBoxByPermanentId(val);
1220 if (box) {
1221 mac->modeId = box->boxId;
1222 validArgumentCount++;
1225 ptr = nextArg(ptr);
1226 if (ptr) {
1227 val = atoi(ptr);
1228 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1229 mac->auxChannelIndex = val;
1230 validArgumentCount++;
1233 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1234 ptr = nextArg(ptr);
1235 if (ptr) {
1236 val = atoi(ptr);
1237 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1238 mac->modeLogic = val;
1239 validArgumentCount++;
1242 ptr = nextArg(ptr);
1243 if (ptr) {
1244 val = atoi(ptr);
1245 const box_t *box = findBoxByPermanentId(val);
1246 if (box) {
1247 mac->linkedTo = box->boxId;
1248 validArgumentCount++;
1251 if (validArgumentCount == 4) { // for backwards compatibility
1252 mac->modeLogic = MODELOGIC_OR;
1253 mac->linkedTo = 0;
1254 } else if (validArgumentCount == 5) { // for backwards compatibility
1255 mac->linkedTo = 0;
1256 } else if (validArgumentCount != 6) {
1257 memset(mac, 0, sizeof(modeActivationCondition_t));
1259 analyzeModeActivationConditions();
1260 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1262 findBoxByBoxId(mac->modeId)->permanentId,
1263 mac->auxChannelIndex,
1264 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1265 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1266 mac->modeLogic,
1267 findBoxByBoxId(mac->linkedTo)->permanentId
1269 } else {
1270 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1275 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1277 const char *format = "serial %d %d %ld %ld %ld %ld";
1278 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1279 for (unsigned i = 0; i < ARRAYLEN(serialConfig->portConfigs); i++) {
1280 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1281 continue;
1283 bool equalsDefault = false;
1284 if (serialConfigDefault) {
1285 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1286 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1287 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1288 serialConfigDefault->portConfigs[i].identifier,
1289 serialConfigDefault->portConfigs[i].functionMask,
1290 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1291 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1292 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1293 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1296 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1297 serialConfig->portConfigs[i].identifier,
1298 serialConfig->portConfigs[i].functionMask,
1299 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1300 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1301 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1302 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1307 static void cliSerial(const char *cmdName, char *cmdline)
1309 const char *format = "serial %d %d %ld %ld %ld %ld";
1310 if (isEmpty(cmdline)) {
1311 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1312 return;
1314 serialPortConfig_t portConfig;
1315 memset(&portConfig, 0 , sizeof(portConfig));
1317 uint8_t validArgumentCount = 0;
1319 const char *ptr = cmdline;
1321 int val = atoi(ptr++);
1322 serialPortConfig_t *currentConfig = serialFindPortConfigurationMutable(val);
1324 if (currentConfig) {
1325 portConfig.identifier = val;
1326 validArgumentCount++;
1329 ptr = nextArg(ptr);
1330 if (ptr) {
1331 val = strtoul(ptr, NULL, 10);
1332 portConfig.functionMask = val;
1333 validArgumentCount++;
1336 for (int i = 0; i < 4; i ++) {
1337 ptr = nextArg(ptr);
1338 if (!ptr) {
1339 break;
1342 val = atoi(ptr);
1344 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1345 if (baudRates[baudRateIndex] != (uint32_t) val) {
1346 break;
1349 switch (i) {
1350 case 0:
1351 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1352 continue;
1354 portConfig.msp_baudrateIndex = baudRateIndex;
1355 break;
1356 case 1:
1357 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1358 continue;
1360 portConfig.gps_baudrateIndex = baudRateIndex;
1361 break;
1362 case 2:
1363 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1364 continue;
1366 portConfig.telemetry_baudrateIndex = baudRateIndex;
1367 break;
1368 case 3:
1369 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1370 continue;
1372 portConfig.blackbox_baudrateIndex = baudRateIndex;
1373 break;
1376 validArgumentCount++;
1379 if (validArgumentCount < 6) {
1380 cliShowInvalidArgumentCountError(cmdName);
1381 return;
1384 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1386 cliDumpPrintLinef(0, false, format,
1387 portConfig.identifier,
1388 portConfig.functionMask,
1389 baudRates[portConfig.msp_baudrateIndex],
1390 baudRates[portConfig.gps_baudrateIndex],
1391 baudRates[portConfig.telemetry_baudrateIndex],
1392 baudRates[portConfig.blackbox_baudrateIndex]
1397 #if defined(USE_SERIAL_PASSTHROUGH)
1398 static void cbCtrlLine_reset(void *context, uint16_t ctrl)
1400 UNUSED(context);
1401 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1402 systemReset();
1406 #ifdef USE_PINIO
1407 static void cbCtrlLine_pinIO(void *context, uint16_t ctrl)
1409 pinioSet((intptr_t)context, !(ctrl & CTRL_LINE_STATE_DTR));
1411 #endif
1413 static portMode_e cliParseSerialMode(const char *tok)
1415 portMode_e mode = 0;
1417 if (strcasestr(tok, "rx")) {
1418 mode |= MODE_RX;
1420 if (strcasestr(tok, "tx")) {
1421 mode |= MODE_TX;
1424 return mode;
1427 static portOptions_e cliParseSerialOptions(const char *tok)
1429 struct {
1430 const char* tag;
1431 portOptions_e val;
1432 } map[] = {
1433 {"Invert", SERIAL_INVERTED},
1434 {"Stop2", SERIAL_STOPBITS_2},
1435 {"Even", SERIAL_PARITY_EVEN},
1436 {"Bidir", SERIAL_BIDIR},
1437 {"Pushpull", SERIAL_BIDIR_PP},
1438 {"Saudio", SERIAL_PULL_SMARTAUDIO},
1439 {"Check", SERIAL_CHECK_TX},
1441 portOptions_e options = 0;
1442 for (unsigned i = 0; i < ARRAYLEN(map); i++) {
1443 if (strstr(tok, map[i].tag) != 0) {
1444 options |= map[i].val;
1447 return options;
1450 static void cliSerialPassthrough(const char *cmdName, char *cmdline)
1452 if (isEmpty(cmdline)) {
1453 cliShowInvalidArgumentCountError(cmdName);
1454 return;
1457 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, 0, NULL}, {cliPort->identifier, 0, 0, 0, cliPort} };
1458 bool enableBaudCb = false;
1459 #ifdef USE_PINIO
1460 int port1PinioDtr = -1; // route port2 USB DTR to pinio
1461 #endif
1462 bool port1ResetOnDtr = false; // reset board with DTR
1463 bool escSensorPassthrough = false;
1465 char* nexttok = cmdline;
1466 char* tok;
1467 int index = 0;
1468 while ((tok = strsep(&nexttok, " ")) != NULL) {
1469 if (*tok == '\0') { // skip adjacent delimiters
1470 continue;
1472 unsigned portN = (index < 4) ? 0 : 1; // port1 / port2
1473 switch (index) {
1474 case 0: // port1 to open: esc_sensor, portName, port ID port1
1475 case 4: // port2 to use (defaults to CLI serial if no more arguments)
1477 serialPortIdentifier_e portId;
1478 char* endptr;
1479 if (portN == 0 && strcasestr(tok, "esc_sensor") != NULL) {
1480 escSensorPassthrough = true;
1481 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1482 portId = portConfig ? portConfig->identifier : SERIAL_PORT_NONE;
1483 } else if (strcasecmp(tok, "cli") == 0) {
1484 portId = cliPort->identifier;
1485 } else if ((portId = findSerialPortByName(tok, strcasecmp)) >= 0) {
1486 // empty
1487 } else if ((portId = strtol(tok, &endptr, 10)) >= 0 && *endptr == '\0') {
1488 // empty
1489 } else {
1490 cliPrintLinef("Failed parsing port%d (%s)", portN + 1, tok);
1491 return;
1493 if (portN == 1) { // port1 is specified, don't use CLI port
1494 ports[portN].port = NULL;
1496 ports[portN].id = portId;
1497 break;
1499 case 1: // baudrate
1500 case 5: {
1501 int baud = atoi(tok);
1502 ports[portN].baud = baud;
1503 break;
1505 case 2: // port1 mode (rx/tx/rxtx) + options
1506 case 6: // port2 mode + options
1507 ports[portN].mode = cliParseSerialMode(tok);
1508 ports[portN].options = cliParseSerialOptions(tok);
1509 break;
1510 case 3: // DTR action
1511 if (strcasecmp(tok, "reset") == 0) {
1512 port1ResetOnDtr = true;
1513 break;
1515 if (strcasecmp(tok, "none") == 0) {
1516 break;
1518 #ifdef USE_PINIO
1519 port1PinioDtr = atoi(tok);
1520 if (port1PinioDtr < 0 || port1PinioDtr >= PINIO_COUNT) {
1521 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1522 return;
1524 #endif /* USE_PINIO */
1525 break;
1526 default:
1527 cliPrintLinef("Unexpected argument %d (%s)", index + 1, tok);
1528 return;
1530 index++;
1533 for (unsigned i = 0; i < ARRAYLEN(ports); i++) {
1534 if (findSerialPortIndexByIdentifier(ports[i].id) < 0) {
1535 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1536 return;
1537 } else {
1538 cliPrintLinef("Port%d: %s", i + 1, serialName(ports[i].id, "<invalid>"));
1542 // Port checks
1543 if (ports[0].id == ports[1].id) {
1544 cliPrintLinef("Port1 and port2 are same");
1545 return ;
1548 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1549 enableBaudCb = true;
1552 for (int i = 0; i < 2; i++) {
1553 serialPassthroughPort_t* cfg = &ports[i];
1554 if (cfg->port != NULL) { // port already selected, don't touch it (used when port2 defaults to cli)
1555 continue;
1558 int portIndex = i + 1;
1559 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(cfg->id);
1560 if (!portUsage || portUsage->serialPort == NULL) {
1561 // serial port is not open yet
1562 const bool isUseDefaultBaud = cfg->baud == 0;
1563 if (isUseDefaultBaud) {
1564 // Set default baud
1565 cfg->baud = 57600;
1568 if (!cfg->mode) {
1569 cliPrintLinef("Using RXTX mode as default");
1570 cfg->mode = MODE_RXTX;
1573 if (cfg->options) {
1574 cliPrintLinef("Port%d: using options 0x%x",
1575 portIndex, cfg->options);
1577 cfg->port = openSerialPort(cfg->id, FUNCTION_NONE,
1578 NULL, NULL, // rxCallback
1579 cfg->baud, cfg->mode, cfg->options);
1580 if (!cfg->port) {
1581 cliPrintLinef("Port%d could not be opened.", portIndex);
1582 return;
1585 cliPrintf("Port%d opened, %sbaud = %d.\r\n", portIndex, isUseDefaultBaud ? "default ":"", cfg->baud);
1586 } else {
1587 cfg->port = portUsage->serialPort;
1588 // If the user supplied a mode, override the port's mode, otherwise
1589 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1590 // Set the baud rate if specified
1591 if (cfg->baud) {
1592 serialSetBaudRate(cfg->port, cfg->baud);
1594 cliPrintLinef("Port%d is already open, %sbaud = %d.", portIndex, cfg->baud ? "new " : "", cfg->port->baudRate);
1596 if (cfg->mode && cfg->port->mode != cfg->mode) {
1597 cliPrintLinef("Port%d mode changed from %d to %d.",
1598 portIndex, cfg->port->mode, cfg->mode);
1599 serialSetMode(cfg->port, cfg->mode);
1602 if (cfg->options) {
1603 cliPrintLinef("Port%d is open, can't change options from 0x%x to 0x%x",
1604 portIndex, cfg->port->options, cfg->options);
1607 // If this port has a rx callback associated we need to remove it now.
1608 // Otherwise no data will be pushed in the serial port buffer!
1609 if (cfg->port->rxCallback) {
1610 cliPrintLinef("Port%d: Callback removed", portIndex);
1611 cfg->port->rxCallback = NULL;
1616 // If no baud rate is specified allow to be set via USB
1617 if (enableBaudCb) {
1618 cliPrintLine("Port1 baud rate change over USB enabled.");
1619 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1620 // baud rate over USB without setting it using the serialpassthrough command
1621 serialSetBaudRateCb(ports[1].port, serialSetBaudRate, ports[0].port);
1624 const char *resetMessage = "";
1625 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1626 resetMessage = "or drop DTR ";
1629 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1631 if ((ports[1].id == SERIAL_PORT_USB_VCP)) {
1632 do {
1633 if (port1ResetOnDtr) {
1634 serialSetCtrlLineStateCb(ports[1].port, cbCtrlLine_reset, NULL);
1635 break;
1637 #ifdef USE_PINIO
1638 if (port1PinioDtr >= 0) {
1639 serialSetCtrlLineStateCb(ports[1].port, cbCtrlLine_pinIO, (void *)(intptr_t)(port1PinioDtr));
1640 break;
1642 #endif /* USE_PINIO */
1643 } while (0);
1646 // XXX Review ESC pass through under refactored motor handling
1647 #ifdef USE_PWM_OUTPUT
1648 if (escSensorPassthrough) {
1649 // pwmDisableMotors();
1650 motorDisable();
1651 delay(5);
1652 for (unsigned i = 0; i < getMotorCount(); i++) {
1653 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1654 if (tag) {
1655 const timerHardware_t *timerHardware = timerGetConfiguredByTag(tag);
1656 if (timerHardware) {
1657 IO_t io = IOGetByTag(tag);
1658 IOInit(io, OWNER_MOTOR, i);
1659 IOConfigGPIO(io, IOCFG_OUT_PP);
1660 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1661 IOLo(io);
1662 } else {
1663 IOHi(io);
1669 #endif
1671 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1673 #endif
1675 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1677 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1678 // print out adjustment ranges channel settings
1679 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1680 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1681 const adjustmentRange_t *ar = &adjustmentRanges[i];
1682 bool equalsDefault = false;
1683 if (defaultAdjustmentRanges) {
1684 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1685 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1686 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1687 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1689 arDefault->auxChannelIndex,
1690 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1691 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1692 arDefault->adjustmentConfig,
1693 arDefault->auxSwitchChannelIndex,
1694 arDefault->adjustmentCenter,
1695 arDefault->adjustmentScale
1698 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1700 ar->auxChannelIndex,
1701 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1702 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1703 ar->adjustmentConfig,
1704 ar->auxSwitchChannelIndex,
1705 ar->adjustmentCenter,
1706 ar->adjustmentScale
1711 static void cliAdjustmentRange(const char *cmdName, char *cmdline)
1713 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1714 const char *ptr;
1716 if (isEmpty(cmdline)) {
1717 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1718 } else {
1719 ptr = cmdline;
1720 int i = atoi(ptr++);
1721 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1722 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1723 uint8_t validArgumentCount = 0;
1725 ptr = nextArg(ptr);
1726 if (ptr) {
1727 // Was: slot
1728 // Keeping the parameter to retain backwards compatibility for the command format.
1729 validArgumentCount++;
1731 ptr = nextArg(ptr);
1732 if (ptr) {
1733 int val = atoi(ptr);
1734 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1735 ar->auxChannelIndex = val;
1736 validArgumentCount++;
1740 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1742 ptr = nextArg(ptr);
1743 if (ptr) {
1744 int val = atoi(ptr);
1745 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1746 ar->adjustmentConfig = val;
1747 validArgumentCount++;
1750 ptr = nextArg(ptr);
1751 if (ptr) {
1752 int val = atoi(ptr);
1753 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1754 ar->auxSwitchChannelIndex = val;
1755 validArgumentCount++;
1759 if (validArgumentCount != 6) {
1760 memset(ar, 0, sizeof(adjustmentRange_t));
1761 cliShowInvalidArgumentCountError(cmdName);
1762 return;
1765 // Optional arguments
1766 ar->adjustmentCenter = 0;
1767 ar->adjustmentScale = 0;
1769 ptr = nextArg(ptr);
1770 if (ptr) {
1771 int val = atoi(ptr);
1772 ar->adjustmentCenter = val;
1773 validArgumentCount++;
1775 ptr = nextArg(ptr);
1776 if (ptr) {
1777 int val = atoi(ptr);
1778 ar->adjustmentScale = val;
1779 validArgumentCount++;
1782 activeAdjustmentRangeReset();
1784 cliDumpPrintLinef(0, false, format,
1786 ar->auxChannelIndex,
1787 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1788 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1789 ar->adjustmentConfig,
1790 ar->auxSwitchChannelIndex,
1791 ar->adjustmentCenter,
1792 ar->adjustmentScale
1795 } else {
1796 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1801 #ifndef USE_QUAD_MIXER_ONLY
1802 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1804 const char *format = "mmix %d %s %s %s %s";
1805 char buf0[FTOA_BUFFER_LENGTH];
1806 char buf1[FTOA_BUFFER_LENGTH];
1807 char buf2[FTOA_BUFFER_LENGTH];
1808 char buf3[FTOA_BUFFER_LENGTH];
1809 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1810 if (customMotorMixer[i].throttle == 0.0f)
1811 break;
1812 const float thr = customMotorMixer[i].throttle;
1813 const float roll = customMotorMixer[i].roll;
1814 const float pitch = customMotorMixer[i].pitch;
1815 const float yaw = customMotorMixer[i].yaw;
1816 bool equalsDefault = false;
1817 if (defaultCustomMotorMixer) {
1818 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1819 const float rollDefault = defaultCustomMotorMixer[i].roll;
1820 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1821 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1822 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1824 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1825 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1827 ftoa(thrDefault, buf0),
1828 ftoa(rollDefault, buf1),
1829 ftoa(pitchDefault, buf2),
1830 ftoa(yawDefault, buf3));
1832 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1834 ftoa(thr, buf0),
1835 ftoa(roll, buf1),
1836 ftoa(pitch, buf2),
1837 ftoa(yaw, buf3));
1840 #endif // USE_QUAD_MIXER_ONLY
1842 static void cliMotorMix(const char *cmdName, char *cmdline)
1844 #ifdef USE_QUAD_MIXER_ONLY
1845 UNUSED(cmdName);
1846 UNUSED(cmdline);
1847 #else
1848 int check = 0;
1849 uint8_t len;
1850 const char *ptr;
1852 if (isEmpty(cmdline)) {
1853 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1854 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1855 // erase custom mixer
1856 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1857 customMotorMixerMutable(i)->throttle = 0.0f;
1859 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1860 ptr = nextArg(cmdline);
1861 if (ptr) {
1862 len = strlen(ptr);
1863 for (uint32_t i = 0; ; i++) {
1864 if (mixerNames[i] == NULL) {
1865 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
1866 break;
1868 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1869 mixerLoadMix(i, customMotorMixerMutable(0));
1870 cliPrintLinef("Loaded %s", mixerNames[i]);
1871 cliMotorMix(cmdName, "");
1872 break;
1876 } else {
1877 ptr = cmdline;
1878 uint32_t i = atoi(ptr); // get motor number
1879 if (i < MAX_SUPPORTED_MOTORS) {
1880 ptr = nextArg(ptr);
1881 if (ptr) {
1882 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1883 check++;
1885 ptr = nextArg(ptr);
1886 if (ptr) {
1887 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1888 check++;
1890 ptr = nextArg(ptr);
1891 if (ptr) {
1892 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1893 check++;
1895 ptr = nextArg(ptr);
1896 if (ptr) {
1897 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1898 check++;
1900 if (check != 4) {
1901 cliShowInvalidArgumentCountError(cmdName);
1902 } else {
1903 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1905 } else {
1906 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1909 #endif
1912 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1914 const char *format = "rxrange %u %u %u";
1915 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1916 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1917 bool equalsDefault = false;
1918 if (defaultChannelRangeConfigs) {
1919 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1920 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1921 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1923 defaultChannelRangeConfigs[i].min,
1924 defaultChannelRangeConfigs[i].max
1927 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1929 channelRangeConfigs[i].min,
1930 channelRangeConfigs[i].max
1935 static void cliRxRange(const char *cmdName, char *cmdline)
1937 const char *format = "rxrange %u %u %u";
1938 int i, validArgumentCount = 0;
1939 const char *ptr;
1941 if (isEmpty(cmdline)) {
1942 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1943 } else if (strcasecmp(cmdline, "reset") == 0) {
1944 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1945 } else {
1946 ptr = cmdline;
1947 i = atoi(ptr);
1948 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1949 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1951 ptr = nextArg(ptr);
1952 if (ptr) {
1953 rangeMin = atoi(ptr);
1954 validArgumentCount++;
1957 ptr = nextArg(ptr);
1958 if (ptr) {
1959 rangeMax = atoi(ptr);
1960 validArgumentCount++;
1963 if (validArgumentCount != 2) {
1964 cliShowInvalidArgumentCountError(cmdName);
1965 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1966 cliShowArgumentRangeError(cmdName, "range min/max", PWM_PULSE_MIN, PWM_PULSE_MAX);
1967 } else {
1968 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1969 channelRangeConfig->min = rangeMin;
1970 channelRangeConfig->max = rangeMax;
1971 cliDumpPrintLinef(0, false, format,
1973 channelRangeConfig->min,
1974 channelRangeConfig->max
1978 } else {
1979 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1984 #ifdef USE_LED_STRIP_STATUS_MODE
1985 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1987 const char *format = "led %u %s";
1988 char ledConfigBuffer[20];
1989 char ledConfigDefaultBuffer[20];
1990 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1991 for (uint32_t i = 0; i < LED_STRIP_MAX_LENGTH; i++) {
1992 ledConfig_t ledConfig = ledConfigs[i];
1993 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1994 bool equalsDefault = false;
1995 if (defaultLedConfigs) {
1996 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1997 equalsDefault = ledConfig == ledConfigDefault;
1998 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1999 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
2000 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
2002 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
2006 static void cliLed(const char *cmdName, char *cmdline)
2008 const char *format = "led %u %s";
2009 char ledConfigBuffer[20];
2010 int i;
2011 const char *ptr;
2013 if (isEmpty(cmdline)) {
2014 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
2015 } else {
2016 ptr = cmdline;
2017 i = atoi(ptr);
2018 if (i >= 0 && i < LED_STRIP_MAX_LENGTH) {
2019 ptr = nextArg(cmdline);
2020 if (parseLedStripConfig(i, ptr)) {
2021 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
2022 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
2023 } else {
2024 cliShowParseError(cmdName);
2026 } else {
2027 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_STRIP_MAX_LENGTH - 1);
2032 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
2034 const char *format = "color %u %d,%u,%u";
2035 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2036 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
2037 const hsvColor_t *color = &colors[i];
2038 bool equalsDefault = false;
2039 if (defaultColors) {
2040 const hsvColor_t *colorDefault = &defaultColors[i];
2041 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
2042 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2043 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
2045 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
2049 static void cliColor(const char *cmdName, char *cmdline)
2051 const char *format = "color %u %d,%u,%u";
2052 if (isEmpty(cmdline)) {
2053 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
2054 } else {
2055 const char *ptr = cmdline;
2056 const int i = atoi(ptr);
2057 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
2058 ptr = nextArg(cmdline);
2059 if (parseColor(i, ptr)) {
2060 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
2061 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
2062 } else {
2063 cliShowParseError(cmdName);
2065 } else {
2066 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
2071 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
2073 const char *format = "mode_color %u %u %u";
2074 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2075 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
2076 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
2077 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
2078 bool equalsDefault = false;
2079 if (defaultLedStripConfig) {
2080 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
2081 equalsDefault = colorIndex == colorIndexDefault;
2082 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2083 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
2085 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
2089 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
2090 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
2091 bool equalsDefault = false;
2092 if (defaultLedStripConfig) {
2093 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
2094 equalsDefault = colorIndex == colorIndexDefault;
2095 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2096 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
2098 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
2101 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
2102 bool equalsDefault = false;
2103 if (defaultLedStripConfig) {
2104 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
2105 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
2106 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2107 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
2109 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
2112 static void cliModeColor(const char *cmdName, char *cmdline)
2114 if (isEmpty(cmdline)) {
2115 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
2116 } else {
2117 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
2118 int args[ARGS_COUNT];
2119 int argNo = 0;
2120 char *saveptr;
2121 const char* ptr = strtok_r(cmdline, " ", &saveptr);
2122 while (ptr && argNo < ARGS_COUNT) {
2123 args[argNo++] = atoi(ptr);
2124 ptr = strtok_r(NULL, " ", &saveptr);
2127 if (ptr != NULL || argNo != ARGS_COUNT) {
2128 cliShowInvalidArgumentCountError(cmdName);
2129 return;
2132 int modeIdx = args[MODE];
2133 int funIdx = args[FUNCTION];
2134 int color = args[COLOR];
2135 if (!setModeColor(modeIdx, funIdx, color)) {
2136 cliShowParseError(cmdName);
2137 return;
2139 // values are validated
2140 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
2143 #endif
2145 #ifdef USE_SERVOS
2146 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
2148 // print out servo settings
2149 const char *format = "servo %u %d %d %d %d %d";
2150 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2151 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2152 const servoParam_t *servoConf = &servoParams[i];
2153 bool equalsDefault = false;
2154 if (defaultServoParams) {
2155 const servoParam_t *defaultServoConf = &defaultServoParams[i];
2156 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
2157 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2158 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2160 defaultServoConf->min,
2161 defaultServoConf->max,
2162 defaultServoConf->middle,
2163 defaultServoConf->rate,
2164 defaultServoConf->forwardFromChannel
2167 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2169 servoConf->min,
2170 servoConf->max,
2171 servoConf->middle,
2172 servoConf->rate,
2173 servoConf->forwardFromChannel
2176 // print servo directions
2177 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2178 const char *format = "smix reverse %d %d r";
2179 const servoParam_t *servoConf = &servoParams[i];
2180 const servoParam_t *servoConfDefault = &defaultServoParams[i];
2181 if (defaultServoParams) {
2182 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
2183 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2184 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
2185 if (servoConfDefault->reversedSources & (1 << channel)) {
2186 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
2188 if (servoConf->reversedSources & (1 << channel)) {
2189 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
2192 } else {
2193 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2194 if (servoConf->reversedSources & (1 << channel)) {
2195 cliDumpPrintLinef(dumpMask, true, format, i , channel);
2202 static void cliServo(const char *cmdName, char *cmdline)
2204 const char *format = "servo %u %d %d %d %d %d";
2205 enum { SERVO_ARGUMENT_COUNT = 6 };
2206 int16_t arguments[SERVO_ARGUMENT_COUNT];
2208 servoParam_t *servo;
2210 int i;
2211 char *ptr;
2213 if (isEmpty(cmdline)) {
2214 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2215 } else {
2216 int validArgumentCount = 0;
2218 ptr = cmdline;
2220 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2222 // If command line doesn't fit the format, don't modify the config
2223 while (*ptr) {
2224 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2225 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2226 cliShowInvalidArgumentCountError(cmdName);
2227 return;
2230 arguments[validArgumentCount++] = atoi(ptr);
2232 do {
2233 ptr++;
2234 } while (*ptr >= '0' && *ptr <= '9');
2235 } else if (*ptr == ' ') {
2236 ptr++;
2237 } else {
2238 cliShowParseError(cmdName);
2239 return;
2243 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2245 i = arguments[INDEX];
2247 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2248 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2249 cliShowInvalidArgumentCountError(cmdName);
2250 return;
2253 servo = servoParamsMutable(i);
2255 if (
2256 arguments[MIN] < PWM_SERVO_MIN || arguments[MIN] > PWM_SERVO_MAX ||
2257 arguments[MAX] < PWM_SERVO_MIN || arguments[MAX] > PWM_SERVO_MAX ||
2258 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2259 arguments[MIN] > arguments[MAX] ||
2260 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2261 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2263 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2264 return;
2267 servo->min = arguments[MIN];
2268 servo->max = arguments[MAX];
2269 servo->middle = arguments[MIDDLE];
2270 servo->rate = arguments[RATE];
2271 servo->forwardFromChannel = arguments[FORWARD];
2273 cliDumpPrintLinef(0, false, format,
2275 servo->min,
2276 servo->max,
2277 servo->middle,
2278 servo->rate,
2279 servo->forwardFromChannel
2284 #endif
2286 #ifdef USE_SERVOS
2287 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2289 const char *format = "smix %d %d %d %d %d %d %d %d";
2290 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2291 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2292 const servoMixer_t customServoMixer = customServoMixers[i];
2293 if (customServoMixer.rate == 0) {
2294 break;
2297 bool equalsDefault = false;
2298 if (defaultCustomServoMixers) {
2299 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2300 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2302 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2303 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2305 customServoMixerDefault.targetChannel,
2306 customServoMixerDefault.inputSource,
2307 customServoMixerDefault.rate,
2308 customServoMixerDefault.speed,
2309 customServoMixerDefault.min,
2310 customServoMixerDefault.max,
2311 customServoMixerDefault.box
2314 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2316 customServoMixer.targetChannel,
2317 customServoMixer.inputSource,
2318 customServoMixer.rate,
2319 customServoMixer.speed,
2320 customServoMixer.min,
2321 customServoMixer.max,
2322 customServoMixer.box
2327 static void cliServoMix(const char *cmdName, char *cmdline)
2329 int args[8], check = 0;
2330 int len = strlen(cmdline);
2332 if (len == 0) {
2333 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2334 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2335 // erase custom mixer
2336 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2337 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2338 servoParamsMutable(i)->reversedSources = 0;
2340 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2341 const char *ptr = nextArg(cmdline);
2342 if (ptr) {
2343 len = strlen(ptr);
2344 for (uint32_t i = 0; ; i++) {
2345 if (mixerNames[i] == NULL) {
2346 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
2347 break;
2349 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2350 servoMixerLoadMix(i);
2351 cliPrintLinef("Loaded %s", mixerNames[i]);
2352 cliServoMix(cmdName, "");
2353 break;
2357 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2358 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2359 char *ptr = strchr(cmdline, ' ');
2361 if (ptr == NULL) {
2362 cliPrintf("s");
2363 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2364 cliPrintf("\ti%d", inputSource);
2365 cliPrintLinefeed();
2367 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2368 cliPrintf("%d", servoIndex);
2369 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2370 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2372 cliPrintLinefeed();
2374 return;
2377 char *saveptr;
2378 ptr = strtok_r(ptr, " ", &saveptr);
2379 while (ptr != NULL && check < ARGS_COUNT - 1) {
2380 args[check++] = atoi(ptr);
2381 ptr = strtok_r(NULL, " ", &saveptr);
2384 if (ptr == NULL || check != ARGS_COUNT - 1) {
2385 cliShowInvalidArgumentCountError(cmdName);
2386 return;
2389 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2390 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2391 && (*ptr == 'r' || *ptr == 'n')) {
2392 if (*ptr == 'r') {
2393 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2394 } else {
2395 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2397 } else {
2398 cliShowArgumentRangeError(cmdName, "servo", 0, MAX_SUPPORTED_SERVOS);
2399 return;
2402 cliServoMix(cmdName, "reverse");
2403 } else {
2404 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2405 char *saveptr;
2406 char *ptr = strtok_r(cmdline, " ", &saveptr);
2407 while (ptr != NULL && check < ARGS_COUNT) {
2408 args[check++] = atoi(ptr);
2409 ptr = strtok_r(NULL, " ", &saveptr);
2412 if (ptr != NULL || check != ARGS_COUNT) {
2413 cliShowInvalidArgumentCountError(cmdName);
2414 return;
2417 int32_t i = args[RULE];
2418 if (i >= 0 && i < MAX_SERVO_RULES &&
2419 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2420 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2421 args[RATE] >= -100 && args[RATE] <= 100 &&
2422 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2423 args[MIN] >= 0 && args[MIN] <= 100 &&
2424 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2425 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2426 customServoMixersMutable(i)->targetChannel = args[TARGET];
2427 customServoMixersMutable(i)->inputSource = args[INPUT];
2428 customServoMixersMutable(i)->rate = args[RATE];
2429 customServoMixersMutable(i)->speed = args[SPEED];
2430 customServoMixersMutable(i)->min = args[MIN];
2431 customServoMixersMutable(i)->max = args[MAX];
2432 customServoMixersMutable(i)->box = args[BOX];
2433 cliServoMix(cmdName, "");
2434 } else {
2435 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2439 #endif
2441 #ifdef USE_SDCARD
2443 static void cliWriteBytes(const uint8_t *buffer, int count)
2445 while (count > 0) {
2446 cliWrite(*buffer);
2447 buffer++;
2448 count--;
2452 static void cliSdInfo(const char *cmdName, char *cmdline)
2454 UNUSED(cmdName);
2455 UNUSED(cmdline);
2457 cliPrint("SD card: ");
2459 if (sdcardConfig()->mode == SDCARD_MODE_NONE) {
2460 cliPrintLine("Not configured");
2462 return;
2465 if (!sdcard_isInserted()) {
2466 cliPrintLine("None inserted");
2467 return;
2470 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2471 cliPrintLine("Startup failed");
2472 return;
2475 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2477 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2478 metadata->manufacturerID,
2479 metadata->numBlocks / 2, /* One block is half a kB */
2480 metadata->productionMonth,
2481 metadata->productionYear,
2482 metadata->productRevisionMajor,
2483 metadata->productRevisionMinor
2486 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2488 cliPrint("'\r\n" "Filesystem: ");
2490 switch (afatfs_getFilesystemState()) {
2491 case AFATFS_FILESYSTEM_STATE_READY:
2492 cliPrint("Ready");
2493 break;
2494 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2495 cliPrint("Initializing");
2496 break;
2497 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2498 case AFATFS_FILESYSTEM_STATE_FATAL:
2499 cliPrint("Fatal");
2501 switch (afatfs_getLastError()) {
2502 case AFATFS_ERROR_BAD_MBR:
2503 cliPrint(" - no FAT MBR partitions");
2504 break;
2505 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2506 cliPrint(" - bad FAT header");
2507 break;
2508 case AFATFS_ERROR_GENERIC:
2509 case AFATFS_ERROR_NONE:
2510 ; // Nothing more detailed to print
2511 break;
2513 break;
2515 cliPrintLinefeed();
2518 #endif
2520 #ifdef USE_FLASH_CHIP
2521 static void cliFlashInfo(const char *cmdName, char *cmdline)
2523 UNUSED(cmdName);
2524 UNUSED(cmdline);
2526 const flashGeometry_t *layout = flashGetGeometry();
2528 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u JEDEC ID=0x%08x",
2529 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, layout->jedecId);
2531 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2532 const flashPartition_t *partition;
2533 if (index == 0) {
2534 cliPrintLine("Partitions:");
2536 partition = flashPartitionFindByIndex(index);
2537 if (!partition) {
2538 break;
2540 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2542 #ifdef USE_FLASHFS
2543 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2545 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2546 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2547 flashfsGetOffset()
2549 #endif
2551 #endif // USE_FLASH_CHIP
2553 #if defined(USE_FLASHFS) && defined(USE_FLASH_CHIP)
2554 static void cliFlashErase(const char *cmdName, char *cmdline)
2556 UNUSED(cmdName);
2557 UNUSED(cmdline);
2559 if (!flashfsIsSupported()) {
2560 return;
2563 #ifndef MINIMAL_CLI
2564 uint32_t i = 0;
2565 cliPrintLine("Erasing, please wait ... ");
2566 #else
2567 cliPrintLine("Erasing,");
2568 #endif
2570 cliWriterFlush();
2571 flashfsEraseCompletely();
2573 while (!flashfsIsReady()) {
2574 #ifndef MINIMAL_CLI
2575 cliPrintf(".");
2576 if (i++ > 120) {
2577 i=0;
2578 cliPrintLinefeed();
2581 cliWriterFlush();
2582 #endif
2583 delay(100);
2585 beeper(BEEPER_BLACKBOX_ERASE);
2586 cliPrintLinefeed();
2587 cliPrintLine("Done.");
2590 #ifdef USE_FLASH_TOOLS
2591 static void cliFlashVerify(const char *cmdName, char *cmdline)
2593 UNUSED(cmdline);
2595 cliPrintLine("Verifying");
2596 if (flashfsVerifyEntireFlash()) {
2597 cliPrintLine("Success");
2598 } else {
2599 cliPrintErrorLinef(cmdName, "Failed");
2603 static void cliFlashWrite(const char *cmdName, char *cmdline)
2605 const uint32_t address = atoi(cmdline);
2606 const char *text = strchr(cmdline, ' ');
2608 if (!text) {
2609 cliShowInvalidArgumentCountError(cmdName);
2610 } else {
2611 flashfsSeekAbs(address);
2612 flashfsWrite((uint8_t*)text, strlen(text), true);
2613 flashfsFlushSync();
2615 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2619 static void cliFlashRead(const char *cmdName, char *cmdline)
2621 uint32_t address = atoi(cmdline);
2623 const char *nextArg = strchr(cmdline, ' ');
2625 if (!nextArg) {
2626 cliShowInvalidArgumentCountError(cmdName);
2627 } else {
2628 uint32_t length = atoi(nextArg);
2630 cliPrintLinef("Reading %u bytes at %u:", length, address);
2632 uint8_t buffer[32];
2633 while (length > 0) {
2634 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2636 for (int i = 0; i < bytesRead; i++) {
2637 cliWrite(buffer[i]);
2640 length -= bytesRead;
2641 address += bytesRead;
2643 if (bytesRead == 0) {
2644 //Assume we reached the end of the volume or something fatal happened
2645 break;
2648 cliPrintLinefeed();
2651 #endif // USE_FLASH_TOOLS
2652 #endif // USE_FLASHFS
2654 #ifdef USE_VTX_CONTROL
2655 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2657 // print out vtx channel settings
2658 const char *format = "vtx %u %u %u %u %u %u %u";
2659 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2660 bool equalsDefault = false;
2661 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2662 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2663 if (vtxConfigDefault) {
2664 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2665 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2666 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2667 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2669 cacDefault->auxChannelIndex,
2670 cacDefault->band,
2671 cacDefault->channel,
2672 cacDefault->power,
2673 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2674 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2677 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2679 cac->auxChannelIndex,
2680 cac->band,
2681 cac->channel,
2682 cac->power,
2683 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2684 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2689 static void cliVtx(const char *cmdName, char *cmdline)
2691 const char *format = "vtx %u %u %u %u %u %u %u";
2692 const char *ptr;
2694 if (isEmpty(cmdline)) {
2695 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2696 } else {
2697 #ifdef USE_VTX_TABLE
2698 const uint8_t maxBandIndex = vtxTableConfig()->bands;
2699 const uint8_t maxChannelIndex = vtxTableConfig()->channels;
2700 const uint8_t maxPowerIndex = vtxTableConfig()->powerLevels;
2701 #else
2702 const uint8_t maxBandIndex = VTX_TABLE_MAX_BANDS;
2703 const uint8_t maxChannelIndex = VTX_TABLE_MAX_CHANNELS;
2704 const uint8_t maxPowerIndex = VTX_TABLE_MAX_POWER_LEVELS;
2705 #endif
2706 ptr = cmdline;
2707 int i = atoi(ptr++);
2708 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2709 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2710 uint8_t validArgumentCount = 0;
2711 ptr = nextArg(ptr);
2712 if (ptr) {
2713 int val = atoi(ptr);
2714 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2715 cac->auxChannelIndex = val;
2716 validArgumentCount++;
2719 ptr = nextArg(ptr);
2720 if (ptr) {
2721 int val = atoi(ptr);
2722 if (val >= 0 && val <= maxBandIndex) {
2723 cac->band = val;
2724 validArgumentCount++;
2727 ptr = nextArg(ptr);
2728 if (ptr) {
2729 int val = atoi(ptr);
2730 if (val >= 0 && val <= maxChannelIndex) {
2731 cac->channel = val;
2732 validArgumentCount++;
2735 ptr = nextArg(ptr);
2736 if (ptr) {
2737 int val = atoi(ptr);
2738 if (val >= 0 && val <= maxPowerIndex) {
2739 cac->power= val;
2740 validArgumentCount++;
2743 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2745 if (validArgumentCount != 6) {
2746 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2747 cliShowInvalidArgumentCountError(cmdName);
2748 } else {
2749 cliDumpPrintLinef(0, false, format,
2751 cac->auxChannelIndex,
2752 cac->band,
2753 cac->channel,
2754 cac->power,
2755 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2756 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2759 } else {
2760 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2765 #endif // VTX_CONTROL
2767 #ifdef USE_VTX_TABLE
2769 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2771 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2772 char freqtmp[5 + 1];
2773 freqbuf[0] = 0;
2774 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2775 for (int channel = 0; channel < channels; channel++) {
2776 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2777 strcat(freqbuf, freqtmp);
2779 return freqbuf;
2782 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2784 char *fmt = "vtxtable band %d %s %c%s";
2785 bool equalsDefault = false;
2787 if (defaultConfig) {
2788 equalsDefault = true;
2789 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2790 equalsDefault = false;
2792 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2793 equalsDefault = false;
2795 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2796 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2797 equalsDefault = false;
2800 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2801 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2802 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2805 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2806 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2807 return headingStr;
2810 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2812 // (max 4 digit + 1 space) per level
2813 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2814 char pwrtmp[5 + 1];
2815 pwrbuf[0] = 0;
2816 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2817 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2818 strcat(pwrbuf, pwrtmp);
2820 return pwrbuf;
2823 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2825 char *fmt = "vtxtable powervalues%s";
2826 bool equalsDefault = false;
2827 if (defaultConfig) {
2828 equalsDefault = true;
2829 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2830 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2831 equalsDefault = false;
2834 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2835 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2836 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2839 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2840 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2841 return headingStr;
2844 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2846 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2847 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2848 pwrbuf[0] = 0;
2849 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2850 strcat(pwrbuf, " ");
2851 strcpy(pwrtmp, labels[pwrindex]);
2852 // trim trailing space
2853 char *sp;
2854 while ((sp = strchr(pwrtmp, ' '))) {
2855 *sp = 0;
2857 strcat(pwrbuf, pwrtmp);
2859 return pwrbuf;
2862 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2864 char *fmt = "vtxtable powerlabels%s";
2865 bool equalsDefault = false;
2866 if (defaultConfig) {
2867 equalsDefault = true;
2868 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2869 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2870 equalsDefault = false;
2873 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2874 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2875 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2878 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2879 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2880 return headingStr;
2883 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2885 bool equalsDefault;
2886 char *fmt;
2888 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2890 // bands
2891 equalsDefault = false;
2892 fmt = "vtxtable bands %d";
2893 if (defaultConfig) {
2894 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2895 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2896 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2898 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2900 // channels
2901 equalsDefault = false;
2902 fmt = "vtxtable channels %d";
2903 if (defaultConfig) {
2904 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2905 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2906 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2908 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2910 // band
2912 for (int band = 0; band < currentConfig->bands; band++) {
2913 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2916 // powerlevels
2918 equalsDefault = false;
2919 fmt = "vtxtable powerlevels %d";
2920 if (defaultConfig) {
2921 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2922 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2923 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2925 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2927 // powervalues
2929 // powerlabels
2930 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2931 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2934 static void cliVtxTable(const char *cmdName, char *cmdline)
2936 char *tok;
2937 char *saveptr;
2939 // Band number or nothing
2940 tok = strtok_r(cmdline, " ", &saveptr);
2942 if (!tok) {
2943 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2944 return;
2947 if (strcasecmp(tok, "bands") == 0) {
2948 tok = strtok_r(NULL, " ", &saveptr);
2949 int bands = atoi(tok);
2950 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2951 cliShowArgumentRangeError(cmdName, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS);
2952 return;
2954 if (bands < vtxTableConfigMutable()->bands) {
2955 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2956 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2959 vtxTableConfigMutable()->bands = bands;
2961 } else if (strcasecmp(tok, "channels") == 0) {
2962 tok = strtok_r(NULL, " ", &saveptr);
2964 int channels = atoi(tok);
2965 if (channels < 0 || channels > VTX_TABLE_MAX_CHANNELS) {
2966 cliShowArgumentRangeError(cmdName, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS);
2967 return;
2969 if (channels < vtxTableConfigMutable()->channels) {
2970 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2971 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2974 vtxTableConfigMutable()->channels = channels;
2976 } else if (strcasecmp(tok, "powerlevels") == 0) {
2977 // Number of power levels
2978 tok = strtok_r(NULL, " ", &saveptr);
2979 if (tok) {
2980 int levels = atoi(tok);
2981 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2982 cliShowArgumentRangeError(cmdName, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS);
2983 } else {
2984 if (levels < vtxTableConfigMutable()->powerLevels) {
2985 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2986 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2988 vtxTableConfigMutable()->powerLevels = levels;
2990 } else {
2991 // XXX Show current level count?
2993 return;
2995 } else if (strcasecmp(tok, "powervalues") == 0) {
2996 // Power values
2997 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2998 int count;
2999 int levels = vtxTableConfigMutable()->powerLevels;
3001 memset(power, 0, sizeof(power));
3003 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
3004 int value = atoi(tok);
3005 power[count] = value;
3008 // Check remaining tokens
3010 if (count < levels) {
3011 cliPrintErrorLinef(cmdName, "NOT ENOUGH VALUES (EXPECTED %d)", levels);
3012 return;
3013 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3014 cliPrintErrorLinef(cmdName, "TOO MANY VALUES (EXPECTED %d)", levels);
3015 return;
3018 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
3019 vtxTableConfigMutable()->powerValues[i] = power[i];
3022 } else if (strcasecmp(tok, "powerlabels") == 0) {
3023 // Power labels
3024 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
3025 int levels = vtxTableConfigMutable()->powerLevels;
3026 int count;
3027 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
3028 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
3029 for (unsigned i = 0; i < strlen(label[count]); i++) {
3030 label[count][i] = toupper(label[count][i]);
3034 // Check remaining tokens
3036 if (count < levels) {
3037 cliPrintErrorLinef(cmdName, "NOT ENOUGH LABELS (EXPECTED %d)", levels);
3038 return;
3039 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3040 cliPrintErrorLinef(cmdName, "TOO MANY LABELS (EXPECTED %d)", levels);
3041 return;
3044 for (int i = 0; i < count; i++) {
3045 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
3047 } else if (strcasecmp(tok, "band") == 0) {
3049 int bands = vtxTableConfigMutable()->bands;
3051 tok = strtok_r(NULL, " ", &saveptr);
3052 if (!tok) {
3053 return;
3056 int band = atoi(tok);
3057 --band;
3059 if (band < 0 || band >= bands) {
3060 cliShowArgumentRangeError(cmdName, "BAND NUMBER", 1, bands);
3061 return;
3064 // Band name
3065 tok = strtok_r(NULL, " ", &saveptr);
3067 if (!tok) {
3068 return;
3071 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
3072 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
3073 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
3074 for (unsigned i = 0; i < strlen(bandname); i++) {
3075 bandname[i] = toupper(bandname[i]);
3078 // Band letter
3079 tok = strtok_r(NULL, " ", &saveptr);
3081 if (!tok) {
3082 return;
3085 char bandletter = toupper(tok[0]);
3087 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
3088 int channel = 0;
3089 int channels = vtxTableConfigMutable()->channels;
3090 bool isFactory = false;
3092 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
3093 if (channel == 0 && !isdigit(tok[0])) {
3094 channel -= 1;
3095 if (strcasecmp(tok, "FACTORY") == 0) {
3096 isFactory = true;
3097 } else if (strcasecmp(tok, "CUSTOM") == 0) {
3098 isFactory = false;
3099 } else {
3100 cliPrintErrorLinef(cmdName, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
3101 return;
3104 int freq = atoi(tok);
3105 if (freq < 0) {
3106 cliPrintErrorLinef(cmdName, "INVALID FREQUENCY %s", tok);
3107 return;
3109 bandfreq[channel] = freq;
3112 if (channel < channels) {
3113 cliPrintErrorLinef(cmdName, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
3114 return;
3115 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3116 cliPrintErrorLinef(cmdName, "TOO MANY FREQUENCIES (EXPECTED %d)", channels);
3117 return;
3120 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
3121 vtxTableConfigMutable()->bandLetters[band] = bandletter;
3123 for (int i = 0; i < channel; i++) {
3124 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
3126 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
3127 } else {
3128 // Bad subcommand
3129 cliPrintErrorLinef(cmdName, "INVALID SUBCOMMAND %s", tok);
3133 static void cliVtxInfo(const char *cmdName, char *cmdline)
3135 UNUSED(cmdline);
3137 // Display the available power levels
3138 uint16_t levels[VTX_TABLE_MAX_POWER_LEVELS];
3139 uint16_t powers[VTX_TABLE_MAX_POWER_LEVELS];
3140 vtxDevice_t *vtxDevice = vtxCommonDevice();
3141 if (vtxDevice) {
3142 uint8_t level_count = vtxCommonGetVTXPowerLevels(vtxDevice, levels, powers);
3144 if (level_count) {
3145 for (int i = 0; i < level_count; i++) {
3146 cliPrintLinef("level %d dBm, power %d mW", levels[i], powers[i]);
3148 } else {
3149 cliPrintErrorLinef(cmdName, "NO POWER VALUES DEFINED");
3151 } else {
3152 cliPrintErrorLinef(cmdName, "NO VTX");
3155 #endif // USE_VTX_TABLE
3157 #if defined(USE_SIMPLIFIED_TUNING)
3158 static void applySimplifiedTuningAllProfiles(void)
3160 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3161 applySimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3165 static void cliSimplifiedTuning(const char *cmdName, char *cmdline)
3167 if (strcasecmp(cmdline, "apply") == 0) {
3168 applySimplifiedTuningAllProfiles();
3170 cliPrintLine("Applied simplified tuning.");
3171 } else if (strcasecmp(cmdline, "disable") == 0) {
3172 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3173 disableSimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3176 cliPrintLine("Disabled simplified tuning.");
3177 } else {
3178 cliShowParseError(cmdName);
3181 #endif
3183 static void printCraftName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
3185 const bool equalsDefault = strlen(pilotConfig->craftName) == 0;
3186 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->craftName);
3189 #if defined(USE_BOARD_INFO)
3191 static void printBoardName(dumpFlags_t dumpMask)
3193 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
3194 cliPrintLinef("board_name %s", getBoardName());
3198 static void cliBoardName(const char *cmdName, char *cmdline)
3200 const unsigned int len = strlen(cmdline);
3201 const char *boardName = getBoardName();
3202 if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
3203 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "BOARD_NAME", boardName);
3204 } else {
3205 if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
3206 boardInformationUpdated = true;
3208 cliPrintHashLine("Set board_name.");
3210 printBoardName(DUMP_ALL);
3214 static void printManufacturerId(dumpFlags_t dumpMask)
3216 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
3217 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3221 static void cliManufacturerId(const char *cmdName, char *cmdline)
3223 const unsigned int len = strlen(cmdline);
3224 const char *manufacturerId = getManufacturerId();
3225 if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
3226 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
3227 } else {
3228 if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
3229 boardInformationUpdated = true;
3231 cliPrintHashLine("Set manufacturer_id.");
3233 printManufacturerId(DUMP_ALL);
3237 #if defined(USE_SIGNATURE)
3238 static void writeSignature(char *signatureStr, uint8_t *signature)
3240 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3241 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
3245 static void cliSignature(const char *cmdName, char *cmdline)
3247 const int len = strlen(cmdline);
3249 uint8_t signature[SIGNATURE_LENGTH] = {0};
3250 if (len > 0) {
3251 if (len != 2 * SIGNATURE_LENGTH) {
3252 cliPrintErrorLinef(cmdName, "INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
3254 return;
3257 #define BLOCK_SIZE 2
3258 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3259 char temp[BLOCK_SIZE + 1];
3260 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3261 temp[BLOCK_SIZE] = '\0';
3262 char *end;
3263 unsigned result = strtoul(temp, &end, 16);
3264 if (end == &temp[BLOCK_SIZE]) {
3265 signature[i] = result;
3266 } else {
3267 cliPrintErrorLinef(cmdName, "INVALID CHARACTER FOUND: %c", end[0]);
3269 return;
3272 #undef BLOCK_SIZE
3275 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3276 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3277 writeSignature(signatureStr, getSignature());
3278 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "SIGNATURE", signatureStr);
3279 } else {
3280 if (len > 0 && !configIsInCopy && setSignature(signature)) {
3281 signatureUpdated = true;
3283 writeSignature(signatureStr, getSignature());
3285 cliPrintHashLine("Set signature.");
3286 } else if (signatureUpdated || signatureIsSet()) {
3287 writeSignature(signatureStr, getSignature());
3290 cliPrintLinef("signature %s", signatureStr);
3293 #endif
3295 #undef ERROR_MESSAGE
3297 #endif // USE_BOARD_INFO
3299 static void cliMcuId(const char *cmdName, char *cmdline)
3301 UNUSED(cmdName);
3302 UNUSED(cmdline);
3304 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3307 static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
3309 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3310 for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) { // disabled features first
3311 if (featureNames[i]) { //Skip unused
3312 const char *format = "feature -%s";
3313 const bool equalsDefault = (~defaultMask | mask) & (1U << i);
3314 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3315 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1U << i), format, featureNames[i]);
3316 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3319 for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) { // enabled features
3320 if (featureNames[i]) { //Skip unused
3321 const char *format = "feature %s";
3322 if (defaultMask & (1U << i)) {
3323 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1U << i), format, featureNames[i]);
3325 if (mask & (1U << i)) {
3326 const bool equalsDefault = (defaultMask | ~mask) & (1U << i);
3327 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3328 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3334 static void printFeatureList(const char* header, uint32_t mask, const char* delimiter, bool lineFeed)
3336 if (header) {
3337 cliPrint(header);
3339 for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) {
3340 if (featureNames[i] && (mask & (1U << i))) {
3341 cliPrintf("%s%s", i ? delimiter : "", featureNames[i]);
3344 if (lineFeed) {
3345 cliPrintLinefeed();
3349 static void cliFeature(const char *cmdName, char *cmdline)
3351 uint32_t len = strlen(cmdline);
3352 const uint32_t mask = featureConfig()->enabledFeatures;
3353 if (len == 0 // `feature`
3354 || strncasecmp(cmdline, "list", len) == 0) { // old `feature list` invocation
3355 printFeatureList("Enabled: ", mask, " ", true);
3356 printFeatureList("Available: ", ~mask & featuresSupportedByBuild, " ", true);
3357 printFeatureList("Unavailable: ", ~featuresSupportedByBuild, " ", true);
3358 } else {
3359 bool remove = false;
3360 if (cmdline[0] == '-') {
3361 // remove feature
3362 remove = true;
3363 cmdline++; // skip over -
3364 len--;
3367 unsigned found = 0;
3368 int featureIdx = -1;
3369 for (unsigned i = 0; !found && i < ARRAYLEN(featureNames); i++) {
3370 if (featureNames[i] && strncasecmp(cmdline, featureNames[i], len) == 0) {
3371 found++;
3372 featureIdx = i;
3375 if (found == 1) {
3376 uint32_t feature = 1U << featureIdx;
3377 const char *verb;
3378 if ((feature & featuresSupportedByBuild) == 0) {
3379 verb = "Unavailable";
3380 } else if (remove) {
3381 featureConfigClear(feature);
3382 verb = (mask & feature) ? "Disabled" : "AlreadyDisabled";
3383 } else {
3384 featureConfigSet(feature);
3385 verb = (mask & feature) ? "AlreadyEnabled" : "Enabled";
3387 cliPrintLinef("%s %s", verb, featureNames[featureIdx]);
3388 } else if (found > 1) {
3389 cliPrintErrorLinef(cmdName, "Multiple features match %s", cmdline);
3390 } else /* found <= 0 */ {
3391 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
3396 #if defined(USE_BEEPER)
3397 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3399 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3400 const uint8_t beeperCount = beeperTableEntryCount();
3401 for (int32_t i = 0; i < beeperCount - 1; i++) {
3402 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3403 if (beeperModeMask & allowedFlags) {
3404 const char *formatOff = "%s -%s";
3405 const char *formatOn = "%s %s";
3406 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3407 cliDefaultPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3408 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3409 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3414 static void processBeeperCommand(const char *cmdName, char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3416 uint32_t len = strlen(cmdline);
3417 uint8_t beeperCount = beeperTableEntryCount();
3419 if (len == 0) {
3420 cliPrintf("Disabled:");
3421 for (int32_t i = 0; ; i++) {
3422 if (i == beeperCount - 1) {
3423 if (*offFlags == 0)
3424 cliPrint(" none");
3425 break;
3428 if (beeperModeMaskForTableIndex(i) & *offFlags)
3429 cliPrintf(" %s", beeperNameForTableIndex(i));
3431 cliPrintLinefeed();
3432 } else if (strncasecmp(cmdline, "list", len) == 0) {
3433 cliPrint("Available:");
3434 for (uint32_t i = 0; i < beeperCount; i++) {
3435 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3436 cliPrintf(" %s", beeperNameForTableIndex(i));
3439 cliPrintLinefeed();
3440 } else {
3441 bool remove = false;
3442 if (cmdline[0] == '-') {
3443 remove = true; // this is for beeper OFF condition
3444 cmdline++;
3445 len--;
3448 for (uint32_t i = 0; ; i++) {
3449 if (i == beeperCount) {
3450 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
3451 break;
3453 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3454 if (remove) { // beeper off
3455 if (i == BEEPER_ALL - 1) {
3456 *offFlags = allowedFlags;
3457 } else {
3458 *offFlags |= beeperModeMaskForTableIndex(i);
3460 cliPrint("Disabled");
3462 else { // beeper on
3463 if (i == BEEPER_ALL - 1) {
3464 *offFlags = 0;
3465 } else {
3466 *offFlags &= ~beeperModeMaskForTableIndex(i);
3468 cliPrint("Enabled");
3470 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3471 break;
3477 #if defined(USE_DSHOT)
3478 static void cliBeacon(const char *cmdName, char *cmdline)
3480 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3482 #endif
3484 static void cliBeeper(const char *cmdName, char *cmdline)
3486 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3488 #endif
3490 #if defined(USE_RX_BIND)
3491 static void cliRxBind(const char *cmdName, char *cmdline)
3493 UNUSED(cmdline);
3494 if (!startRxBind()) {
3495 cliPrintErrorLinef(cmdName, "Not supported.");
3496 } else {
3497 cliPrintLinef("Binding...");
3500 #endif
3502 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3504 bool equalsDefault = true;
3505 char buf[16];
3506 char bufDefault[16];
3507 uint32_t i;
3509 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3510 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3511 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3512 if (defaultRxConfig) {
3513 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3514 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3517 buf[i] = '\0';
3519 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3520 const char *formatMap = "map %s";
3521 if (defaultRxConfig) {
3522 bufDefault[i] = '\0';
3523 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3525 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3528 static void cliMap(const char *cmdName, char *cmdline)
3530 uint32_t i;
3531 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3533 uint32_t len = strlen(cmdline);
3534 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3536 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3537 buf[i] = toupper((unsigned char)cmdline[i]);
3539 buf[i] = '\0';
3541 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3542 buf[i] = toupper((unsigned char)cmdline[i]);
3544 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3545 continue;
3547 cliShowParseError(cmdName);
3548 return;
3550 parseRcChannels(buf, rxConfigMutable());
3551 } else if (len > 0) {
3552 cliShowInvalidArgumentCountError(cmdName);
3553 return;
3556 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3557 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3560 buf[i] = '\0';
3561 cliPrintLinef("map %s", buf);
3564 static char *skipSpace(char *buffer)
3566 while (*(buffer) == ' ') {
3567 buffer++;
3570 return buffer;
3573 static char *checkCommand(char *cmdline, const char *command)
3575 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3576 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3577 return skipSpace(cmdline + strlen(command) + 1);
3578 } else {
3579 return 0;
3583 static void cliRebootEx(rebootTarget_e rebootTarget)
3585 cliPrint("\r\nRebooting");
3586 cliWriterFlush();
3587 waitForSerialPortToFinishTransmitting(cliPort);
3588 motorShutdown();
3590 switch (rebootTarget) {
3591 case REBOOT_TARGET_BOOTLOADER_ROM:
3592 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3594 break;
3595 #if defined(USE_FLASH_BOOT_LOADER)
3596 case REBOOT_TARGET_BOOTLOADER_FLASH:
3597 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3599 break;
3600 #endif
3601 case REBOOT_TARGET_FIRMWARE:
3602 default:
3603 systemReset();
3605 break;
3609 static void cliReboot(void)
3611 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3614 static void cliBootloader(const char *cmdName, char *cmdline)
3616 rebootTarget_e rebootTarget;
3617 if (
3618 #if !defined(USE_FLASH_BOOT_LOADER)
3619 isEmpty(cmdline) ||
3620 #endif
3621 strncasecmp(cmdline, "rom", 3) == 0) {
3622 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3624 cliPrintHashLine("restarting in ROM bootloader mode");
3625 #if defined(USE_FLASH_BOOT_LOADER)
3626 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3627 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3629 cliPrintHashLine("restarting in flash bootloader mode");
3630 #endif
3631 } else {
3632 cliPrintErrorLinef(cmdName, "Invalid option");
3634 return;
3637 cliRebootEx(rebootTarget);
3640 static void cliExitCmd(const char *cmdName, char *cmdline)
3642 UNUSED(cmdName);
3644 const bool reboot = strcasecmp(cmdline, "noreboot") != 0;
3645 if (reboot) {
3646 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3647 } else {
3648 cliPrintHashLine("leaving CLI mode, no reboot");
3650 cliExit(reboot);
3653 #ifdef USE_GPS
3654 static void cliGpsPassthrough(const char *cmdName, char *cmdline)
3656 UNUSED(cmdName);
3657 UNUSED(cmdline);
3659 if (!gpsPassthrough(cliPort)) {
3660 cliPrintErrorLinef(cmdName, "GPS forwarding failed");
3663 #endif
3665 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3666 static void cliPrintGyroRegisters(uint8_t whichSensor)
3668 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3669 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3670 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3673 static void cliDumpGyroRegisters(const char *cmdName, char *cmdline)
3675 UNUSED(cmdName);
3676 UNUSED(cmdline);
3678 #ifdef USE_MULTI_GYRO
3679 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3680 cliPrintLinef("\r\n# Gyro 1");
3681 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3683 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3684 cliPrintLinef("\r\n# Gyro 2");
3685 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3687 #else
3688 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3689 #endif
3691 #endif
3693 static int parseOutputIndex(const char *cmdName, char *pch, bool allowAllEscs)
3695 int outputIndex = atoi(pch);
3696 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3697 cliPrintLinef("Using output %d.", outputIndex);
3698 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3699 cliPrintLinef("Using all outputs.");
3700 } else {
3701 cliPrintErrorLinef(cmdName, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3703 return -1;
3706 return outputIndex;
3709 #if defined(USE_DSHOT)
3710 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3712 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3713 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3714 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3716 enum {
3717 ESC_INFO_KISS_V1,
3718 ESC_INFO_KISS_V2,
3719 ESC_INFO_BLHELI32
3722 #define ESC_INFO_VERSION_POSITION 12
3724 static void printEscInfo(const char *cmdName, const uint8_t *escInfoBuffer, uint8_t bytesRead)
3726 bool escInfoReceived = false;
3727 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3728 uint8_t escInfoVersion;
3729 uint8_t frameLength;
3730 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3731 escInfoVersion = ESC_INFO_BLHELI32;
3732 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3733 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3734 escInfoVersion = ESC_INFO_KISS_V2;
3735 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3736 } else {
3737 escInfoVersion = ESC_INFO_KISS_V1;
3738 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3741 if (bytesRead == frameLength) {
3742 escInfoReceived = true;
3744 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3745 uint8_t firmwareVersion = 0;
3746 uint8_t firmwareSubVersion = 0;
3747 uint8_t escType = 0;
3748 switch (escInfoVersion) {
3749 case ESC_INFO_KISS_V1:
3750 firmwareVersion = escInfoBuffer[12];
3751 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3752 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3754 break;
3755 case ESC_INFO_KISS_V2:
3756 firmwareVersion = escInfoBuffer[13];
3757 firmwareSubVersion = escInfoBuffer[14];
3758 escType = escInfoBuffer[15];
3760 break;
3761 case ESC_INFO_BLHELI32:
3762 firmwareVersion = escInfoBuffer[13];
3763 firmwareSubVersion = escInfoBuffer[14];
3764 escType = escInfoBuffer[15];
3766 break;
3769 cliPrint("ESC Type: ");
3770 switch (escInfoVersion) {
3771 case ESC_INFO_KISS_V1:
3772 case ESC_INFO_KISS_V2:
3773 switch (escType) {
3774 case 1:
3775 cliPrintLine("KISS8A");
3777 break;
3778 case 2:
3779 cliPrintLine("KISS16A");
3781 break;
3782 case 3:
3783 cliPrintLine("KISS24A");
3785 break;
3786 case 5:
3787 cliPrintLine("KISS Ultralite");
3789 break;
3790 default:
3791 cliPrintLine("unknown");
3793 break;
3796 break;
3797 case ESC_INFO_BLHELI32:
3799 char *escType = (char *)(escInfoBuffer + 31);
3800 escType[32] = 0;
3801 cliPrintLine(escType);
3804 break;
3807 cliPrint("MCU Serial No: 0x");
3808 for (int i = 0; i < 12; i++) {
3809 if (i && (i % 3 == 0)) {
3810 cliPrint("-");
3812 cliPrintf("%02x", escInfoBuffer[i]);
3814 cliPrintLinefeed();
3816 switch (escInfoVersion) {
3817 case ESC_INFO_KISS_V1:
3818 case ESC_INFO_KISS_V2:
3819 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3821 break;
3822 case ESC_INFO_BLHELI32:
3823 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3825 break;
3827 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3828 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3829 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3830 if (escInfoVersion == ESC_INFO_BLHELI32) {
3831 uint8_t setting = escInfoBuffer[18];
3832 cliPrint("Low voltage Limit: ");
3833 switch (setting) {
3834 case 0:
3835 cliPrintLine("off");
3837 break;
3838 case 255:
3839 cliPrintLine("unsupported");
3841 break;
3842 default:
3843 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3845 break;
3848 setting = escInfoBuffer[19];
3849 cliPrint("Current Limit: ");
3850 switch (setting) {
3851 case 0:
3852 cliPrintLine("off");
3854 break;
3855 case 255:
3856 cliPrintLine("unsupported");
3858 break;
3859 default:
3860 cliPrintLinef("%d", setting);
3862 break;
3865 for (int i = 0; i < 4; i++) {
3866 setting = escInfoBuffer[i + 20];
3867 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3871 } else {
3872 cliPrintErrorLinef(cmdName, "CHECKSUM ERROR.");
3877 if (!escInfoReceived) {
3878 cliPrintLine("No Info.");
3882 static void executeEscInfoCommand(const char *cmdName, uint8_t escIndex)
3884 cliPrintLinef("Info for ESC %d:", escIndex);
3886 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3888 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3890 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, DSHOT_CMD_TYPE_BLOCKING);
3892 delay(10);
3894 printEscInfo(cmdName, escInfoBuffer, getNumberEscBytesRead());
3896 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3898 static void cliDshotProg(const char *cmdName, char *cmdline)
3900 if (isEmpty(cmdline) || !isMotorProtocolDshot()) {
3901 cliShowParseError(cmdName);
3903 return;
3906 char *saveptr;
3907 char *pch = strtok_r(cmdline, " ", &saveptr);
3908 int pos = 0;
3909 int escIndex = 0;
3910 bool firstCommand = true;
3911 while (pch != NULL) {
3912 switch (pos) {
3913 case 0:
3914 escIndex = parseOutputIndex(cmdName, pch, true);
3915 if (escIndex == -1) {
3916 return;
3919 break;
3920 default:
3922 int command = atoi(pch);
3923 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3924 if (firstCommand) {
3925 // pwmDisableMotors();
3926 motorDisable();
3928 firstCommand = false;
3931 if (command != DSHOT_CMD_ESC_INFO) {
3932 dshotCommandWrite(escIndex, getMotorCount(), command, DSHOT_CMD_TYPE_BLOCKING);
3933 } else {
3934 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3935 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3936 if (escIndex != ALL_MOTORS) {
3937 executeEscInfoCommand(cmdName, escIndex);
3938 } else {
3939 for (uint8_t i = 0; i < getMotorCount(); i++) {
3940 executeEscInfoCommand(cmdName, i);
3943 } else
3944 #endif
3946 cliPrintLine("Not supported.");
3950 cliPrintLinef("Command Sent: %d", command);
3952 } else {
3953 cliPrintErrorLinef(cmdName, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3957 break;
3960 pos++;
3961 pch = strtok_r(NULL, " ", &saveptr);
3964 motorEnable();
3966 #endif // USE_DSHOT
3968 #ifdef USE_ESCSERIAL
3969 static void cliEscPassthrough(const char *cmdName, char *cmdline)
3971 if (isEmpty(cmdline)) {
3972 cliShowInvalidArgumentCountError(cmdName);
3974 return;
3977 char *saveptr;
3978 char *pch = strtok_r(cmdline, " ", &saveptr);
3979 int pos = 0;
3980 uint8_t mode = 0;
3981 int escIndex = 0;
3982 while (pch != NULL) {
3983 switch (pos) {
3984 case 0:
3985 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3986 mode = PROTOCOL_SIMONK;
3987 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3988 mode = PROTOCOL_BLHELI;
3989 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3990 mode = PROTOCOL_KISS;
3991 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3992 mode = PROTOCOL_CASTLE;
3993 } else {
3994 cliShowParseError(cmdName);
3996 return;
3998 break;
3999 case 1:
4000 escIndex = parseOutputIndex(cmdName, pch, mode == PROTOCOL_KISS);
4001 if (escIndex == -1) {
4002 return;
4005 break;
4006 default:
4007 cliShowInvalidArgumentCountError(cmdName);
4009 return;
4011 break;
4014 pos++;
4015 pch = strtok_r(NULL, " ", &saveptr);
4018 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
4019 cliPrintErrorLinef(cmdName, "Error starting ESC connection");
4022 #endif
4024 #ifndef USE_QUAD_MIXER_ONLY
4025 static void cliMixer(const char *cmdName, char *cmdline)
4027 int len;
4029 len = strlen(cmdline);
4031 if (len == 0) {
4032 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
4033 return;
4034 } else if (strncasecmp(cmdline, "list", len) == 0) {
4035 cliPrint("Available:");
4036 for (uint32_t i = 0; ; i++) {
4037 if (mixerNames[i] == NULL)
4038 break;
4039 cliPrintf(" %s", mixerNames[i]);
4041 cliPrintLinefeed();
4042 return;
4045 for (uint32_t i = 0; ; i++) {
4046 if (mixerNames[i] == NULL) {
4047 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
4048 return;
4050 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
4051 mixerConfigMutable()->mixerMode = i + 1;
4052 break;
4056 cliMixer(cmdName, "");
4058 #endif
4060 static void cliMotor(const char *cmdName, char *cmdline)
4062 if (isEmpty(cmdline)) {
4063 cliShowInvalidArgumentCountError(cmdName);
4065 return;
4068 int motorIndex = 0;
4069 int motorValue = 0;
4071 char *saveptr;
4072 char *pch = strtok_r(cmdline, " ", &saveptr);
4073 int index = 0;
4074 while (pch != NULL) {
4075 switch (index) {
4076 case 0:
4077 motorIndex = parseOutputIndex(cmdName, pch, true);
4078 if (motorIndex == -1) {
4079 return;
4082 break;
4083 case 1:
4084 motorValue = atoi(pch);
4086 break;
4088 index++;
4089 pch = strtok_r(NULL, " ", &saveptr);
4092 if (index == 2) {
4093 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
4094 cliShowArgumentRangeError(cmdName, "VALUE", 1000, 2000);
4095 } else {
4096 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
4098 if (motorIndex != ALL_MOTORS) {
4099 motor_disarmed[motorIndex] = motorOutputValue;
4101 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
4102 } else {
4103 for (int i = 0; i < getMotorCount(); i++) {
4104 motor_disarmed[i] = motorOutputValue;
4107 cliPrintLinef("all motors: %d", motorOutputValue);
4110 } else {
4111 cliShowInvalidArgumentCountError(cmdName);
4115 #ifndef MINIMAL_CLI
4116 static void cliPlaySound(const char *cmdName, char *cmdline)
4118 int i;
4119 const char *name;
4120 static int lastSoundIdx = -1;
4122 if (isEmpty(cmdline)) {
4123 i = lastSoundIdx + 1; //next sound index
4124 if ((name=beeperNameForTableIndex(i)) == NULL) {
4125 while (true) { //no name for index; try next one
4126 if (++i >= beeperTableEntryCount())
4127 i = 0; //if end then wrap around to first entry
4128 if ((name=beeperNameForTableIndex(i)) != NULL)
4129 break; //if name OK then play sound below
4130 if (i == lastSoundIdx + 1) { //prevent infinite loop
4131 cliPrintErrorLinef(cmdName, "ERROR PLAYING SOUND");
4132 return;
4136 } else { //index value was given
4137 i = atoi(cmdline);
4138 if ((name=beeperNameForTableIndex(i)) == NULL) {
4139 cliPrintLinef("No sound for index %d", i);
4140 return;
4143 lastSoundIdx = i;
4144 beeperSilence();
4145 cliPrintLinef("Playing sound %d: %s", i, name);
4146 beeper(beeperModeForTableIndex(i));
4148 #endif
4150 static void cliProfile(const char *cmdName, char *cmdline)
4152 if (isEmpty(cmdline)) {
4153 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4154 return;
4155 } else {
4156 const int i = atoi(cmdline);
4157 if (i >= 0 && i < PID_PROFILE_COUNT) {
4158 changePidProfile(i);
4159 cliProfile(cmdName, "");
4160 } else {
4161 cliPrintErrorLinef(cmdName, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
4166 static void cliRateProfile(const char *cmdName, char *cmdline)
4168 if (isEmpty(cmdline)) {
4169 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4170 return;
4171 } else {
4172 const int i = atoi(cmdline);
4173 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
4174 changeControlRateProfile(i);
4175 cliRateProfile(cmdName, "");
4176 } else {
4177 cliPrintErrorLinef(cmdName, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
4182 static void cliDumpPidProfile(const char *cmdName, uint8_t pidProfileIndex, dumpFlags_t dumpMask)
4184 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4185 // Faulty values
4186 return;
4189 pidProfileIndexToUse = pidProfileIndex;
4191 cliPrintLinefeed();
4192 cliProfile(cmdName, "");
4194 char profileStr[10];
4195 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4196 dumpAllValues(cmdName, PROFILE_VALUE, dumpMask, profileStr);
4198 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4201 static void cliDumpRateProfile(const char *cmdName, uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4203 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4204 // Faulty values
4205 return;
4208 rateProfileIndexToUse = rateProfileIndex;
4210 cliPrintLinefeed();
4211 cliRateProfile(cmdName, "");
4213 char rateProfileStr[14];
4214 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4215 dumpAllValues(cmdName, PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4217 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4220 #ifdef USE_CLI_BATCH
4221 static void cliPrintCommandBatchWarning(const char *cmdName, const char *warning)
4223 cliPrintErrorLinef(cmdName, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4224 if (warning) {
4225 cliPrintErrorLinef(cmdName, warning);
4229 static void resetCommandBatch(void)
4231 commandBatchActive = false;
4232 commandBatchError = false;
4235 static void cliBatch(const char *cmdName, char *cmdline)
4237 if (strncasecmp(cmdline, "start", 5) == 0) {
4238 if (!commandBatchActive) {
4239 commandBatchActive = true;
4240 commandBatchError = false;
4242 cliPrintLine("Command batch started");
4243 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4244 if (commandBatchActive && commandBatchError) {
4245 cliPrintCommandBatchWarning(cmdName, NULL);
4246 } else {
4247 cliPrintLine("Command batch ended");
4249 resetCommandBatch();
4250 } else {
4251 cliPrintErrorLinef(cmdName, "Invalid option");
4254 #endif
4256 static bool prepareSave(void)
4259 #ifdef USE_CLI_BATCH
4260 if (commandBatchActive && commandBatchError) {
4261 return false;
4263 #endif
4265 #if defined(USE_BOARD_INFO)
4266 if (boardInformationUpdated) {
4267 persistBoardInformation();
4269 #if defined(USE_SIGNATURE)
4270 if (signatureUpdated) {
4271 persistSignature();
4273 #endif
4274 #endif // USE_BOARD_INFO
4276 return true;
4279 bool tryPrepareSave(const char *cmdName)
4281 bool success = prepareSave();
4282 #if defined(USE_CLI_BATCH)
4283 if (!success) {
4284 cliPrintCommandBatchWarning(cmdName, "PLEASE FIX ERRORS THEN 'SAVE'");
4285 resetCommandBatch();
4287 return false;
4289 #else
4290 UNUSED(cmdName);
4291 UNUSED(success);
4292 #endif
4294 return true;
4297 static void cliSave(const char *cmdName, char *cmdline)
4299 UNUSED(cmdline);
4301 if (tryPrepareSave(cmdName)) {
4302 writeEEPROM();
4303 cliPrintHashLine("saving");
4305 if (strcasecmp(cmdline, "noreboot") == 0) {
4306 return;
4308 cliReboot();
4312 static void cliDefaults(const char *cmdName, char *cmdline)
4314 bool saveConfigs = true;
4315 uint16_t parameterGroupId = 0;
4317 char *saveptr;
4318 char* tok = strtok_r(cmdline, " ", &saveptr);
4319 bool expectParameterGroupId = false;
4320 while (tok != NULL) {
4321 if (expectParameterGroupId) {
4322 parameterGroupId = atoi(tok);
4323 expectParameterGroupId = false;
4325 if (!parameterGroupId) {
4326 cliShowParseError(cmdName);
4327 return;
4329 } else if (strcasestr(tok, "group_id")) {
4330 expectParameterGroupId = true;
4331 } else if (strcasestr(tok, "nosave")) {
4332 saveConfigs = false;
4333 } else {
4334 cliShowParseError(cmdName);
4336 return;
4339 tok = strtok_r(NULL, " ", &saveptr);
4342 if (expectParameterGroupId) {
4343 cliShowParseError(cmdName);
4345 return;
4348 if (parameterGroupId) {
4349 cliPrintLinef("\r\n# resetting group %d to defaults", parameterGroupId);
4350 backupConfigs();
4351 } else {
4352 cliPrintHashLine("resetting to defaults");
4355 resetConfig();
4357 #ifdef USE_CLI_BATCH
4358 // Reset only the error state and allow the batch active state to remain.
4359 // This way if a "defaults nosave" was issued after the "batch on" we'll
4360 // only reset the current error state but the batch will still be active
4361 // for subsequent commands.
4362 commandBatchError = false;
4363 #endif
4365 #if defined(USE_SIMPLIFIED_TUNING)
4366 applySimplifiedTuningAllProfiles();
4367 #endif
4369 if (parameterGroupId) {
4370 restoreConfigs(parameterGroupId);
4373 if (saveConfigs && tryPrepareSave(cmdName)) {
4374 writeUnmodifiedConfigToEEPROM();
4376 cliReboot();
4380 static void cliPrintVarDefault(const char *cmdName, const clivalue_t *value)
4382 const pgRegistry_t *pg = pgFind(value->pgn);
4383 if (pg) {
4384 const char *defaultFormat = "Default value: ";
4385 const int valueOffset = getValueOffset(value);
4386 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
4387 if (!equalsDefault) {
4388 cliPrintf(defaultFormat, value->name);
4389 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
4390 cliPrintLinefeed();
4395 STATIC_UNIT_TESTED void cliGet(const char *cmdName, char *cmdline)
4397 const clivalue_t *val;
4398 int matchedCommands = 0;
4400 pidProfileIndexToUse = getCurrentPidProfileIndex();
4401 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4403 backupAndResetConfigs();
4405 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4406 if (strcasestr(valueTable[i].name, cmdline)) {
4407 val = &valueTable[i];
4408 if (matchedCommands > 0) {
4409 cliPrintLinefeed();
4411 cliPrintf("%s = ", valueTable[i].name);
4412 cliPrintVar(cmdName, val, 0);
4413 cliPrintLinefeed();
4414 switch (val->type & VALUE_SECTION_MASK) {
4415 case PROFILE_VALUE:
4416 cliProfile(cmdName, "");
4418 break;
4419 case PROFILE_RATE_VALUE:
4420 cliRateProfile(cmdName, "");
4422 break;
4423 default:
4425 break;
4427 cliPrintVarRange(val);
4428 cliPrintVarDefault(cmdName, val);
4430 matchedCommands++;
4434 restoreConfigs(0);
4436 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4437 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4439 if (!matchedCommands) {
4440 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
4444 static uint8_t getWordLength(const char *bufBegin, const char *bufEnd)
4446 while (*(bufEnd - 1) == ' ') {
4447 bufEnd--;
4450 return bufEnd - bufBegin;
4453 uint16_t cliGetSettingIndex(const char *name, size_t length)
4455 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4456 const char *settingName = valueTable[i].name;
4458 // ensure exact match when setting to prevent setting variables with longer names
4459 if (strncasecmp(name, settingName, length) == 0 && length == strlen(settingName)) {
4460 return i;
4463 return valueTableEntryCount;
4466 STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
4468 const uint32_t len = strlen(cmdline);
4469 char *eqptr;
4471 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4472 cliPrintLine("Current settings: ");
4474 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4475 const clivalue_t *val = &valueTable[i];
4476 cliPrintf("%s = ", valueTable[i].name);
4477 cliPrintVar(cmdName, val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4478 cliPrintLinefeed();
4480 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4481 // has equals
4483 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4485 // skip the '=' and any ' ' characters
4486 eqptr++;
4487 eqptr = skipSpace(eqptr);
4489 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4490 if (index >= valueTableEntryCount) {
4491 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
4492 return;
4494 const clivalue_t *val = &valueTable[index];
4496 bool valueChanged = false;
4497 int16_t value = 0;
4498 switch (val->type & VALUE_MODE_MASK) {
4499 case MODE_DIRECT: {
4500 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4501 uint32_t value = strtoul(eqptr, NULL, 10);
4503 if (value <= val->config.u32Max) {
4504 cliSetVar(val, value);
4505 valueChanged = true;
4507 } else if ((val->type & VALUE_TYPE_MASK) == VAR_INT32) {
4508 int32_t value = strtol(eqptr, NULL, 10);
4510 // INT32s are limited to being symmetric, so we test both bounds with the same magnitude
4511 if (value <= val->config.d32Max && value >= -val->config.d32Max) {
4512 cliSetVar(val, value);
4513 valueChanged = true;
4515 } else {
4516 int value = atoi(eqptr);
4518 int min;
4519 int max;
4520 getMinMax(val, &min, &max);
4522 if (value >= min && value <= max) {
4523 cliSetVar(val, value);
4524 valueChanged = true;
4529 break;
4530 case MODE_LOOKUP:
4531 case MODE_BITSET: {
4532 int tableIndex;
4533 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4534 tableIndex = TABLE_OFF_ON;
4535 } else {
4536 tableIndex = val->config.lookup.tableIndex;
4538 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4539 bool matched = false;
4540 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4541 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4543 if (matched) {
4544 value = tableValueIndex;
4546 cliSetVar(val, value);
4547 valueChanged = true;
4552 break;
4554 case MODE_ARRAY: {
4555 const uint8_t arrayLength = val->config.array.length;
4556 char *valPtr = eqptr;
4558 int i = 0;
4559 while (i < arrayLength && valPtr != NULL) {
4560 // skip spaces
4561 valPtr = skipSpace(valPtr);
4563 // process substring starting at valPtr
4564 // note: no need to copy substrings for atoi()
4565 // it stops at the first character that cannot be converted...
4566 switch (val->type & VALUE_TYPE_MASK) {
4567 default:
4568 case VAR_UINT8:
4570 // fetch data pointer
4571 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4572 // store value
4573 *data = (uint8_t)atoi((const char*) valPtr);
4576 break;
4577 case VAR_INT8:
4579 // fetch data pointer
4580 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4581 // store value
4582 *data = (int8_t)atoi((const char*) valPtr);
4585 break;
4586 case VAR_UINT16:
4588 // fetch data pointer
4589 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4590 // store value
4591 *data = (uint16_t)atoi((const char*) valPtr);
4594 break;
4595 case VAR_INT16:
4597 // fetch data pointer
4598 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4599 // store value
4600 *data = (int16_t)atoi((const char*) valPtr);
4603 break;
4604 case VAR_UINT32:
4606 // fetch data pointer
4607 uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
4608 // store value
4609 *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
4612 break;
4613 case VAR_INT32:
4615 // fetch data pointer
4616 int32_t *data = (int32_t *)cliGetValuePointer(val) + i;
4617 // store value
4618 *data = (int32_t)strtol((const char*) valPtr, NULL, 10);
4621 break;
4624 // find next comma (or end of string)
4625 valPtr = strchr(valPtr, ',') + 1;
4627 i++;
4631 // mark as changed
4632 valueChanged = true;
4634 break;
4635 case MODE_STRING: {
4636 char *valPtr = eqptr;
4637 valPtr = skipSpace(valPtr);
4639 const unsigned int len = strlen(valPtr);
4640 const uint8_t min = val->config.string.minlength;
4641 const uint8_t max = val->config.string.maxlength;
4642 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4643 strlen((char *)cliGetValuePointer(val)) == 0 ||
4644 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4646 if (updatable && len > 0 && len <= max) {
4647 memset((char *)cliGetValuePointer(val), 0, max);
4648 if (len >= min && strncmp(valPtr, emptyName, len)) {
4649 memcpy((char *)cliGetValuePointer(val), valPtr, len);
4651 valueChanged = true;
4652 } else {
4653 cliPrintErrorLinef(cmdName, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4656 break;
4659 if (valueChanged) {
4660 cliPrintf("%s set to ", val->name);
4661 cliPrintVar(cmdName, val, 0);
4662 } else {
4663 cliPrintErrorLinef(cmdName, "INVALID VALUE");
4664 cliPrintVarRange(val);
4667 return;
4668 } else {
4669 // no equals, check for matching variables.
4670 cliGet(cmdName, cmdline);
4674 static const char *getMcuTypeById(mcuTypeId_e id)
4676 if (id < ARRAYLEN(mcuTypeNames)) {
4677 return mcuTypeNames[id];
4678 } else {
4679 return "UNKNOWN";
4683 static void cliStatus(const char *cmdName, char *cmdline)
4685 UNUSED(cmdName);
4686 UNUSED(cmdline);
4688 // MCU type, clock, vrefint, core temperature
4690 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock / 1000000));
4692 #if defined(STM32F4) || defined(STM32G4) || defined(APM32F4)
4693 // Only F4 and G4 is capable of switching between HSE/HSI (for now)
4694 int sysclkSource = SystemSYSCLKSource();
4696 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4697 const char *PLLSource[] = { "-HSI", "-HSE" };
4699 int pllSource;
4701 if (sysclkSource >= 2) {
4702 pllSource = SystemPLLSource();
4705 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4706 #endif
4708 #ifdef USE_ADC_INTERNAL
4709 uint16_t vrefintMv = getVrefMv();
4710 int16_t coretemp = getCoreTemperatureCelsius();
4711 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4712 #else
4713 cliPrintLinefeed();
4714 #endif
4716 // Stack and config sizes and usages
4718 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4719 #ifdef USE_STACK_CHECK
4720 cliPrintf(", Stack used: %d", stackUsedSize());
4721 #endif
4722 cliPrintLinefeed();
4724 cliPrintLinef("Configuration: %s, size: %d, max available: %d", configurationStates[systemConfigMutable()->configurationState], getEEPROMConfigSize(), getEEPROMStorageSize());
4726 // Devices
4727 #if defined(USE_SPI) || defined(USE_I2C)
4728 cliPrint("Devices detected:");
4729 #if defined(USE_SPI)
4730 cliPrintf(" SPI:%d", spiGetRegisteredDeviceCount());
4731 #if defined(USE_I2C)
4732 cliPrint(",");
4733 #endif
4734 #endif
4735 #if defined(USE_I2C)
4736 cliPrintf(" I2C:%d", i2cGetRegisteredDeviceCount());
4737 #endif
4738 cliPrintLinefeed();
4739 #endif
4741 // Sensors
4742 cliPrint("Gyros detected:");
4743 bool found = false;
4744 for (unsigned pos = 0; pos < 7; pos++) {
4745 if (gyroConfig()->gyrosDetected & BIT(pos)) {
4746 if (found) {
4747 cliPrint(",");
4748 } else {
4749 found = true;
4751 cliPrintf(" gyro %d", pos + 1);
4754 #ifdef USE_SPI
4755 if (gyroActiveDev()->gyroModeSPI != GYRO_EXTI_NO_INT) {
4756 cliPrintf(" locked");
4758 if (gyroActiveDev()->gyroModeSPI == GYRO_EXTI_INT_DMA) {
4759 cliPrintf(" dma");
4761 if (spiGetExtDeviceCount(&gyroActiveDev()->dev) > 1) {
4762 cliPrintf(" shared");
4764 #endif
4765 cliPrintLinefeed();
4767 #if defined(USE_SENSOR_NAMES)
4768 const uint32_t detectedSensorsMask = sensorsMask();
4769 for (uint32_t i = 0; ; i++) {
4770 if (sensorTypeNames[i] == NULL) {
4771 break;
4773 const uint32_t mask = (1 << i);
4774 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4775 const uint8_t sensorHardwareIndex = detectedSensors[i];
4776 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4777 if (i) {
4778 cliPrint(", ");
4780 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4781 #if defined(USE_ACC)
4782 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4783 cliPrintf(".%c", acc.dev.revisionCode);
4785 #endif
4788 cliPrintLinefeed();
4789 #endif /* USE_SENSOR_NAMES */
4791 #if defined(USE_OSD)
4792 osdDisplayPortDevice_e displayPortDeviceType;
4793 displayPort_t *osdDisplayPort = osdGetDisplayPort(&displayPortDeviceType);
4795 cliPrintLinef("OSD: %s (%u x %u)", lookupTableOsdDisplayPortDevice[displayPortDeviceType], osdDisplayPort->cols, osdDisplayPort->rows);
4796 #endif
4798 if (buildKey) {
4799 cliPrintf("BUILD KEY: %s", buildKey);
4800 if (releaseName) {
4801 cliPrintf(" (%s)", releaseName);
4803 cliPrintLinefeed();
4805 // Uptime and wall clock
4807 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4809 #ifdef USE_RTC_TIME
4810 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4811 dateTime_t dt;
4812 if (rtcGetDateTime(&dt)) {
4813 dateTimeFormatLocal(buf, &dt);
4814 cliPrintf(", Current Time: %s", buf);
4816 #endif
4817 cliPrintLinefeed();
4819 // Run status
4821 const int gyroRate = getTaskDeltaTimeUs(TASK_GYRO) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_GYRO)));
4823 int rxRate = getRxRateValid() ? getCurrentRxRateHz() : 0;
4825 const int systemRate = getTaskDeltaTimeUs(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_SYSTEM)));
4826 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4827 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE), getTaskDeltaTimeUs(TASK_GYRO), gyroRate, rxRate, systemRate);
4829 // Battery meter
4831 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4833 // Other devices and status
4835 #ifdef USE_I2C
4836 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4837 #else
4838 const uint16_t i2cErrorCounter = 0;
4839 #endif
4840 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4842 #ifdef USE_SDCARD
4843 cliSdInfo(cmdName, "");
4844 #endif
4846 #ifdef USE_FLASH_CHIP
4847 const flashGeometry_t *layout = flashGetGeometry();
4848 if (layout->jedecId != 0) {
4849 cliPrintLinef("FLASH: JEDEC ID=0x%08x %uM", layout->jedecId, layout->totalSize >> 20);
4851 #endif
4853 #ifdef USE_GPS
4854 cliPrint("GPS: ");
4855 if (featureIsEnabled(FEATURE_GPS)) {
4856 if (gpsData.state >= GPS_STATE_CONFIGURE) {
4857 cliPrint("connected, ");
4858 } else {
4859 cliPrint("NOT CONNECTED, ");
4861 if (gpsConfig()->provider == GPS_MSP) {
4862 cliPrint("MSP, ");
4863 } else {
4864 const serialPortConfig_t *gpsPortConfig = findSerialPortConfig(FUNCTION_GPS);
4865 if (!gpsPortConfig) {
4866 cliPrint("NO PORT, ");
4867 } else {
4868 cliPrintf("UART%d %ld (set to ", (gpsPortConfig->identifier + 1), baudRates[getGpsPortActualBaudRateIndex()]);
4869 if (gpsConfig()->autoBaud == GPS_AUTOBAUD_ON) {
4870 cliPrint("AUTO");
4871 } else {
4872 cliPrintf("%ld", baudRates[gpsPortConfig->gps_baudrateIndex]);
4874 cliPrint("), ");
4877 if (gpsData.state <= GPS_STATE_CONFIGURE) {
4878 cliPrint("NOT CONFIGURED");
4879 } else {
4880 if (gpsConfig()->autoConfig == GPS_AUTOCONFIG_OFF) {
4881 cliPrint("auto config OFF");
4882 } else {
4883 cliPrint("configured");
4886 cliPrintf(", version = %s", gpsData.platformVersion != UBX_VERSION_UNDEF ? ubloxVersionMap[gpsData.platformVersion].str : "unknown");
4887 } else {
4888 cliPrint("NOT ENABLED");
4890 cliPrintLinefeed();
4891 #endif // USE_GPS
4893 cliPrint("Arming disable flags:");
4894 armingDisableFlags_e flags = getArmingDisableFlags();
4895 while (flags) {
4896 const int bitpos = ffs(flags) - 1;
4897 flags &= ~(1 << bitpos);
4898 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4900 cliPrintLinefeed();
4903 static void cliTasks(const char *cmdName, char *cmdline)
4905 UNUSED(cmdName);
4906 UNUSED(cmdline);
4907 int averageLoadSum = 0;
4909 #ifndef MINIMAL_CLI
4910 if (systemConfig()->task_statistics) {
4911 #if defined(USE_LATE_TASK_STATISTICS)
4912 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms late run reqd/us");
4913 #else
4914 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4915 #endif
4916 } else {
4917 cliPrintLine("Task list");
4919 #endif
4920 for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4921 taskInfo_t taskInfo;
4922 getTaskInfo(taskId, &taskInfo);
4923 if (taskInfo.isEnabled) {
4924 int taskFrequency = taskInfo.averageDeltaTime10thUs == 0 ? 0 : lrintf(1e7f / taskInfo.averageDeltaTime10thUs);
4925 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4926 const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 : (taskInfo.maxExecutionTimeUs * taskFrequency) / 1000;
4927 const int averageLoad = taskInfo.averageExecutionTime10thUs == 0 ? 0 : (taskInfo.averageExecutionTime10thUs * taskFrequency) / 10000;
4928 if (taskId != TASK_SERIAL) {
4929 averageLoadSum += averageLoad;
4931 if (systemConfig()->task_statistics) {
4932 #if defined(USE_LATE_TASK_STATISTICS)
4933 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d %6d %6d %7d",
4934 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
4935 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4936 taskInfo.totalExecutionTimeUs / 1000,
4937 taskInfo.lateCount, taskInfo.runCount, taskInfo.execTime);
4938 #else
4939 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4940 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
4941 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4942 taskInfo.totalExecutionTimeUs / 1000);
4943 #endif
4944 } else {
4945 cliPrintLinef("%6d", taskFrequency);
4948 schedulerResetTaskMaxExecutionTime(taskId);
4951 if (systemConfig()->task_statistics) {
4952 cfCheckFuncInfo_t checkFuncInfo;
4953 getCheckFuncInfo(&checkFuncInfo);
4954 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTimeUs, checkFuncInfo.averageExecutionTimeUs, checkFuncInfo.totalExecutionTimeUs / 1000);
4955 cliPrintLinef("Total (excluding SERIAL) %33d.%1d%%", averageLoadSum/10, averageLoadSum%10);
4956 if (debugMode == DEBUG_SCHEDULER_DETERMINISM) {
4957 extern int32_t schedLoopStartCycles, taskGuardCycles;
4959 cliPrintLinef("Scheduler start cycles %d guard cycles %d", schedLoopStartCycles, taskGuardCycles);
4961 schedulerResetCheckFunctionMaxExecutionTime();
4965 static void printVersion(bool printBoardInfo)
4967 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4968 FC_FIRMWARE_NAME,
4969 targetName,
4970 systemConfig()->boardIdentifier,
4971 FC_VERSION_STRING,
4972 buildDate,
4973 buildTime,
4974 shortGitRevision,
4975 MSP_API_VERSION_STRING
4978 cliPrintLinefeed();
4980 #if defined(__CONFIG_REVISION__)
4981 cliPrintLinef("# config rev: %s", shortConfigGitRevision);
4982 #endif
4984 #if defined(USE_BOARD_INFO)
4985 if (printBoardInfo && strlen(getManufacturerId()) && strlen(getBoardName())) {
4986 cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
4988 #else
4989 UNUSED(printBoardInfo);
4990 #endif
4993 static void cliVersion(const char *cmdName, char *cmdline)
4995 UNUSED(cmdName);
4996 UNUSED(cmdline);
4998 printVersion(true);
5001 #ifdef USE_RC_SMOOTHING_FILTER
5002 static void cliRcSmoothing(const char *cmdName, char *cmdline)
5004 UNUSED(cmdName);
5005 UNUSED(cmdline);
5006 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
5007 cliPrint("# RC Smoothing Type: ");
5008 if (rxConfig()->rc_smoothing_mode) {
5009 cliPrintLine("FILTER");
5010 if (rcSmoothingAutoCalculate()) {
5011 cliPrint("# Detected Rx frequency: ");
5012 if (getRxRateValid()) {
5013 cliPrintLinef("%dHz", lrintf(rcSmoothingData->smoothedRxRateHz));
5014 } else {
5015 cliPrintLine("NO SIGNAL");
5018 cliPrintf("# Active setpoint cutoff: %dhz ", rcSmoothingData->setpointCutoffFrequency);
5019 if (rcSmoothingData->setpointCutoffSetting) {
5020 cliPrintLine("(manual)");
5021 } else {
5022 cliPrintLine("(auto)");
5024 cliPrintf("# Active FF cutoff: %dhz ", rcSmoothingData->feedforwardCutoffFrequency);
5025 if (rcSmoothingData->feedforwardCutoffSetting) {
5026 cliPrintLine("(manual)");
5027 } else {
5028 cliPrintLine("(auto)");
5030 cliPrintf("# Active throttle cutoff: %dhz ", rcSmoothingData->throttleCutoffFrequency);
5031 if (rcSmoothingData->throttleCutoffSetting) {
5032 cliPrintLine("(manual)");
5033 } else {
5034 cliPrintLine("(auto)");
5036 } else {
5037 cliPrintLine("OFF");
5040 #endif // USE_RC_SMOOTHING_FILTER
5042 #if defined(USE_RESOURCE_MGMT)
5044 #define RESOURCE_VALUE_MAX_INDEX(x) ((x) == 0 ? 1 : (x))
5046 typedef struct {
5047 const uint8_t owner;
5048 pgn_t pgn;
5049 uint8_t stride;
5050 uint8_t offset;
5051 const uint8_t maxIndex;
5052 } cliResourceValue_t;
5054 // Handy macros for keeping the table tidy.
5055 // DEFS : Single entry
5056 // DEFA : Array of uint8_t (stride = 1)
5057 // DEFW : Wider stride case; array of structs.
5059 #define DEFS(owner, pgn, type, member) \
5060 { owner, pgn, 0, offsetof(type, member), 0 }
5062 #define DEFA(owner, pgn, type, member, max) \
5063 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
5065 #define DEFW(owner, pgn, type, member, max) \
5066 { owner, pgn, sizeof(type), offsetof(type, member), max }
5068 const cliResourceValue_t resourceTable[] = {
5069 #if defined(USE_BEEPER)
5070 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag ),
5071 #endif
5072 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
5073 #if defined(USE_SERVOS)
5074 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
5075 #endif
5076 #if defined(USE_RX_PPM)
5077 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
5078 #endif
5079 #if defined(USE_RX_PWM)
5080 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
5081 #endif
5082 #if defined(USE_RANGEFINDER_HCSR04)
5083 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
5084 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
5085 #endif
5086 #if defined(USE_LED_STRIP)
5087 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
5088 #endif
5089 #if defined(USE_UART)
5090 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
5091 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
5092 #endif
5093 #if defined(USE_INVERTER)
5094 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
5095 // LPUART and SOFTSERIAL don't need external inversion
5096 #endif
5097 #if defined(USE_SOFTSERIAL)
5098 DEFA( OWNER_SOFTSERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_SOFTSERIAL_OFFSET], RESOURCE_SOFTSERIAL_COUNT ),
5099 DEFA( OWNER_SOFTSERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_SOFTSERIAL_OFFSET], RESOURCE_SOFTSERIAL_COUNT ),
5100 #endif
5101 #if defined(USE_LPUART)
5102 DEFA( OWNER_LPUART_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_LPUART_OFFSET], RESOURCE_LPUART_COUNT ),
5103 DEFA( OWNER_LPUART_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_LPUART_OFFSET], RESOURCE_LPUART_COUNT ),
5104 #endif
5105 #ifdef USE_I2C
5106 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
5107 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
5108 #endif
5109 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
5110 #ifdef USE_SPEKTRUM_BIND
5111 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
5112 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
5113 #endif
5114 #ifdef USE_TRANSPONDER
5115 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
5116 #endif
5117 #ifdef USE_SPI
5118 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
5119 DEFW( OWNER_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
5120 DEFW( OWNER_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
5121 #endif
5122 #ifdef USE_ESCSERIAL
5123 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
5124 #endif
5125 #ifdef USE_CAMERA_CONTROL
5126 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
5127 #endif
5128 #ifdef USE_ADC
5129 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
5130 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
5131 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
5132 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
5133 #endif
5134 #ifdef USE_BARO
5135 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
5136 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
5137 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
5138 #endif
5139 #ifdef USE_MAG
5140 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
5141 #ifdef USE_MAG_DATA_READY_SIGNAL
5142 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
5143 #endif
5144 #endif
5145 #ifdef USE_SDCARD_SPI
5146 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
5147 #endif
5148 #ifdef USE_SDCARD
5149 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
5150 #endif
5151 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
5152 DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
5153 DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
5154 DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
5155 DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
5156 DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
5157 DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
5158 #endif
5159 #ifdef USE_PINIO
5160 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
5161 #endif
5162 #if defined(USE_USB_MSC)
5163 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
5164 #endif
5165 #ifdef USE_FLASH_CHIP
5166 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
5167 #endif
5168 #ifdef USE_MAX7456
5169 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
5170 #endif
5171 #ifdef USE_RX_SPI
5172 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
5173 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
5174 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
5175 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
5176 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
5177 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
5178 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
5179 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
5180 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
5181 #endif
5182 #endif
5183 #if defined(USE_RX_EXPRESSLRS)
5184 DEFS( OWNER_RX_SPI_EXPRESSLRS_RESET, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, resetIoTag ),
5185 DEFS( OWNER_RX_SPI_EXPRESSLRS_BUSY, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, busyIoTag ),
5186 #endif
5187 #endif
5188 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
5189 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
5190 #if defined(USE_GYRO_CLKIN)
5191 DEFW( OWNER_GYRO_CLKIN, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, clkIn, MAX_GYRODEV_COUNT),
5192 #endif
5193 #ifdef USE_USB_DETECT
5194 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
5195 #endif
5196 #ifdef USE_VTX_RTC6705
5197 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
5198 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
5199 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
5200 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
5201 #endif
5202 #ifdef USE_PIN_PULL_UP_DOWN
5203 DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5204 DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5205 #endif
5208 #undef DEFS
5209 #undef DEFA
5210 #undef DEFW
5212 static ioTag_t* getIoTag(const cliResourceValue_t value, uint8_t index)
5214 const pgRegistry_t* rec = pgFind(value.pgn);
5215 return (ioTag_t *)(rec->address + value.stride * index + value.offset);
5218 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
5220 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5221 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
5222 const char* owner = ownerNames[resourceTable[i].owner];
5223 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
5224 const void *currentConfig;
5225 const void *defaultConfig;
5226 if (isReadingConfigFromCopy()) {
5227 currentConfig = pg->copy;
5228 defaultConfig = pg->address;
5229 } else {
5230 currentConfig = pg->address;
5231 defaultConfig = NULL;
5234 for (int index = 0; index < RESOURCE_VALUE_MAX_INDEX(resourceTable[i].maxIndex); index++) {
5235 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5236 ioTag_t ioTagDefault = 0;
5237 if (defaultConfig) {
5238 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5241 const bool equalsDefault = ioTag == ioTagDefault;
5242 const char *format = "resource %s %d %c%02d";
5243 const char *formatUnassigned = "resource %s %d NONE";
5244 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5245 if (ioTagDefault) {
5246 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
5247 } else if (defaultConfig) {
5248 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5250 if (ioTag) {
5251 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
5252 } else if (!(dumpMask & HIDE_UNUSED)) {
5253 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5259 static void printResourceOwner(uint8_t owner, uint8_t index)
5261 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
5263 if (resourceTable[owner].maxIndex > 0) {
5264 cliPrintf(" %d", RESOURCE_INDEX(index));
5268 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
5270 if (!newTag) {
5271 return;
5274 const char * format = "\r\nNOTE: %c%02d already assigned to ";
5275 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
5276 for (int i = 0; i < RESOURCE_VALUE_MAX_INDEX(resourceTable[r].maxIndex); i++) {
5277 ioTag_t *tag = getIoTag(resourceTable[r], i);
5278 if (*tag == newTag) {
5279 bool cleared = false;
5280 if (r == resourceIndex) {
5281 if (i == index) {
5282 continue;
5284 *tag = IO_TAG_NONE;
5285 cleared = true;
5288 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
5290 printResourceOwner(r, i);
5292 if (cleared) {
5293 cliPrintf(". ");
5294 printResourceOwner(r, i);
5295 cliPrintf(" disabled");
5298 cliPrintLine(".");
5304 static bool strToPin(char *ptr, ioTag_t *tag)
5306 if (strcasecmp(ptr, "NONE") == 0) {
5307 *tag = IO_TAG_NONE;
5309 return true;
5310 } else {
5311 const unsigned port = (*ptr >= 'a') ? *ptr - 'a' : *ptr - 'A';
5312 if (port < 8) {
5313 ptr++;
5315 char *end;
5316 const long pin = strtol(ptr, &end, 10);
5317 if (end != ptr && pin >= 0 && pin < 16) {
5318 *tag = DEFIO_TAG_MAKE(port, pin);
5320 return true;
5325 return false;
5328 #ifdef USE_DMA
5329 static void showDma(void)
5331 cliPrintLinefeed();
5333 #ifdef MINIMAL_CLI
5334 cliPrintLine("DMA:");
5335 #else
5336 cliPrintLine("Currently active DMA:");
5337 cliRepeat('-', 20);
5338 #endif
5339 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
5340 const resourceOwner_t *owner = dmaGetOwner(i);
5342 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
5343 if (owner->resourceIndex > 0) {
5344 cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
5345 } else {
5346 cliPrintLinef(" %s", ownerNames[owner->owner]);
5350 #endif
5352 #ifdef USE_DMA_SPEC
5354 typedef struct dmaoptEntry_s {
5355 char *device;
5356 dmaPeripheral_e peripheral;
5357 pgn_t pgn;
5358 uint8_t stride;
5359 uint8_t offset;
5360 uint8_t maxIndex;
5361 uint32_t presenceMask;
5362 } dmaoptEntry_t;
5364 #define MASK_IGNORED (0)
5366 // Handy macros for keeping the table tidy.
5367 // DEFS : Single entry
5368 // DEFA : Array of uint8_t (stride = 1)
5369 // DEFW : Wider stride case; array of structs.
5370 // DEFW_OFS: array of structs, starting at offset ofs
5372 #define DEFS(device, peripheral, pgn, type, member) \
5373 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5375 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5376 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5378 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5379 DEFW_OFS(device, peripheral, pgn, type, member, 0, max, mask)
5381 #define DEFW_OFS(device, peripheral, pgn, type, member, ofs, max, mask) \
5382 { device, peripheral, pgn, sizeof(type), offsetof(type, member) + (ofs) * sizeof(type), max, mask }
5384 dmaoptEntry_t dmaoptEntryTable[] = {
5385 DEFW("SPI_SDO", DMA_PERIPH_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5386 DEFW("SPI_SDI", DMA_PERIPH_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5387 // SPI_TX/SPI_RX for backwards compatibility with unified configs defined for 4.2.x
5388 DEFW("SPI_TX", DMA_PERIPH_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5389 DEFW("SPI_RX", DMA_PERIPH_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5390 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
5391 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
5392 #ifdef USE_UART
5393 DEFW_OFS("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, RESOURCE_UART_OFFSET, RESOURCE_UART_COUNT, MASK_IGNORED),
5394 DEFW_OFS("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, RESOURCE_UART_OFFSET, RESOURCE_UART_COUNT, MASK_IGNORED),
5395 #endif
5396 #ifdef USE_LPUART
5397 DEFW_OFS("LPUART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, RESOURCE_LPUART_OFFSET, RESOURCE_LPUART_COUNT, MASK_IGNORED),
5398 DEFW_OFS("LPUART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, RESOURCE_LPUART_OFFSET, RESOURCE_LPUART_COUNT, MASK_IGNORED),
5399 #endif
5400 #if defined(STM32H7) || defined(STM32G4)
5401 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
5402 #endif
5405 #undef DEFS
5406 #undef DEFA
5407 #undef DEFW
5409 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5410 #define DMA_OPT_STRING_BUFSIZE 5
5412 #if defined(STM32H7) || defined(STM32G4) || defined(AT32F435)
5413 #define DMA_CHANREQ_STRING "Request"
5414 #else
5415 #define DMA_CHANREQ_STRING "Channel"
5416 #endif
5418 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(APM32F4)
5419 #define DMA_STCH_STRING "Stream"
5420 #else
5421 #define DMA_STCH_STRING "Channel"
5422 #endif
5424 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5426 static void optToString(int optval, char *buf)
5428 if (optval == DMA_OPT_UNUSED) {
5429 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
5430 } else {
5431 tfp_sprintf(buf, "%d", optval);
5435 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5437 // We compute number to display for different peripherals in advance.
5438 // This is done to deal with TIMUP which numbered non-contiguously.
5439 // Note that using timerGetNumberByIndex is not a generic solution,
5440 // but we are lucky that TIMUP is the only peripheral with non-contiguous numbering.
5442 int uiIndex;
5444 if (entry->presenceMask) {
5445 uiIndex = timerGetNumberByIndex(index);
5446 } else {
5447 uiIndex = DMA_OPT_UI_INDEX(index);
5450 if (dmaopt != DMA_OPT_UNUSED) {
5451 printValue(dumpMask, equalsDefault,
5452 "dma %s %d %d",
5453 entry->device, uiIndex, dmaopt);
5455 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5456 dmaCode_t dmaCode = 0;
5457 if (dmaChannelSpec) {
5458 dmaCode = dmaChannelSpec->code;
5460 printValue(dumpMask, equalsDefault,
5461 "# %s %d: " DMASPEC_FORMAT_STRING,
5462 entry->device, uiIndex, DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5463 } else if (!(dumpMask & HIDE_UNUSED)) {
5464 printValue(dumpMask, equalsDefault,
5465 "dma %s %d NONE",
5466 entry->device, uiIndex);
5470 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5472 const pgRegistry_t* pg = pgFind(entry->pgn);
5473 const void *currentConfig;
5474 const void *defaultConfig;
5476 if (isReadingConfigFromCopy()) {
5477 currentConfig = pg->copy;
5478 defaultConfig = pg->address;
5479 } else {
5480 currentConfig = pg->address;
5481 defaultConfig = NULL;
5484 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5485 dmaoptValue_t defaultOpt;
5487 if (defaultConfig) {
5488 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5489 } else {
5490 defaultOpt = DMA_OPT_UNUSED;
5493 bool equalsDefault = currentOpt == defaultOpt;
5494 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5496 if (defaultConfig) {
5497 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5500 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5501 return headingStr;
5504 #if defined(USE_TIMER_MGMT)
5505 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5507 const char *format = "dma pin %c%02d %d";
5509 if (dmaopt != DMA_OPT_UNUSED) {
5510 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5511 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5512 dmaopt
5515 if (printDetails) {
5516 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5517 if (dmaChannelSpec) {
5518 dmaCode_t dmaCode = dmaChannelSpec->code;
5519 printValue(dumpMask, false,
5520 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5521 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5522 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5526 } else if (!(dumpMask & HIDE_UNUSED)) {
5527 printValue(dumpMask, equalsDefault,
5528 "dma pin %c%02d NONE",
5529 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5534 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5536 const ioTag_t ioTag = currentConfig[index].ioTag;
5538 if (!ioTag) {
5539 return headingStr;
5542 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5543 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5545 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5546 bool equalsDefault = defaultDmaopt == dmaopt;
5547 if (defaultConfig) {
5548 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5549 if (defaultConfig[i].ioTag == ioTag) {
5550 defaultDmaopt = defaultConfig[i].dmaopt;
5552 // 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.
5553 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5555 tagsInUse[index] = true;
5557 break;
5562 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5564 if (defaultConfig) {
5565 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5568 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5569 return headingStr;
5571 #endif
5573 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5575 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5576 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5577 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5578 for (int index = 0; index < entry->maxIndex; index++) {
5579 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5583 #if defined(USE_TIMER_MGMT)
5584 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5585 const timerIOConfig_t *currentConfig;
5586 const timerIOConfig_t *defaultConfig;
5588 if (isReadingConfigFromCopy()) {
5589 currentConfig = (timerIOConfig_t *)pg->copy;
5590 defaultConfig = (timerIOConfig_t *)pg->address;
5591 } else {
5592 currentConfig = (timerIOConfig_t *)pg->address;
5593 defaultConfig = NULL;
5596 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5597 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5598 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5601 if (defaultConfig) {
5602 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5603 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5604 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5605 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5606 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5608 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5612 #endif
5615 static void cliDmaopt(const char *cmdName, char *cmdline)
5617 char *pch = NULL;
5618 char *saveptr;
5620 // Peripheral name or command option
5621 pch = strtok_r(cmdline, " ", &saveptr);
5622 if (!pch) {
5623 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5625 return;
5626 } else if (strcasecmp(pch, "list") == 0) {
5627 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5629 return;
5632 dmaoptEntry_t *entry = NULL;
5633 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5634 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5635 entry = &dmaoptEntryTable[i];
5639 if (!entry && strcasecmp(pch, "pin") != 0) {
5640 cliPrintErrorLinef(cmdName, "BAD DEVICE: %s", pch);
5641 return;
5644 // Index
5645 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5647 int index = 0;
5648 dmaoptValue_t *optaddr = NULL;
5650 ioTag_t ioTag = IO_TAG_NONE;
5651 #if defined(USE_TIMER_MGMT)
5652 timerIOConfig_t *timerIoConfig = NULL;
5653 #endif
5654 const timerHardware_t *timer = NULL;
5655 pch = strtok_r(NULL, " ", &saveptr);
5656 if (entry) {
5657 index = pch ? (atoi(pch) - 1) : -1;
5658 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5659 cliPrintErrorLinef(cmdName, "BAD INDEX: '%s'", pch ? pch : "");
5660 return;
5663 const pgRegistry_t* pg = pgFind(entry->pgn);
5664 const void *currentConfig;
5665 if (isWritingConfigToCopy()) {
5666 currentConfig = pg->copy;
5667 } else {
5668 currentConfig = pg->address;
5670 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5671 orgval = *optaddr;
5672 } else {
5673 // It's a pin
5674 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5675 cliPrintErrorLinef(cmdName, "INVALID PIN: '%s'", pch ? pch : "");
5677 return;
5680 orgval = dmaoptByTag(ioTag);
5681 #if defined(USE_TIMER_MGMT)
5682 timerIoConfig = timerIoConfigByTag(ioTag);
5683 #endif
5684 timer = timerGetConfiguredByTag(ioTag);
5687 // opt or list
5688 pch = strtok_r(NULL, " ", &saveptr);
5689 if (!pch) {
5690 if (entry) {
5691 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5693 #if defined(USE_TIMER_MGMT)
5694 else {
5695 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5697 #endif
5699 return;
5700 } else if (strcasecmp(pch, "list") == 0) {
5701 // Show possible opts
5702 const dmaChannelSpec_t *dmaChannelSpec;
5703 if (entry) {
5704 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5705 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5707 } else {
5708 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5709 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5713 return;
5714 } else if (pch) {
5715 int optval;
5716 if (strcasecmp(pch, "none") == 0) {
5717 optval = DMA_OPT_UNUSED;
5718 } else {
5719 optval = atoi(pch);
5721 if (entry) {
5722 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5723 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5725 return;
5727 } else {
5728 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5729 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5731 return;
5736 char optvalString[DMA_OPT_STRING_BUFSIZE];
5737 optToString(optval, optvalString);
5739 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5740 optToString(orgval, orgvalString);
5742 if (optval != orgval) {
5743 if (entry) {
5744 *optaddr = optval;
5746 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5747 } else {
5748 #if defined(USE_TIMER_MGMT)
5749 timerIoConfig->dmaopt = optval;
5750 #endif
5752 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5754 } else {
5755 if (entry) {
5756 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5757 } else {
5758 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5763 #endif // USE_DMA_SPEC
5765 #ifdef USE_DMA
5766 static void cliDma(const char *cmdName, char* cmdline)
5768 int len = strlen(cmdline);
5769 if (len && strncasecmp(cmdline, "show", len) == 0) {
5770 showDma();
5772 return;
5775 #if defined(USE_DMA_SPEC)
5776 cliDmaopt(cmdName, cmdline);
5777 #else
5778 cliShowParseError(cmdName);
5779 #endif
5781 #endif
5782 #endif // USE_RESOURCE_MGMT
5784 #ifdef USE_TIMER_MGMT
5785 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5787 const char *format = "timer %c%02d AF%d";
5788 const char *emptyFormat = "timer %c%02d NONE";
5790 if (timerIndex > 0) {
5791 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5792 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5793 IO_GPIOPortIdxByTag(ioTag) + 'A',
5794 IO_GPIOPinIdxByTag(ioTag),
5795 timer->alternateFunction
5797 if (printDetails) {
5798 printValue(dumpMask, false,
5799 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5800 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5801 timerGetTIMNumber(timer->tim),
5802 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5803 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5804 timer->alternateFunction
5807 } else {
5808 printValue(dumpMask, equalsDefault, emptyFormat,
5809 IO_GPIOPortIdxByTag(ioTag) + 'A',
5810 IO_GPIOPinIdxByTag(ioTag)
5815 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5817 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5818 const timerIOConfig_t *currentConfig;
5819 const timerIOConfig_t *defaultConfig;
5821 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5822 if (isReadingConfigFromCopy()) {
5823 currentConfig = (timerIOConfig_t *)pg->copy;
5824 defaultConfig = (timerIOConfig_t *)pg->address;
5825 } else {
5826 currentConfig = (timerIOConfig_t *)pg->address;
5827 defaultConfig = NULL;
5830 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5831 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5832 const ioTag_t ioTag = currentConfig[i].ioTag;
5834 if (!ioTag) {
5835 continue;
5838 const uint8_t timerIndex = currentConfig[i].index;
5840 uint8_t defaultTimerIndex = 0;
5841 if (defaultConfig) {
5842 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5843 if (defaultConfig[i].ioTag == ioTag) {
5844 defaultTimerIndex = defaultConfig[i].index;
5845 tagsInUse[i] = true;
5847 break;
5852 const bool equalsDefault = defaultTimerIndex == timerIndex;
5853 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5854 if (defaultConfig && defaultTimerIndex) {
5855 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5858 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5861 if (defaultConfig) {
5862 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5863 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5864 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5865 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5867 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5873 #define TIMER_INDEX_UNDEFINED -1
5874 #define TIMER_AF_STRING_BUFSIZE 5
5876 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5878 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5879 if (!timer) {
5880 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5881 } else {
5882 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5886 #ifdef USE_TIMER_MAP_PRINT
5887 static void showTimerMap(void)
5889 cliPrintLinefeed();
5890 cliPrintLine("Timer Mapping:");
5891 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5892 const ioTag_t ioTag = timerIOConfig(i)->ioTag;
5894 if (!ioTag) {
5895 continue;
5898 cliPrintLinef(" TIMER_PIN_MAP(%d, P%c%d, %d, %d)",
5900 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5901 timerIOConfig(i)->index,
5902 timerIOConfig(i)->dmaopt
5906 #endif
5908 static void showTimers(void)
5910 cliPrintLinefeed();
5912 #ifdef MINIMAL_CLI
5913 cliPrintLine("Timers:");
5914 #else
5915 cliPrintLine("Currently active Timers:");
5916 cliRepeat('-', 23);
5917 #endif
5919 int8_t timerNumber;
5920 for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
5921 cliPrintf("TIM%d:", timerNumber);
5922 bool timerUsed = false;
5923 for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
5924 const timerHardware_t *timer = timerGetAllocatedByNumberAndChannel(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
5925 const resourceOwner_t *timerOwner = timerGetOwner(timer);
5926 if (timerOwner->owner) {
5927 if (!timerUsed) {
5928 timerUsed = true;
5930 cliPrintLinefeed();
5933 if (timerOwner->resourceIndex > 0) {
5934 cliPrintLinef(" CH%d%s: %s %d", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner], timerOwner->resourceIndex);
5935 } else {
5936 cliPrintLinef(" CH%d%s: %s", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner]);
5941 if (!timerUsed) {
5942 cliPrintLine(" FREE");
5947 static void cliTimer(const char *cmdName, char *cmdline)
5949 int len = strlen(cmdline);
5951 if (len == 0) {
5952 printTimer(DUMP_MASTER, NULL);
5954 return;
5955 } else if (strncasecmp(cmdline, "list", len) == 0) {
5956 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5958 return;
5959 #ifdef USE_TIMER_MAP_PRINT
5960 } else if (strncasecmp(cmdline, "map", len) == 0) {
5961 showTimerMap();
5963 return;
5964 #endif
5965 } else if (strncasecmp(cmdline, "show", len) == 0) {
5966 showTimers();
5968 return;
5971 char *pch = NULL;
5972 char *saveptr;
5974 ioTag_t ioTag = IO_TAG_NONE;
5975 pch = strtok_r(cmdline, " ", &saveptr);
5976 if (!pch || !strToPin(pch, &ioTag)) {
5977 cliShowParseError(cmdName);
5979 return;
5980 } else if (!IOGetByTag(ioTag)) {
5981 cliPrintErrorLinef(cmdName, "PIN NOT USED ON BOARD.");
5983 return;
5986 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5987 bool isExistingTimerOpt = false;
5988 /* find existing entry, or go for next available */
5989 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5990 if (timerIOConfig(i)->ioTag == ioTag) {
5991 timerIOIndex = i;
5992 isExistingTimerOpt = true;
5994 break;
5997 /* first available empty slot */
5998 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5999 timerIOIndex = i;
6003 if (timerIOIndex < 0) {
6004 cliPrintErrorLinef(cmdName, "PIN TIMER MAP FULL.");
6006 return;
6009 pch = strtok_r(NULL, " ", &saveptr);
6010 if (pch) {
6011 int timerIndex = TIMER_INDEX_UNDEFINED;
6012 if (strcasecmp(pch, "list") == 0) {
6013 /* output the list of available options */
6014 const timerHardware_t *timer;
6015 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
6016 cliPrintLinef("# AF%d: TIM%d CH%d%s",
6017 timer->alternateFunction,
6018 timerGetTIMNumber(timer->tim),
6019 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
6020 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
6024 return;
6025 } else if (strncasecmp(pch, "af", 2) == 0) {
6026 unsigned alternateFunction = atoi(&pch[2]);
6028 const timerHardware_t *timer;
6029 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
6030 if (timer->alternateFunction == alternateFunction) {
6031 timerIndex = index;
6033 break;
6037 if (!timer) {
6038 cliPrintErrorLinef(cmdName, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
6040 return;
6042 } else if (strcasecmp(pch, "none") != 0) {
6043 cliPrintErrorLinef(cmdName, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
6045 return;
6048 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
6049 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
6050 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
6051 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
6053 char optvalString[DMA_OPT_STRING_BUFSIZE];
6054 alternateFunctionToString(ioTag, timerIndex, optvalString);
6056 char orgvalString[DMA_OPT_STRING_BUFSIZE];
6057 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
6059 if (timerIndex == oldTimerIndex) {
6060 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
6061 } else {
6062 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
6065 return;
6066 } else {
6067 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
6069 return;
6072 #endif
6074 #if defined(USE_RESOURCE_MGMT)
6075 static void cliResource(const char *cmdName, char *cmdline)
6077 char *pch = NULL;
6078 char *saveptr;
6080 pch = strtok_r(cmdline, " ", &saveptr);
6081 if (!pch) {
6082 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
6084 return;
6085 } else if (strcasecmp(pch, "show") == 0) {
6086 #ifdef MINIMAL_CLI
6087 cliPrintLine("IO");
6088 #else
6089 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
6090 cliRepeat('-', 20);
6091 #endif
6092 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
6093 const char* owner;
6094 owner = ownerNames[ioRecs[i].owner];
6096 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
6097 if (ioRecs[i].index > 0) {
6098 cliPrintf(" %d", ioRecs[i].index);
6100 cliPrintLinefeed();
6103 pch = strtok_r(NULL, " ", &saveptr);
6104 if (strcasecmp(pch, "all") == 0) {
6105 #if defined(USE_TIMER_MGMT)
6106 cliTimer(cmdName, "show");
6107 #endif
6108 #if defined(USE_DMA)
6109 cliDma(cmdName, "show");
6110 #endif
6113 return;
6116 unsigned resourceIndex = 0;
6117 for (; ; resourceIndex++) {
6118 if (resourceIndex >= ARRAYLEN(resourceTable)) {
6119 cliPrintErrorLinef(cmdName, "INVALID RESOURCE NAME: '%s'", pch);
6120 return;
6123 const char *resourceName = ownerNames[resourceTable[resourceIndex].owner];
6124 if (strcasecmp(pch, resourceName) == 0) {
6125 break;
6129 pch = strtok_r(NULL, " ", &saveptr);
6130 int index = pch ? atoi(pch) : 0;
6132 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
6133 if (index <= 0 || index > RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex)) {
6134 cliShowArgumentRangeError(cmdName, "INDEX", 1, RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex));
6135 return;
6137 index -= 1;
6139 pch = strtok_r(NULL, " ", &saveptr);
6142 ioTag_t *resourceTag = getIoTag(resourceTable[resourceIndex], index);
6144 if (pch && strlen(pch) > 0) {
6145 ioTag_t tag;
6146 if (strToPin(pch, &tag)) {
6147 if (!tag) {
6148 *resourceTag = tag;
6149 #ifdef MINIMAL_CLI
6150 cliPrintLine("Freed");
6151 #else
6152 cliPrintLine("Resource is freed");
6153 #endif
6154 return;
6155 } else {
6156 ioRec_t *rec = IO_Rec(IOGetByTag(tag));
6157 if (rec) {
6158 *resourceTag = tag;
6159 resourceCheck(resourceIndex, index, tag);
6160 #ifdef MINIMAL_CLI
6161 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6162 #else
6163 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6164 #endif
6165 } else {
6166 cliShowParseError(cmdName);
6169 } else {
6170 cliPrintErrorLinef(cmdName, "Failed to parse '%s' as pin", pch);
6172 } else {
6173 ioTag_t tag = *resourceTag;
6174 char ioName[5];
6175 if (tag) {
6176 tfp_sprintf(ioName, "%c%02d", IO_GPIOPortIdxByTag(tag) + 'A', IO_GPIOPinIdxByTag(tag));
6178 cliPrintLinef("# resource %s %d %s", ownerNames[resourceTable[resourceIndex].owner], RESOURCE_INDEX(index), tag ? ioName : "NONE");
6181 #endif
6183 #ifdef USE_DSHOT_TELEMETRY
6185 static void cliDshotTelemetryInfo(const char *cmdName, char *cmdline)
6187 UNUSED(cmdName);
6188 UNUSED(cmdline);
6190 if (useDshotTelemetry) {
6191 cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
6192 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
6193 int32_t directionChangeCycles = cmp32(dshotDMAHandlerCycleCounters.changeDirectionCompletedAt, dshotDMAHandlerCycleCounters.irqAt);
6194 int32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
6195 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
6196 cliPrintLinefeed();
6198 #ifdef USE_DSHOT_TELEMETRY_STATS
6199 cliPrintLine("Motor Type eRPM RPM Hz Invalid TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6200 cliPrintLine("===== ====== ====== ====== ====== ======= ====== ====== ====== ====== ====== ====== ======");
6201 #else
6202 cliPrintLine("Motor Type eRPM RPM Hz TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6203 cliPrintLine("===== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======");
6204 #endif
6206 for (uint8_t i = 0; i < getMotorCount(); i++) {
6207 const uint16_t erpm = getDshotErpm(i);
6208 const uint16_t rpm = lrintf(getDshotRpm(i));
6210 cliPrintf("%5d %c%c%c%c%c %6d %6d %6d",
6211 i + 1,
6212 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_eRPM)) ? 'R' : '-'),
6213 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE)) ? 'T' : '-'),
6214 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_VOLTAGE)) ? 'V' : '-'),
6215 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_CURRENT)) ? 'C' : '-'),
6216 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_STATE_EVENTS)) ? 'S' : '-'),
6217 erpm * 100, rpm, rpm / 60);
6219 #ifdef USE_DSHOT_TELEMETRY_STATS
6220 if (isDshotMotorTelemetryActive(i)) {
6221 int32_t calcPercent = getDshotTelemetryMotorInvalidPercent(i);
6222 cliPrintf(" %3d.%02d%%", calcPercent / 100, calcPercent % 100);
6223 } else {
6224 cliPrint(" NO DATA");
6226 #endif
6228 cliPrintLinef(" %6d %3d.%02d %6d %6d %6d %6d %6d",
6229 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_TEMPERATURE],
6230 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] / 4,
6231 25 * (dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] % 4),
6232 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_CURRENT],
6233 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_STATE_EVENTS],
6234 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG1],
6235 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG2],
6236 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG3]
6239 cliPrintLinefeed();
6241 const int len = MAX_GCR_EDGES;
6242 #ifdef DEBUG_BBDECODE
6243 extern uint16_t bbBuffer[134];
6244 for (int i = 0; i < 134; i++) {
6245 cliPrintf("%u ", (int)bbBuffer[i]);
6247 cliPrintLinefeed();
6248 #endif
6249 for (int i = 0; i < len; i++) {
6250 cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
6252 cliPrintLinefeed();
6253 for (int i = 1; i < len; i++) {
6254 cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
6256 cliPrintLinefeed();
6257 } else {
6258 cliPrintLine("Dshot telemetry not enabled");
6262 #endif
6264 static void printConfig(const char *cmdName, char *cmdline, bool doDiff)
6266 dumpFlags_t dumpMask = DUMP_MASTER;
6267 char *options;
6268 if ((options = checkCommand(cmdline, "master"))) {
6269 dumpMask = DUMP_MASTER; // only
6270 } else if ((options = checkCommand(cmdline, "profile"))) {
6271 dumpMask = DUMP_PROFILE; // only
6272 } else if ((options = checkCommand(cmdline, "rates"))) {
6273 dumpMask = DUMP_RATES; // only
6274 } else if ((options = checkCommand(cmdline, "hardware"))) {
6275 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
6276 } else if ((options = checkCommand(cmdline, "all"))) {
6277 dumpMask = DUMP_ALL; // all profiles and rates
6278 } else {
6279 options = cmdline;
6282 if (doDiff) {
6283 dumpMask = dumpMask | DO_DIFF;
6286 if (checkCommand(options, "defaults")) {
6287 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
6288 } else if (checkCommand(options, "bare")) {
6289 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
6292 backupAndResetConfigs();
6294 #ifdef USE_CLI_BATCH
6295 bool batchModeEnabled = false;
6296 #endif
6297 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
6298 cliPrintHashLine("version");
6299 printVersion(false);
6301 if (!(dumpMask & BARE)) {
6302 #ifdef USE_CLI_BATCH
6303 cliPrintHashLine("start the command batch");
6304 cliPrintLine("batch start");
6305 batchModeEnabled = true;
6306 #endif
6308 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
6309 cliPrintHashLine("reset configuration to default settings");
6310 cliPrintLine("defaults nosave");
6314 #if defined(USE_BOARD_INFO)
6315 cliPrintLinefeed();
6316 printBoardName(dumpMask);
6317 printManufacturerId(dumpMask);
6318 #endif
6320 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
6321 cliMcuId(cmdName, "");
6322 #if defined(USE_SIGNATURE)
6323 cliSignature(cmdName, "");
6324 #endif
6327 if (!(dumpMask & HARDWARE_ONLY)) {
6328 printCraftName(dumpMask, &pilotConfig_Copy);
6331 #ifdef USE_RESOURCE_MGMT
6332 printResource(dumpMask, "resources");
6333 #if defined(USE_TIMER_MGMT)
6334 printTimer(dumpMask, "timer");
6335 #endif
6336 #ifdef USE_DMA_SPEC
6337 printDmaopt(dumpMask, "dma");
6338 #endif
6339 #endif
6341 printFeature(dumpMask, featureConfig_Copy.enabledFeatures, featureConfig()->enabledFeatures, "feature");
6343 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
6345 if (!(dumpMask & HARDWARE_ONLY)) {
6346 #ifndef USE_QUAD_MIXER_ONLY
6347 const char *mixerHeadingStr = "mixer";
6348 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
6349 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
6350 const char *formatMixer = "mixer %s";
6351 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
6352 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
6354 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
6356 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
6358 #ifdef USE_SERVOS
6359 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
6361 const char *servoMixHeadingStr = "servo mixer";
6362 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
6363 cliPrintHashLine(servoMixHeadingStr);
6364 cliPrintLine("smix reset\r\n");
6365 servoMixHeadingStr = NULL;
6367 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
6368 #endif
6369 #endif
6371 #if defined(USE_BEEPER)
6372 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
6374 #if defined(USE_DSHOT)
6375 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
6376 #endif
6377 #endif // USE_BEEPER
6379 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
6381 #ifdef USE_LED_STRIP_STATUS_MODE
6382 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
6384 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
6386 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
6387 #endif
6389 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
6391 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
6393 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
6395 #ifdef USE_VTX_TABLE
6396 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
6397 #endif
6399 #ifdef USE_VTX_CONTROL
6400 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
6401 #endif
6403 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
6406 if (dumpMask & HARDWARE_ONLY) {
6407 dumpAllValues(cmdName, HARDWARE_VALUE, dumpMask, "master");
6408 } else {
6409 dumpAllValues(cmdName, MASTER_VALUE, dumpMask, "master");
6411 if (dumpMask & DUMP_ALL) {
6412 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
6413 cliDumpPidProfile(cmdName, pidProfileIndex, dumpMask);
6416 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
6418 if (!(dumpMask & BARE)) {
6419 cliPrintHashLine("restore original profile selection");
6421 cliProfile(cmdName, "");
6424 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
6426 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
6427 cliDumpRateProfile(cmdName, rateIndex, dumpMask);
6430 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
6432 if (!(dumpMask & BARE)) {
6433 cliPrintHashLine("restore original rateprofile selection");
6435 cliRateProfile(cmdName, "");
6437 cliPrintHashLine("save configuration");
6438 cliPrint("save");
6439 #ifdef USE_CLI_BATCH
6440 batchModeEnabled = false;
6441 #endif
6444 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
6445 } else {
6446 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6448 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6451 } else if (dumpMask & DUMP_PROFILE) {
6452 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6453 } else if (dumpMask & DUMP_RATES) {
6454 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6457 #ifdef USE_CLI_BATCH
6458 if (batchModeEnabled) {
6459 cliPrintHashLine("end the command batch");
6460 cliPrintLine("batch end");
6462 #endif
6464 // restore configs from copies
6465 restoreConfigs(0);
6468 static void cliDump(const char *cmdName, char *cmdline)
6470 printConfig(cmdName, cmdline, false);
6473 static void cliDiff(const char *cmdName, char *cmdline)
6475 printConfig(cmdName, cmdline, true);
6478 #if defined(USE_USB_MSC)
6479 static void cliMsc(const char *cmdName, char *cmdline)
6481 if (mscCheckFilesystemReady()) {
6482 #ifdef USE_RTC_TIME
6483 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
6484 if (!isEmpty(cmdline)) {
6485 timezoneOffsetMinutes = atoi(cmdline);
6486 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
6487 cliPrintErrorLinef(cmdName, "INVALID TIMEZONE OFFSET");
6488 return;
6491 #else
6492 int timezoneOffsetMinutes = 0;
6493 UNUSED(cmdline);
6494 #endif
6495 cliPrintHashLine("Restarting in mass storage mode");
6496 cliPrint("\r\nRebooting");
6497 cliWriterFlush();
6498 waitForSerialPortToFinishTransmitting(cliPort);
6499 motorShutdown();
6501 systemResetToMsc(timezoneOffsetMinutes);
6502 } else {
6503 cliPrintHashLine("Storage not present or failed to initialize!");
6506 #endif
6508 typedef void cliCommandFn(const char* name, char *cmdline);
6510 typedef struct {
6511 const char *name;
6512 #ifndef MINIMAL_CLI
6513 const char *description;
6514 const char *args;
6515 #endif
6516 cliCommandFn *cliCommand;
6517 } clicmd_t;
6519 #ifndef MINIMAL_CLI
6520 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6522 name , \
6523 description , \
6524 args , \
6525 cliCommand \
6527 #else
6528 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6530 name, \
6531 cliCommand \
6533 #endif
6535 static void cliHelp(const char *cmdName, char *cmdline);
6537 // should be sorted a..z for bsearch()
6538 const clicmd_t cmdTable[] = {
6539 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
6540 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
6541 #ifdef USE_CLI_BATCH
6542 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
6543 #endif
6544 #if defined(USE_BEEPER)
6545 #if defined(USE_DSHOT)
6546 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6547 "\t<->[name]", cliBeacon),
6548 #endif
6549 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6550 "\t<->[name]", cliBeeper),
6551 #endif // USE_BEEPER
6552 #if defined(USE_RX_BIND)
6553 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI, SRXL2 or CRSF", NULL, cliRxBind),
6554 #endif
6555 #if defined(USE_FLASH_BOOT_LOADER)
6556 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6557 #else
6558 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6559 #endif
6560 #if defined(USE_BOARD_INFO)
6561 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6562 #endif
6563 #ifdef USE_LED_STRIP_STATUS_MODE
6564 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6565 #endif
6566 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{nosave}", cliDefaults),
6567 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6568 #ifdef USE_RESOURCE_MGMT
6570 #ifdef USE_DMA
6571 #ifdef USE_DMA_SPEC
6572 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6573 #else
6574 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6575 #endif
6576 #endif
6578 #endif
6579 #ifdef USE_DSHOT_TELEMETRY
6580 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6581 #endif
6582 #ifdef USE_DSHOT
6583 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6584 #endif
6585 CLI_COMMAND_DEF("dump", "dump configuration",
6586 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6587 #ifdef USE_ESCSERIAL
6588 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6589 #endif
6590 CLI_COMMAND_DEF("exit", "exit command line interface and reboot (default)", "[noreboot]", cliExitCmd),
6591 CLI_COMMAND_DEF("feature", "configure features",
6592 "list\r\n"
6593 "\t<->[name]", cliFeature),
6594 #ifdef USE_FLASH_CHIP
6595 #ifdef USE_FLASHFS
6596 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6597 #endif
6598 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6599 #if defined(USE_FLASH_TOOLS) && defined(USE_FLASHFS)
6600 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6601 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6602 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6603 #endif
6604 #endif
6605 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6606 #ifdef USE_GPS
6607 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6608 #endif
6609 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6610 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6611 #endif
6612 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp),
6613 #ifdef USE_LED_STRIP_STATUS_MODE
6614 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6615 #endif
6616 #if defined(USE_BOARD_INFO)
6617 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6618 #endif
6619 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6620 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6621 #ifndef USE_QUAD_MIXER_ONLY
6622 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6623 #endif
6624 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6625 #ifdef USE_LED_STRIP_STATUS_MODE
6626 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6627 #endif
6628 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6629 #ifdef USE_USB_MSC
6630 #ifdef USE_RTC_TIME
6631 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6632 #else
6633 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6634 #endif
6635 #endif
6636 #ifndef MINIMAL_CLI
6637 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6638 #endif
6639 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6640 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6641 #ifdef USE_RC_SMOOTHING_FILTER
6642 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6643 #endif // USE_RC_SMOOTHING_FILTER
6644 #ifdef USE_RESOURCE_MGMT
6645 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6646 #endif
6647 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6648 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6649 CLI_COMMAND_DEF("save", "save and reboot (default)", "[noreboot]", cliSave),
6650 #ifdef USE_SDCARD
6651 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6652 #endif
6653 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6654 #if defined(USE_SERIAL_PASSTHROUGH)
6655 #if defined(USE_PINIO)
6656 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|<dtr pinio>|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6657 #else
6658 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6659 #endif
6660 #endif
6661 #ifdef USE_SERVOS
6662 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6663 #endif
6664 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6665 #if defined(USE_SIGNATURE)
6666 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6667 #endif
6668 #if defined(USE_SIMPLIFIED_TUNING)
6669 CLI_COMMAND_DEF("simplified_tuning", "applies or disables simplified tuning", "apply | disable", cliSimplifiedTuning),
6670 #endif
6671 #ifdef USE_SERVOS
6672 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6673 "\treset\r\n"
6674 "\tload <mixer>\r\n"
6675 "\treverse <servo> <source> r|n", cliServoMix),
6676 #endif
6677 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6678 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6679 #ifdef USE_TIMER_MGMT
6680 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6681 #endif
6682 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6683 #ifdef USE_VTX_CONTROL
6684 #ifdef MINIMAL_CLI
6685 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6686 #else
6687 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6688 #endif
6689 #endif
6690 #ifdef USE_VTX_TABLE
6691 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL, cliVtxInfo),
6692 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6693 #endif
6696 static void cliHelp(const char *cmdName, char *cmdline)
6698 bool anyMatches = false;
6700 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6701 bool printEntry = false;
6702 if (isEmpty(cmdline)) {
6703 printEntry = true;
6704 } else {
6705 if (strcasestr(cmdTable[i].name, cmdline)
6706 #ifndef MINIMAL_CLI
6707 || strcasestr(cmdTable[i].description, cmdline)
6708 #endif
6710 printEntry = true;
6714 if (printEntry) {
6715 anyMatches = true;
6716 cliPrint(cmdTable[i].name);
6717 #ifndef MINIMAL_CLI
6718 if (cmdTable[i].description) {
6719 cliPrintf(" - %s", cmdTable[i].description);
6721 if (cmdTable[i].args) {
6722 cliPrintf("\r\n\t%s", cmdTable[i].args);
6724 #endif
6725 cliPrintLinefeed();
6728 if (!isEmpty(cmdline) && !anyMatches) {
6729 cliPrintErrorLinef(cmdName, "NO MATCHES FOR '%s'", cmdline);
6733 static void processCharacter(const char c)
6735 if (bufferIndex && (c == '\n' || c == '\r')) {
6736 if (cliInteractive) {
6737 // echo new line back to terminal
6738 cliPrintLinefeed();
6741 // Strip comment starting with # from line
6742 char *p = cliBuffer;
6743 p = strchr(p, '#');
6744 if (NULL != p) {
6745 bufferIndex = (uint32_t)(p - cliBuffer);
6748 // Strip trailing whitespace
6749 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6750 bufferIndex--;
6753 // Process non-empty lines
6754 if (bufferIndex > 0) {
6755 cliBuffer[bufferIndex] = 0; // null terminate
6757 const clicmd_t *cmd;
6758 char *options;
6759 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6760 if ((options = checkCommand(cliBuffer, cmd->name))) {
6761 break;
6764 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6765 cmd->cliCommand(cmd->name, options);
6766 if (!cliMode) {
6767 // cli session ended
6768 return;
6770 } else {
6771 if (cliInteractive) {
6772 cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
6773 } else {
6774 cliPrint("ERR_CMD_NA: ");
6775 cliPrintLine(cliBuffer);
6780 cliClearInputBuffer();
6782 // prompt if in interactive mode
6783 if (cliInteractive) {
6784 cliPrompt();
6787 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6788 if (!bufferIndex && c == ' ') {
6789 return; // Ignore leading spaces
6791 cliBuffer[bufferIndex++] = c;
6793 // echo the character if interactive
6794 if (cliInteractive) {
6795 cliWrite(c);
6800 static void processCharacterInteractive(const char c)
6802 if (c == '\t' || c == '?') {
6803 // do tab completion
6804 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6805 uint32_t i = bufferIndex;
6806 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6807 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) {
6808 continue;
6810 if (!pstart) {
6811 pstart = cmd;
6813 pend = cmd;
6815 if (pstart) { /* Buffer matches one or more commands */
6816 for (; ; bufferIndex++) {
6817 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6818 break;
6819 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6820 /* Unambiguous -- append a space */
6821 cliBuffer[bufferIndex++] = ' ';
6822 cliBuffer[bufferIndex] = '\0';
6823 break;
6825 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6828 if (!bufferIndex || pstart != pend) {
6829 /* Print list of ambiguous matches */
6830 cliPrint("\r\n\033[K");
6831 for (cmd = pstart; cmd <= pend; cmd++) {
6832 cliPrint(cmd->name);
6833 cliWrite('\t');
6835 cliPrompt();
6836 i = 0; /* Redraw prompt */
6838 for (; i < bufferIndex; i++)
6839 cliWrite(cliBuffer[i]);
6840 } else if (!bufferIndex && c == 4) { // CTRL-D
6841 cliExit(true);
6842 return;
6843 } else if (c == 12) { // NewPage / CTRL-L
6844 // clear screen
6845 cliPrint("\033[2J\033[1;1H");
6846 cliPrompt();
6847 } else if (c == 127) {
6848 // backspace
6849 if (bufferIndex) {
6850 cliBuffer[--bufferIndex] = 0;
6851 cliPrint("\010 \010");
6853 } else {
6854 processCharacter(c);
6858 bool cliProcess(void)
6860 if (!cliWriter || !cliMode) {
6861 return false;
6864 while (serialRxBytesWaiting(cliPort)) {
6865 uint8_t c = serialRead(cliPort);
6866 if (cliInteractive) {
6867 processCharacterInteractive(c);
6868 } else {
6869 // handle terminating flow control character
6870 if (c == 0x3 || (cmp32(millis(), cliEntryTime) > 2000)) { // CTRL-C (ETX) or 2 seconds timeout
6871 cliWrite(0x3); // send end of text, terminating flow control
6872 cliExit(false);
6873 return cliMode;
6875 processCharacter(c);
6878 cliWriterFlush();
6879 return cliMode;
6882 static void cliExit(const bool reboot)
6884 cliWriterFlush();
6885 waitForSerialPortToFinishTransmitting(cliPort);
6886 cliClearInputBuffer();
6887 cliMode = false;
6888 cliInteractive = false;
6889 // incase a motor was left running during motortest, clear it here
6890 mixerResetDisarmedMotors();
6892 if (reboot) {
6893 cliReboot();
6897 void cliEnter(serialPort_t *serialPort, bool interactive)
6899 cliMode = true;
6900 cliInteractive = interactive;
6901 cliPort = serialPort;
6902 cliEntryTime = millis();
6903 cliClearInputBuffer();
6905 if (interactive) {
6906 setPrintfSerialPort(cliPort);
6909 bufWriterInit(&cliWriterDesc, cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6910 cliErrorWriter = cliWriter = &cliWriterDesc;
6912 if (interactive) {
6913 #ifndef MINIMAL_CLI
6914 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to reboot, or 'help'");
6915 #else
6916 cliPrintLine("\r\nCLI");
6917 #endif
6918 // arming flag not released if exiting cli with no reboot for safety
6919 setArmingDisabled(ARMING_DISABLED_CLI);
6920 cliPrompt();
6922 #ifdef USE_CLI_BATCH
6923 resetCommandBatch();
6924 #endif
6925 } else {
6926 cliWrite(0x2); // send start of text, initiating flow control
6930 #endif // USE_CLI