Adding support for UART0 (#14094)
[betaflight.git] / src / main / cli / cli.c
bloba174fe8cf5a8cbee5d88342b588fed054b84b01c
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/failsafe.h"
108 #include "flight/imu.h"
109 #include "flight/mixer.h"
110 #include "flight/pid.h"
111 #include "flight/position.h"
112 #include "flight/servos.h"
114 #include "io/asyncfatfs/asyncfatfs.h"
115 #include "io/beeper.h"
116 #include "io/flashfs.h"
117 #include "io/gimbal.h"
118 #include "io/gps.h"
119 #include "io/ledstrip.h"
120 #include "io/serial.h"
121 #include "io/transponder_ir.h"
122 #include "io/usb_msc.h"
123 #include "io/vtx_control.h"
124 #include "io/vtx.h"
126 #include "msp/msp.h"
127 #include "msp/msp_box.h"
128 #include "msp/msp_protocol.h"
130 #include "osd/osd.h"
132 #include "pg/adc.h"
133 #include "pg/beeper.h"
134 #include "pg/beeper_dev.h"
135 #include "pg/board.h"
136 #include "pg/bus_i2c.h"
137 #include "pg/bus_spi.h"
138 #include "pg/gyrodev.h"
139 #include "pg/max7456.h"
140 #include "pg/mco.h"
141 #include "pg/motor.h"
142 #include "pg/pilot.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 = "-";
213 static const char* const invalidName = "INVALID";
215 #define MAX_CHANGESET_ID_LENGTH 8
216 #define MAX_DATE_LENGTH 20
218 #define ERROR_INVALID_NAME "INVALID NAME: %s"
219 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
221 #ifndef USE_QUAD_MIXER_ONLY
222 // sync this with mixerMode_e
223 static const char * const mixerNames[] = {
224 "TRI", "QUADP", "QUADX", "BI",
225 "GIMBAL", "Y6", "HEX6",
226 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
227 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
228 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
229 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
231 #endif
233 // sync this with features_e
234 #define _R(_flag, _name) [LOG2(_flag)] = _name
235 static const char * const featureNames[] = {
236 _R(FEATURE_RX_PPM, "RX_PPM"),
237 _R(FEATURE_INFLIGHT_ACC_CAL, "INFLIGHT_ACC_CAL"),
238 _R(FEATURE_RX_SERIAL, "RX_SERIAL"),
239 _R(FEATURE_MOTOR_STOP, "MOTOR_STOP"),
240 _R(FEATURE_SERVO_TILT, "SERVO_TILT"),
241 _R(FEATURE_SOFTSERIAL, "SOFTSERIAL"),
242 _R(FEATURE_GPS, "GPS"),
243 _R(FEATURE_RANGEFINDER, "RANGEFINDER"),
244 _R(FEATURE_OPTICALFLOW, "OPTICALFLOW"),
245 _R(FEATURE_TELEMETRY, "TELEMETRY"),
246 _R(FEATURE_3D, "3D"),
247 _R(FEATURE_RX_PARALLEL_PWM, "RX_PARALLEL_PWM"),
248 _R(FEATURE_RSSI_ADC, "RSSI_ADC"),
249 _R(FEATURE_LED_STRIP, "LED_STRIP"),
250 _R(FEATURE_DASHBOARD, "DISPLAY"),
251 _R(FEATURE_OSD, "OSD"),
252 _R(FEATURE_CHANNEL_FORWARDING, "CHANNEL_FORWARDING"),
253 _R(FEATURE_TRANSPONDER, "TRANSPONDER"),
254 _R(FEATURE_AIRMODE, "AIRMODE"),
255 _R(FEATURE_RX_SPI, "RX_SPI"),
256 _R(FEATURE_ESC_SENSOR, "ESC_SENSOR"),
257 _R(FEATURE_ANTI_GRAVITY, "ANTI_GRAVITY"),
259 #undef _R
261 // sync this with rxFailsafeChannelMode_e
262 static const char rxFailsafeModeCharacters[] = "ahs";
264 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
265 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
266 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
269 #if defined(USE_SENSOR_NAMES)
270 // sync this with sensors_e
271 static const char *const sensorTypeNames[] = {
272 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
275 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
277 static const char * const *sensorHardwareNames[] = {
278 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware, lookupTableOpticalflowHardware
280 #endif // USE_SENSOR_NAMES
282 // Needs to be aligned with mcuTypeId_e
283 static const char *mcuTypeNames[] = {
284 "SIMULATOR",
285 "F40X",
286 "F411",
287 "F446",
288 "F722",
289 "F745",
290 "F746",
291 "F765",
292 "H750",
293 "H743 (Rev Unknown)",
294 "H743 (Rev.Y)",
295 "H743 (Rev.X)",
296 "H743 (Rev.V)",
297 "H7A3",
298 "H723/H725",
299 "G474",
300 "H730",
301 "AT32F435",
302 "APM32F405",
303 "APM32F407",
306 static const char *configurationStates[] = {
307 [CONFIGURATION_STATE_UNCONFIGURED] = "UNCONFIGURED",
308 [CONFIGURATION_STATE_CONFIGURED] = "CONFIGURED"
311 typedef enum dumpFlags_e {
312 DUMP_MASTER = (1 << 0),
313 DUMP_PROFILE = (1 << 1),
314 DUMP_RATES = (1 << 2),
315 DUMP_ALL = (1 << 3),
316 DO_DIFF = (1 << 4),
317 SHOW_DEFAULTS = (1 << 5),
318 HIDE_UNUSED = (1 << 6),
319 HARDWARE_ONLY = (1 << 7),
320 BARE = (1 << 8),
321 } dumpFlags_t;
323 static void cliExit(const bool reboot);
324 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
326 typedef enum {
327 REBOOT_TARGET_FIRMWARE,
328 REBOOT_TARGET_BOOTLOADER_ROM,
329 REBOOT_TARGET_BOOTLOADER_FLASH,
330 } rebootTarget_e;
332 typedef struct serialPassthroughPort_s {
333 serialPortIdentifier_e id;
334 uint32_t baud;
335 portMode_e mode;
336 portOptions_e options;
337 serialPort_t *port;
338 } serialPassthroughPort_t;
340 static void cliClearInputBuffer(void)
342 memset(cliBuffer, 0, sizeof(cliBuffer));
343 bufferIndex = 0;
346 static void cliWriterFlushInternal(bufWriter_t *writer)
348 if (writer) {
349 bufWriterFlush(writer);
353 static void cliPrintInternal(bufWriter_t *writer, const char *str)
355 if (writer) {
356 while (*str) {
357 bufWriterAppend(writer, *str++);
359 cliWriterFlushInternal(writer);
363 static void cliWriterFlush(void)
365 cliWriterFlushInternal(cliWriter);
368 void cliPrint(const char *str)
370 cliPrintInternal(cliWriter, str);
373 void cliPrintLinefeed(void)
375 cliPrint("\r\n");
378 void cliPrintLine(const char *str)
380 cliPrint(str);
381 cliPrintLinefeed();
384 #ifdef MINIMAL_CLI
385 #define cliPrintHashLine(str)
386 #else
387 static void cliPrintHashLine(const char *str)
389 cliPrint("\r\n# ");
390 cliPrintLine(str);
392 #endif
394 static void cliPutp(void *p, char ch)
396 bufWriterAppend(p, ch);
399 static void cliPrintfva(const char *format, va_list va)
401 if (cliWriter) {
402 tfp_format(cliWriter, cliPutp, format, va);
403 cliWriterFlush();
407 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
409 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
410 va_list va;
411 va_start(va, format);
412 cliPrintfva(format, va);
413 va_end(va);
414 cliPrintLinefeed();
415 return true;
416 } else {
417 return false;
421 static void cliWrite(uint8_t ch)
423 if (cliWriter) {
424 bufWriterAppend(cliWriter, ch);
428 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
430 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
431 cliWrite('#');
433 va_list va;
434 va_start(va, format);
435 cliPrintfva(format, va);
436 va_end(va);
437 cliPrintLinefeed();
438 return true;
439 } else {
440 return false;
444 void cliPrintf(const char *format, ...)
446 va_list va;
447 va_start(va, format);
448 cliPrintfva(format, va);
449 va_end(va);
452 void cliPrintLinef(const char *format, ...)
454 va_list va;
455 va_start(va, format);
456 cliPrintfva(format, va);
457 va_end(va);
458 cliPrintLinefeed();
461 static void cliPrintErrorVa(const char *cmdName, const char *format, va_list va)
463 if (cliErrorWriter) {
464 cliPrintInternal(cliErrorWriter, "###ERROR IN ");
465 cliPrintInternal(cliErrorWriter, cmdName);
466 cliPrintInternal(cliErrorWriter, ": ");
468 tfp_format(cliErrorWriter, cliPutp, format, va);
469 va_end(va);
471 cliPrintInternal(cliErrorWriter, "###");
474 #ifdef USE_CLI_BATCH
475 if (commandBatchActive) {
476 commandBatchError = true;
478 #endif
481 static void cliPrintError(const char *cmdName, const char *format, ...)
483 va_list va;
484 va_start(va, format);
485 cliPrintErrorVa(cmdName, format, va);
486 va_end(va);
488 if (!cliWriter) {
489 // Supply our own linefeed in case we are printing inside a custom defaults operation
490 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
491 // instead of expecting the directly following command to supply the line feed.
492 cliPrintInternal(cliErrorWriter, "\r\n");
496 static void cliPrintErrorLinef(const char *cmdName, const char *format, ...)
498 va_list va;
499 va_start(va, format);
500 cliPrintErrorVa(cmdName, format, va);
501 va_end(va);
502 cliPrintInternal(cliErrorWriter, "\r\n");
505 static void getMinMax(const clivalue_t *var, int *min, int *max)
507 switch (var->type & VALUE_TYPE_MASK) {
508 case VAR_UINT8:
509 case VAR_UINT16:
510 *min = var->config.minmaxUnsigned.min;
511 *max = var->config.minmaxUnsigned.max;
513 break;
514 default:
515 *min = var->config.minmax.min;
516 *max = var->config.minmax.max;
518 break;
522 static void printValuePointer(const char *cmdName, const clivalue_t *var, const void *valuePointer, bool full)
524 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
525 for (int i = 0; i < var->config.array.length; i++) {
526 switch (var->type & VALUE_TYPE_MASK) {
527 default:
528 case VAR_UINT8:
529 // uint8_t array
530 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
531 break;
533 case VAR_INT8:
534 // int8_t array
535 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
536 break;
538 case VAR_UINT16:
539 // uin16_t array
540 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
541 break;
543 case VAR_INT16:
544 // int16_t array
545 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
546 break;
548 case VAR_UINT32:
549 // uin32_t array
550 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
551 break;
553 case VAR_INT32:
554 // in32_t array
555 cliPrintf("%d", ((int32_t *)valuePointer)[i]);
556 break;
559 if (i < var->config.array.length - 1) {
560 cliPrint(",");
563 } else {
564 int value = 0;
566 switch (var->type & VALUE_TYPE_MASK) {
567 case VAR_UINT8:
568 value = *(uint8_t *)valuePointer;
570 break;
571 case VAR_INT8:
572 value = *(int8_t *)valuePointer;
574 break;
575 case VAR_UINT16:
576 value = *(uint16_t *)valuePointer;
578 break;
579 case VAR_INT16:
580 value = *(int16_t *)valuePointer;
582 break;
583 case VAR_UINT32:
584 value = *(uint32_t *)valuePointer;
586 break;
587 case VAR_INT32:
588 value = *(int32_t *)valuePointer;
590 break;
593 bool valueIsCorrupted = false;
594 switch (var->type & VALUE_MODE_MASK) {
595 case MODE_DIRECT:
596 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
597 cliPrintf("%u", (uint32_t)value);
598 if ((uint32_t)value > var->config.u32Max) {
599 valueIsCorrupted = true;
600 } else if (full) {
601 cliPrintf(" 0 %u", var->config.u32Max);
603 } else if ((var->type & VALUE_TYPE_MASK) == VAR_INT32) {
604 cliPrintf("%d", (int32_t)value);
605 if ((int32_t)value > var->config.d32Max || (int32_t)value < -var->config.d32Max) {
606 valueIsCorrupted = true;
607 } else if (full) {
608 cliPrintf(" 0 %u", var->config.u32Max);
610 } else {
611 int min;
612 int max;
613 getMinMax(var, &min, &max);
615 cliPrintf("%d", value);
616 if ((value < min) || (value > max)) {
617 valueIsCorrupted = true;
618 } else if (full) {
619 cliPrintf(" %d %d", min, max);
622 break;
623 case MODE_LOOKUP:
624 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
625 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
626 } else {
627 valueIsCorrupted = true;
629 break;
630 case MODE_BITSET:
631 if (value & 1 << var->config.bitpos) {
632 cliPrintf("ON");
633 } else {
634 cliPrintf("OFF");
636 break;
637 case MODE_STRING:
638 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
639 break;
642 if (valueIsCorrupted) {
643 cliPrintLinefeed();
644 cliPrintError(cmdName, "CORRUPTED CONFIG: %s = %d", var->name, value);
649 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
651 bool result = true;
652 int elementCount = 1;
653 uint32_t mask = 0xffffffff;
655 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
656 elementCount = var->config.array.length;
658 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
659 mask = 1 << var->config.bitpos;
661 for (int i = 0; i < elementCount; i++) {
662 switch (var->type & VALUE_TYPE_MASK) {
663 case VAR_UINT8:
664 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
665 break;
667 case VAR_INT8:
668 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
669 break;
671 case VAR_UINT16:
672 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
673 break;
674 case VAR_INT16:
675 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
676 break;
677 case VAR_UINT32:
678 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
679 break;
680 case VAR_INT32:
681 result = result && (((int32_t *)ptr)[i] & mask) == (((int32_t *)ptrDefault)[i] & mask);
682 break;
686 return result;
689 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
691 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
692 cliPrintHashLine(headingStr);
693 return NULL;
694 } else {
695 return headingStr;
699 static void backupPgConfig(const pgRegistry_t *pg)
701 memcpy(pg->copy, pg->address, pg->size);
704 static void restorePgConfig(const pgRegistry_t *pg, uint16_t notToRestoreGroupId)
706 if (!notToRestoreGroupId || pgN(pg) != notToRestoreGroupId) {
707 memcpy(pg->address, pg->copy, pg->size);
711 static void backupConfigs(void)
713 if (configIsInCopy) {
714 return;
717 PG_FOREACH(pg) {
718 backupPgConfig(pg);
721 configIsInCopy = true;
724 static void restoreConfigs(uint16_t notToRestoreGroupId)
726 if (!configIsInCopy) {
727 return;
730 PG_FOREACH(pg) {
731 restorePgConfig(pg, notToRestoreGroupId);
734 configIsInCopy = false;
737 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
738 static bool isReadingConfigFromCopy(void)
740 return configIsInCopy;
742 #endif
744 static bool isWritingConfigToCopy(void)
746 return configIsInCopy;
749 static void backupAndResetConfigs(void)
751 backupConfigs();
752 // reset all configs to defaults to do differencing
753 resetConfig();
756 static uint8_t getPidProfileIndexToUse(void)
758 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
761 static uint8_t getRateProfileIndexToUse(void)
763 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
766 static uint16_t getValueOffset(const clivalue_t *value)
768 switch (value->type & VALUE_SECTION_MASK) {
769 case MASTER_VALUE:
770 case HARDWARE_VALUE:
771 return value->offset;
772 case PROFILE_VALUE:
773 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
774 case PROFILE_RATE_VALUE:
775 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
777 return 0;
780 STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
782 const pgRegistry_t* rec = pgFind(value->pgn);
783 if (isWritingConfigToCopy()) {
784 return CONST_CAST(void *, rec->copy + getValueOffset(value));
785 } else {
786 return CONST_CAST(void *, rec->address + getValueOffset(value));
790 static const char *dumpPgValue(const char *cmdName, const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
792 const pgRegistry_t *pg = pgFind(value->pgn);
793 #ifdef DEBUG
794 if (!pg) {
795 cliPrintLinef("VALUE %s ERROR", value->name);
796 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
798 #endif
800 const char *format = "set %s = ";
801 const char *defaultFormat = "#set %s = ";
802 const int valueOffset = getValueOffset(value);
803 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
805 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
806 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
807 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
808 cliPrintf(defaultFormat, value->name);
809 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
810 cliPrintLinefeed();
812 cliPrintf(format, value->name);
813 printValuePointer(cmdName, value, pg->copy + valueOffset, false);
814 cliPrintLinefeed();
816 return headingStr;
819 static void dumpAllValues(const char *cmdName, uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
821 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
823 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
824 const clivalue_t *value = &valueTable[i];
825 cliWriterFlush();
826 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
827 headingStr = dumpPgValue(cmdName, value, dumpMask, headingStr);
832 static void cliPrintVar(const char *cmdName, const clivalue_t *var, bool full)
834 const void *ptr = cliGetValuePointer(var);
836 printValuePointer(cmdName, var, ptr, full);
839 static void cliPrintVarRange(const clivalue_t *var)
841 switch (var->type & VALUE_MODE_MASK) {
842 case (MODE_DIRECT): {
843 switch (var->type & VALUE_TYPE_MASK) {
844 case VAR_UINT32:
845 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
847 break;
848 case VAR_INT32:
849 cliPrintLinef("Allowed range: %d - %d", -var->config.d32Max, var->config.d32Max);
851 break;
852 case VAR_UINT8:
853 case VAR_UINT16:
854 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
856 break;
857 default:
858 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
860 break;
863 break;
864 case (MODE_LOOKUP): {
865 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
866 cliPrint("Allowed values: ");
867 bool firstEntry = true;
868 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
869 if (tableEntry->values[i]) {
870 if (!firstEntry) {
871 cliPrint(", ");
873 cliPrintf("%s", tableEntry->values[i]);
874 firstEntry = false;
877 cliPrintLinefeed();
879 break;
880 case (MODE_ARRAY): {
881 cliPrintLinef("Array length: %d", var->config.array.length);
883 break;
884 case (MODE_STRING): {
885 cliPrintLinef("String length: %d - %d", var->config.string.minlength, var->config.string.maxlength);
887 break;
888 case (MODE_BITSET): {
889 cliPrintLinef("Allowed values: OFF, ON");
891 break;
895 static void cliSetVar(const clivalue_t *var, const uint32_t value)
897 void *ptr = cliGetValuePointer(var);
898 uint32_t workValue;
899 uint32_t mask;
901 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
902 switch (var->type & VALUE_TYPE_MASK) {
903 case VAR_UINT8:
904 mask = (1 << var->config.bitpos) & 0xff;
905 if (value) {
906 workValue = *(uint8_t *)ptr | mask;
907 } else {
908 workValue = *(uint8_t *)ptr & ~mask;
910 *(uint8_t *)ptr = workValue;
911 break;
913 case VAR_UINT16:
914 mask = (1 << var->config.bitpos) & 0xffff;
915 if (value) {
916 workValue = *(uint16_t *)ptr | mask;
917 } else {
918 workValue = *(uint16_t *)ptr & ~mask;
920 *(uint16_t *)ptr = workValue;
921 break;
923 case VAR_UINT32:
924 mask = 1 << var->config.bitpos;
925 if (value) {
926 workValue = *(uint32_t *)ptr | mask;
927 } else {
928 workValue = *(uint32_t *)ptr & ~mask;
930 *(uint32_t *)ptr = workValue;
931 break;
933 case VAR_INT32:
934 mask = 1 << var->config.bitpos;
935 if (value) {
936 workValue = *(int32_t *)ptr | mask;
937 } else {
938 workValue = *(int32_t *)ptr & ~mask;
940 *(int32_t *)ptr = workValue;
941 break;
943 } else {
944 switch (var->type & VALUE_TYPE_MASK) {
945 case VAR_UINT8:
946 *(uint8_t *)ptr = value;
947 break;
949 case VAR_INT8:
950 *(int8_t *)ptr = value;
951 break;
953 case VAR_UINT16:
954 *(uint16_t *)ptr = value;
955 break;
957 case VAR_INT16:
958 *(int16_t *)ptr = value;
959 break;
961 case VAR_UINT32:
962 *(uint32_t *)ptr = value;
963 break;
965 case VAR_INT32:
966 *(int32_t *)ptr = value;
967 break;
972 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
973 static void cliRepeat(char ch, uint8_t len)
975 if (cliWriter) {
976 for (int i = 0; i < len; i++) {
977 bufWriterAppend(cliWriter, ch);
979 cliPrintLinefeed();
982 #endif
984 static void cliPrompt(void)
986 cliPrint("\r\n# ");
989 static void cliShowParseError(const char *cmdName)
991 cliPrintErrorLinef(cmdName, "PARSING FAILED");
994 static void cliShowInvalidArgumentCountError(const char *cmdName)
996 cliPrintErrorLinef(cmdName, "INVALID ARGUMENT COUNT");
999 static void cliShowArgumentRangeError(const char *cmdName, char *name, int min, int max)
1001 if (name) {
1002 cliPrintErrorLinef(cmdName, "%s NOT BETWEEN %d AND %d", name, min, max);
1003 } else {
1004 cliPrintErrorLinef(cmdName, "ARGUMENT OUT OF RANGE");
1008 static const char *nextArg(const char *currentArg)
1010 const char *ptr = strchr(currentArg, ' ');
1011 while (ptr && *ptr == ' ') {
1012 ptr++;
1015 return ptr;
1018 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
1020 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
1021 ptr = nextArg(ptr);
1022 if (ptr) {
1023 int val = atoi(ptr);
1024 val = CHANNEL_VALUE_TO_STEP(val);
1025 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
1026 if (argIndex == 0) {
1027 range->startStep = val;
1028 } else {
1029 range->endStep = val;
1031 (*validArgumentCount)++;
1036 return ptr;
1039 // Check if a string's length is zero
1040 static bool isEmpty(const char *string)
1042 return (string == NULL || *string == '\0') ? true : false;
1045 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
1047 // print out rxConfig failsafe settings
1048 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1049 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1050 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
1051 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
1052 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
1053 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1054 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1055 if (requireValue) {
1056 const char *format = "rxfail %u %c %d";
1057 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1058 channel,
1059 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
1060 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
1062 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1063 channel,
1064 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
1065 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1067 } else {
1068 const char *format = "rxfail %u %c";
1069 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1070 channel,
1071 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
1073 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1074 channel,
1075 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
1081 static void cliRxFailsafe(const char *cmdName, char *cmdline)
1083 uint8_t channel;
1084 char buf[3];
1086 if (isEmpty(cmdline)) {
1087 // print out rxConfig failsafe settings
1088 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1089 cliRxFailsafe(cmdName, itoa(channel, buf, 10));
1091 } else {
1092 const char *ptr = cmdline;
1093 channel = atoi(ptr++);
1094 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
1096 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
1098 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
1099 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
1100 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1102 ptr = nextArg(ptr);
1103 if (ptr) {
1104 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
1105 if (p) {
1106 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
1107 mode = rxFailsafeModesTable[type][requestedMode];
1108 } else {
1109 mode = RX_FAILSAFE_MODE_INVALID;
1111 if (mode == RX_FAILSAFE_MODE_INVALID) {
1112 cliShowParseError(cmdName);
1113 return;
1116 requireValue = mode == RX_FAILSAFE_MODE_SET;
1118 ptr = nextArg(ptr);
1119 if (ptr) {
1120 if (!requireValue) {
1121 cliShowParseError(cmdName);
1122 return;
1124 uint16_t value = atoi(ptr);
1125 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
1126 if (value > MAX_RXFAIL_RANGE_STEP) {
1127 cliPrintErrorLinef(cmdName, "value out of range: %d", value);
1128 return;
1131 channelFailsafeConfig->step = value;
1132 } else if (requireValue) {
1133 cliShowInvalidArgumentCountError(cmdName);
1134 return;
1136 channelFailsafeConfig->mode = mode;
1139 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
1141 // double use of cliPrintf below
1142 // 1. acknowledge interpretation on command,
1143 // 2. query current setting on single item,
1145 if (requireValue) {
1146 cliPrintLinef("rxfail %u %c %d",
1147 channel,
1148 modeCharacter,
1149 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1151 } else {
1152 cliPrintLinef("rxfail %u %c",
1153 channel,
1154 modeCharacter
1157 } else {
1158 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1163 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1165 const char *format = "aux %u %u %u %u %u %u %u";
1166 // print out aux channel settings
1167 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1168 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1169 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1170 bool equalsDefault = false;
1171 if (defaultModeActivationConditions) {
1172 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1173 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1174 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1175 const box_t *box = findBoxByBoxId(macDefault->modeId);
1176 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1177 if (box) {
1178 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1180 box->permanentId,
1181 macDefault->auxChannelIndex,
1182 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1183 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1184 macDefault->modeLogic,
1185 linkedTo ? linkedTo->permanentId : 0
1189 const box_t *box = findBoxByBoxId(mac->modeId);
1190 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1191 if (box) {
1192 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1194 box->permanentId,
1195 mac->auxChannelIndex,
1196 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1197 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1198 mac->modeLogic,
1199 linkedTo ? linkedTo->permanentId : 0
1205 static void cliAux(const char *cmdName, char *cmdline)
1207 int i, val = 0;
1208 const char *ptr;
1210 if (isEmpty(cmdline)) {
1211 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1212 } else {
1213 ptr = cmdline;
1214 i = atoi(ptr++);
1215 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1216 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1217 uint8_t validArgumentCount = 0;
1218 ptr = nextArg(ptr);
1219 if (ptr) {
1220 val = atoi(ptr);
1221 const box_t *box = findBoxByPermanentId(val);
1222 if (box) {
1223 mac->modeId = box->boxId;
1224 validArgumentCount++;
1227 ptr = nextArg(ptr);
1228 if (ptr) {
1229 val = atoi(ptr);
1230 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1231 mac->auxChannelIndex = val;
1232 validArgumentCount++;
1235 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1236 ptr = nextArg(ptr);
1237 if (ptr) {
1238 val = atoi(ptr);
1239 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1240 mac->modeLogic = val;
1241 validArgumentCount++;
1244 ptr = nextArg(ptr);
1245 if (ptr) {
1246 val = atoi(ptr);
1247 const box_t *box = findBoxByPermanentId(val);
1248 if (box) {
1249 mac->linkedTo = box->boxId;
1250 validArgumentCount++;
1253 if (validArgumentCount == 4) { // for backwards compatibility
1254 mac->modeLogic = MODELOGIC_OR;
1255 mac->linkedTo = 0;
1256 } else if (validArgumentCount == 5) { // for backwards compatibility
1257 mac->linkedTo = 0;
1258 } else if (validArgumentCount != 6) {
1259 memset(mac, 0, sizeof(modeActivationCondition_t));
1261 analyzeModeActivationConditions();
1262 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1264 findBoxByBoxId(mac->modeId)->permanentId,
1265 mac->auxChannelIndex,
1266 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1267 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1268 mac->modeLogic,
1269 findBoxByBoxId(mac->linkedTo)->permanentId
1271 } else {
1272 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1277 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1279 const char *format = "serial %s %d %ld %ld %ld %ld";
1280 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1281 for (unsigned i = 0; i < ARRAYLEN(serialConfig->portConfigs); i++) {
1282 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1283 continue;
1285 bool equalsDefault = false;
1286 if (serialConfigDefault) {
1287 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1288 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1289 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1290 serialName(serialConfigDefault->portConfigs[i].identifier, invalidName),
1291 serialConfigDefault->portConfigs[i].functionMask,
1292 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1293 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1294 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1295 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1298 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1299 serialName(serialConfig->portConfigs[i].identifier, invalidName),
1300 serialConfig->portConfigs[i].functionMask,
1301 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1302 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1303 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1304 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1309 static void cliSerial(const char *cmdName, char *cmdline)
1311 const char *format = "serial %s %d %ld %ld %ld %ld";
1312 if (isEmpty(cmdline)) {
1313 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1314 return;
1317 serialPortConfig_t portConfig;
1318 memset(&portConfig, 0 , sizeof(portConfig));
1320 uint8_t validArgumentCount = 0;
1322 char *ptr = cmdline;
1323 char *tok = strsep(&ptr, " ");
1324 serialPortIdentifier_e identifier = findSerialPortByName(tok, strcasecmp);
1325 if (identifier == SERIAL_PORT_NONE) {
1326 char *eptr;
1327 identifier = strtoul(tok, &eptr, 10);
1328 if (*eptr) {
1329 // parsing ended before end of token indicating an invalid identifier
1330 identifier = SERIAL_PORT_NONE;
1331 } else {
1332 // correction for legacy configuration where UART1 == 0
1333 if (identifier >= SERIAL_PORT_LEGACY_START_IDENTIFIER && identifier < SERIAL_PORT_START_IDENTIFIER) {
1334 identifier += SERIAL_PORT_UART1;
1339 serialPortConfig_t *currentConfig = serialFindPortConfigurationMutable(identifier);
1341 if (!currentConfig) {
1342 cliShowParseError(cmdName);
1343 return;
1346 portConfig.identifier = identifier;
1347 validArgumentCount++;
1349 tok = strsep(&ptr, " ");
1350 if (tok) {
1351 int val = strtoul(tok, NULL, 10);
1352 portConfig.functionMask = val;
1353 validArgumentCount++;
1356 for (int i = 0; i < 4; i ++) {
1357 tok = strsep(&ptr, " ");
1358 if (!tok) {
1359 break;
1362 int val = atoi(tok);
1364 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1365 if (baudRates[baudRateIndex] != (uint32_t) val) {
1366 break;
1369 switch (i) {
1370 case 0:
1371 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1372 continue;
1374 portConfig.msp_baudrateIndex = baudRateIndex;
1375 break;
1376 case 1:
1377 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1378 continue;
1380 portConfig.gps_baudrateIndex = baudRateIndex;
1381 break;
1382 case 2:
1383 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1384 continue;
1386 portConfig.telemetry_baudrateIndex = baudRateIndex;
1387 break;
1388 case 3:
1389 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1390 continue;
1392 portConfig.blackbox_baudrateIndex = baudRateIndex;
1393 break;
1396 validArgumentCount++;
1399 if (validArgumentCount < 6) {
1400 cliShowInvalidArgumentCountError(cmdName);
1401 return;
1404 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1406 cliDumpPrintLinef(0, false, format,
1407 serialName(portConfig.identifier, invalidName),
1408 portConfig.functionMask,
1409 baudRates[portConfig.msp_baudrateIndex],
1410 baudRates[portConfig.gps_baudrateIndex],
1411 baudRates[portConfig.telemetry_baudrateIndex],
1412 baudRates[portConfig.blackbox_baudrateIndex]
1416 #if defined(USE_SERIAL_PASSTHROUGH)
1417 static void cbCtrlLine_reset(void *context, uint16_t ctrl)
1419 UNUSED(context);
1420 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1421 systemReset();
1425 #ifdef USE_PINIO
1426 static void cbCtrlLine_pinIO(void *context, uint16_t ctrl)
1428 pinioSet((intptr_t)context, !(ctrl & CTRL_LINE_STATE_DTR));
1430 #endif
1432 static portMode_e cliParseSerialMode(const char *tok)
1434 portMode_e mode = 0;
1436 if (strcasestr(tok, "rx")) {
1437 mode |= MODE_RX;
1439 if (strcasestr(tok, "tx")) {
1440 mode |= MODE_TX;
1443 return mode;
1446 static portOptions_e cliParseSerialOptions(const char *tok)
1448 struct {
1449 const char* tag;
1450 portOptions_e val;
1451 } map[] = {
1452 {"Invert", SERIAL_INVERTED},
1453 {"Stop2", SERIAL_STOPBITS_2},
1454 {"Even", SERIAL_PARITY_EVEN},
1455 {"Bidir", SERIAL_BIDIR},
1456 {"Pushpull", SERIAL_BIDIR_PP},
1457 {"Saudio", SERIAL_PULL_SMARTAUDIO},
1458 {"Check", SERIAL_CHECK_TX},
1460 portOptions_e options = 0;
1461 for (unsigned i = 0; i < ARRAYLEN(map); i++) {
1462 if (strstr(tok, map[i].tag) != 0) {
1463 options |= map[i].val;
1466 return options;
1469 static void cliSerialPassthrough(const char *cmdName, char *cmdline)
1471 if (isEmpty(cmdline)) {
1472 cliShowInvalidArgumentCountError(cmdName);
1473 return;
1476 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, 0, NULL}, {cliPort->identifier, 0, 0, 0, cliPort} };
1477 bool enableBaudCb = false;
1478 #ifdef USE_PINIO
1479 int port1PinioDtr = -1; // route port2 USB DTR to pinio
1480 #endif
1481 bool port1ResetOnDtr = false; // reset board with DTR
1482 bool escSensorPassthrough = false;
1484 char* nexttok = cmdline;
1485 char* tok;
1486 int index = 0;
1487 while ((tok = strsep(&nexttok, " ")) != NULL) {
1488 if (*tok == '\0') { // skip adjacent delimiters
1489 continue;
1491 unsigned portN = (index < 4) ? 0 : 1; // port1 / port2
1492 switch (index) {
1493 case 0: // port1 to open: esc_sensor, portName, port ID port1
1494 case 4: // port2 to use (defaults to CLI serial if no more arguments)
1496 serialPortIdentifier_e portId;
1497 char* endptr;
1498 if (portN == 0 && strcasestr(tok, "esc_sensor") != NULL) {
1499 escSensorPassthrough = true;
1500 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1501 portId = portConfig ? portConfig->identifier : SERIAL_PORT_NONE;
1502 } else if (strcasecmp(tok, "cli") == 0) {
1503 portId = cliPort->identifier;
1504 } else if ((portId = findSerialPortByName(tok, strcasecmp)) >= 0) {
1505 // empty
1506 } else if ((portId = strtol(tok, &endptr, 10)) >= 0 && *endptr == '\0') {
1507 // empty
1508 } else {
1509 cliPrintLinef("Failed parsing port%d (%s)", portN + 1, tok);
1510 return;
1512 if (portN == 1) { // port1 is specified, don't use CLI port
1513 ports[portN].port = NULL;
1515 ports[portN].id = portId;
1516 break;
1518 case 1: // baudrate
1519 case 5: {
1520 int baud = atoi(tok);
1521 ports[portN].baud = baud;
1522 break;
1524 case 2: // port1 mode (rx/tx/rxtx) + options
1525 case 6: // port2 mode + options
1526 ports[portN].mode = cliParseSerialMode(tok);
1527 ports[portN].options = cliParseSerialOptions(tok);
1528 break;
1529 case 3: // DTR action
1530 if (strcasecmp(tok, "reset") == 0) {
1531 port1ResetOnDtr = true;
1532 break;
1534 if (strcasecmp(tok, "none") == 0) {
1535 break;
1537 #ifdef USE_PINIO
1538 port1PinioDtr = atoi(tok);
1539 if (port1PinioDtr < 0 || port1PinioDtr >= PINIO_COUNT) {
1540 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1541 return;
1543 #endif /* USE_PINIO */
1544 break;
1545 default:
1546 cliPrintLinef("Unexpected argument %d (%s)", index + 1, tok);
1547 return;
1549 index++;
1552 for (unsigned i = 0; i < ARRAYLEN(ports); i++) {
1553 if (findSerialPortIndexByIdentifier(ports[i].id) < 0) {
1554 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1555 return;
1556 } else {
1557 cliPrintLinef("Port%d: %s", i + 1, serialName(ports[i].id, "<invalid>"));
1561 // Port checks
1562 if (ports[0].id == ports[1].id) {
1563 cliPrintLinef("Port1 and port2 are same");
1564 return ;
1567 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1568 enableBaudCb = true;
1571 for (int i = 0; i < 2; i++) {
1572 serialPassthroughPort_t* cfg = &ports[i];
1573 if (cfg->port != NULL) { // port already selected, don't touch it (used when port2 defaults to cli)
1574 continue;
1577 int portIndex = i + 1;
1578 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(cfg->id);
1579 if (!portUsage || portUsage->serialPort == NULL) {
1580 // serial port is not open yet
1581 const bool isUseDefaultBaud = cfg->baud == 0;
1582 if (isUseDefaultBaud) {
1583 // Set default baud
1584 cfg->baud = 57600;
1587 if (!cfg->mode) {
1588 cliPrintLinef("Using RXTX mode as default");
1589 cfg->mode = MODE_RXTX;
1592 if (cfg->options) {
1593 cliPrintLinef("Port%d: using options 0x%x",
1594 portIndex, cfg->options);
1596 cfg->port = openSerialPort(cfg->id, FUNCTION_NONE,
1597 NULL, NULL, // rxCallback
1598 cfg->baud, cfg->mode, cfg->options);
1599 if (!cfg->port) {
1600 cliPrintLinef("Port%d could not be opened.", portIndex);
1601 return;
1604 cliPrintf("Port%d opened, %sbaud = %d.\r\n", portIndex, isUseDefaultBaud ? "default ":"", cfg->baud);
1605 } else {
1606 cfg->port = portUsage->serialPort;
1607 // If the user supplied a mode, override the port's mode, otherwise
1608 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1609 // Set the baud rate if specified
1610 if (cfg->baud) {
1611 serialSetBaudRate(cfg->port, cfg->baud);
1613 cliPrintLinef("Port%d is already open, %sbaud = %d.", portIndex, cfg->baud ? "new " : "", cfg->port->baudRate);
1615 if (cfg->mode && cfg->port->mode != cfg->mode) {
1616 cliPrintLinef("Port%d mode changed from %d to %d.",
1617 portIndex, cfg->port->mode, cfg->mode);
1618 serialSetMode(cfg->port, cfg->mode);
1621 if (cfg->options) {
1622 cliPrintLinef("Port%d is open, can't change options from 0x%x to 0x%x",
1623 portIndex, cfg->port->options, cfg->options);
1626 // If this port has a rx callback associated we need to remove it now.
1627 // Otherwise no data will be pushed in the serial port buffer!
1628 if (cfg->port->rxCallback) {
1629 cliPrintLinef("Port%d: Callback removed", portIndex);
1630 cfg->port->rxCallback = NULL;
1635 // If no baud rate is specified allow to be set via USB
1636 if (enableBaudCb) {
1637 cliPrintLine("Port1 baud rate change over USB enabled.");
1638 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1639 // baud rate over USB without setting it using the serialpassthrough command
1640 serialSetBaudRateCb(ports[1].port, serialSetBaudRate, ports[0].port);
1643 const char *resetMessage = "";
1644 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1645 resetMessage = "or drop DTR ";
1648 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1650 if ((ports[1].id == SERIAL_PORT_USB_VCP)) {
1651 do {
1652 if (port1ResetOnDtr) {
1653 serialSetCtrlLineStateCb(ports[1].port, cbCtrlLine_reset, NULL);
1654 break;
1656 #ifdef USE_PINIO
1657 if (port1PinioDtr >= 0) {
1658 serialSetCtrlLineStateCb(ports[1].port, cbCtrlLine_pinIO, (void *)(intptr_t)(port1PinioDtr));
1659 break;
1661 #endif /* USE_PINIO */
1662 } while (0);
1665 // XXX Review ESC pass through under refactored motor handling
1666 #ifdef USE_PWM_OUTPUT
1667 if (escSensorPassthrough) {
1668 // pwmDisableMotors();
1669 motorDisable();
1670 delay(5);
1671 for (unsigned i = 0; i < getMotorCount(); i++) {
1672 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1673 if (tag) {
1674 const timerHardware_t *timerHardware = timerGetConfiguredByTag(tag);
1675 if (timerHardware) {
1676 IO_t io = IOGetByTag(tag);
1677 IOInit(io, OWNER_MOTOR, i);
1678 IOConfigGPIO(io, IOCFG_OUT_PP);
1679 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1680 IOLo(io);
1681 } else {
1682 IOHi(io);
1688 #endif
1690 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1692 #endif
1694 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1696 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1697 // print out adjustment ranges channel settings
1698 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1699 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1700 const adjustmentRange_t *ar = &adjustmentRanges[i];
1701 bool equalsDefault = false;
1702 if (defaultAdjustmentRanges) {
1703 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1704 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1705 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1706 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1708 arDefault->auxChannelIndex,
1709 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1710 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1711 arDefault->adjustmentConfig,
1712 arDefault->auxSwitchChannelIndex,
1713 arDefault->adjustmentCenter,
1714 arDefault->adjustmentScale
1717 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1719 ar->auxChannelIndex,
1720 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1721 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1722 ar->adjustmentConfig,
1723 ar->auxSwitchChannelIndex,
1724 ar->adjustmentCenter,
1725 ar->adjustmentScale
1730 static void cliAdjustmentRange(const char *cmdName, char *cmdline)
1732 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1733 const char *ptr;
1735 if (isEmpty(cmdline)) {
1736 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1737 } else {
1738 ptr = cmdline;
1739 int i = atoi(ptr++);
1740 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1741 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1742 uint8_t validArgumentCount = 0;
1744 ptr = nextArg(ptr);
1745 if (ptr) {
1746 // Was: slot
1747 // Keeping the parameter to retain backwards compatibility for the command format.
1748 validArgumentCount++;
1750 ptr = nextArg(ptr);
1751 if (ptr) {
1752 int val = atoi(ptr);
1753 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1754 ar->auxChannelIndex = val;
1755 validArgumentCount++;
1759 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1761 ptr = nextArg(ptr);
1762 if (ptr) {
1763 int val = atoi(ptr);
1764 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1765 ar->adjustmentConfig = val;
1766 validArgumentCount++;
1769 ptr = nextArg(ptr);
1770 if (ptr) {
1771 int val = atoi(ptr);
1772 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1773 ar->auxSwitchChannelIndex = val;
1774 validArgumentCount++;
1778 if (validArgumentCount != 6) {
1779 memset(ar, 0, sizeof(adjustmentRange_t));
1780 cliShowInvalidArgumentCountError(cmdName);
1781 return;
1784 // Optional arguments
1785 ar->adjustmentCenter = 0;
1786 ar->adjustmentScale = 0;
1788 ptr = nextArg(ptr);
1789 if (ptr) {
1790 int val = atoi(ptr);
1791 ar->adjustmentCenter = val;
1792 validArgumentCount++;
1794 ptr = nextArg(ptr);
1795 if (ptr) {
1796 int val = atoi(ptr);
1797 ar->adjustmentScale = val;
1798 validArgumentCount++;
1801 activeAdjustmentRangeReset();
1803 cliDumpPrintLinef(0, false, format,
1805 ar->auxChannelIndex,
1806 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1807 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1808 ar->adjustmentConfig,
1809 ar->auxSwitchChannelIndex,
1810 ar->adjustmentCenter,
1811 ar->adjustmentScale
1814 } else {
1815 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1820 #ifndef USE_QUAD_MIXER_ONLY
1821 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1823 const char *format = "mmix %d %s %s %s %s";
1824 char buf0[FTOA_BUFFER_LENGTH];
1825 char buf1[FTOA_BUFFER_LENGTH];
1826 char buf2[FTOA_BUFFER_LENGTH];
1827 char buf3[FTOA_BUFFER_LENGTH];
1828 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1829 if (customMotorMixer[i].throttle == 0.0f)
1830 break;
1831 const float thr = customMotorMixer[i].throttle;
1832 const float roll = customMotorMixer[i].roll;
1833 const float pitch = customMotorMixer[i].pitch;
1834 const float yaw = customMotorMixer[i].yaw;
1835 bool equalsDefault = false;
1836 if (defaultCustomMotorMixer) {
1837 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1838 const float rollDefault = defaultCustomMotorMixer[i].roll;
1839 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1840 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1841 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1843 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1844 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1846 ftoa(thrDefault, buf0),
1847 ftoa(rollDefault, buf1),
1848 ftoa(pitchDefault, buf2),
1849 ftoa(yawDefault, buf3));
1851 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1853 ftoa(thr, buf0),
1854 ftoa(roll, buf1),
1855 ftoa(pitch, buf2),
1856 ftoa(yaw, buf3));
1859 #endif // USE_QUAD_MIXER_ONLY
1861 static void cliMotorMix(const char *cmdName, char *cmdline)
1863 #ifdef USE_QUAD_MIXER_ONLY
1864 UNUSED(cmdName);
1865 UNUSED(cmdline);
1866 #else
1867 int check = 0;
1868 uint8_t len;
1869 const char *ptr;
1871 if (isEmpty(cmdline)) {
1872 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1873 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1874 // erase custom mixer
1875 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1876 customMotorMixerMutable(i)->throttle = 0.0f;
1878 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1879 ptr = nextArg(cmdline);
1880 if (ptr) {
1881 len = strlen(ptr);
1882 for (uint32_t i = 0; ; i++) {
1883 if (mixerNames[i] == NULL) {
1884 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
1885 break;
1887 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1888 mixerLoadMix(i, customMotorMixerMutable(0));
1889 cliPrintLinef("Loaded %s", mixerNames[i]);
1890 cliMotorMix(cmdName, "");
1891 break;
1895 } else {
1896 ptr = cmdline;
1897 uint32_t i = atoi(ptr); // get motor number
1898 if (i < MAX_SUPPORTED_MOTORS) {
1899 ptr = nextArg(ptr);
1900 if (ptr) {
1901 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1902 check++;
1904 ptr = nextArg(ptr);
1905 if (ptr) {
1906 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1907 check++;
1909 ptr = nextArg(ptr);
1910 if (ptr) {
1911 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1912 check++;
1914 ptr = nextArg(ptr);
1915 if (ptr) {
1916 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1917 check++;
1919 if (check != 4) {
1920 cliShowInvalidArgumentCountError(cmdName);
1921 } else {
1922 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1924 } else {
1925 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1928 #endif
1931 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1933 const char *format = "rxrange %u %u %u";
1934 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1935 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1936 bool equalsDefault = false;
1937 if (defaultChannelRangeConfigs) {
1938 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1939 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1940 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1942 defaultChannelRangeConfigs[i].min,
1943 defaultChannelRangeConfigs[i].max
1946 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1948 channelRangeConfigs[i].min,
1949 channelRangeConfigs[i].max
1954 static void cliRxRange(const char *cmdName, char *cmdline)
1956 const char *format = "rxrange %u %u %u";
1957 int i, validArgumentCount = 0;
1958 const char *ptr;
1960 if (isEmpty(cmdline)) {
1961 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1962 } else if (strcasecmp(cmdline, "reset") == 0) {
1963 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1964 } else {
1965 ptr = cmdline;
1966 i = atoi(ptr);
1967 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1968 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1970 ptr = nextArg(ptr);
1971 if (ptr) {
1972 rangeMin = atoi(ptr);
1973 validArgumentCount++;
1976 ptr = nextArg(ptr);
1977 if (ptr) {
1978 rangeMax = atoi(ptr);
1979 validArgumentCount++;
1982 if (validArgumentCount != 2) {
1983 cliShowInvalidArgumentCountError(cmdName);
1984 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1985 cliShowArgumentRangeError(cmdName, "range min/max", PWM_PULSE_MIN, PWM_PULSE_MAX);
1986 } else {
1987 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1988 channelRangeConfig->min = rangeMin;
1989 channelRangeConfig->max = rangeMax;
1990 cliDumpPrintLinef(0, false, format,
1992 channelRangeConfig->min,
1993 channelRangeConfig->max
1997 } else {
1998 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
2003 #ifdef USE_LED_STRIP_STATUS_MODE
2004 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
2006 const char *format = "led %u %s";
2007 char ledConfigBuffer[20];
2008 char ledConfigDefaultBuffer[20];
2009 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2010 for (uint32_t i = 0; i < LED_STRIP_MAX_LENGTH; i++) {
2011 ledConfig_t ledConfig = ledConfigs[i];
2012 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
2013 bool equalsDefault = false;
2014 if (defaultLedConfigs) {
2015 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
2016 equalsDefault = ledConfig == ledConfigDefault;
2017 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2018 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
2019 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
2021 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
2025 static void cliLed(const char *cmdName, char *cmdline)
2027 const char *format = "led %u %s";
2028 char ledConfigBuffer[20];
2029 int i;
2030 const char *ptr;
2032 if (isEmpty(cmdline)) {
2033 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
2034 } else {
2035 ptr = cmdline;
2036 i = atoi(ptr);
2037 if (i >= 0 && i < LED_STRIP_MAX_LENGTH) {
2038 ptr = nextArg(cmdline);
2039 if (parseLedStripConfig(i, ptr)) {
2040 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
2041 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
2042 } else {
2043 cliShowParseError(cmdName);
2045 } else {
2046 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_STRIP_MAX_LENGTH - 1);
2051 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
2053 const char *format = "color %u %d,%u,%u";
2054 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2055 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
2056 const hsvColor_t *color = &colors[i];
2057 bool equalsDefault = false;
2058 if (defaultColors) {
2059 const hsvColor_t *colorDefault = &defaultColors[i];
2060 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
2061 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2062 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
2064 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
2068 static void cliColor(const char *cmdName, char *cmdline)
2070 const char *format = "color %u %d,%u,%u";
2071 if (isEmpty(cmdline)) {
2072 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
2073 } else {
2074 const char *ptr = cmdline;
2075 const int i = atoi(ptr);
2076 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
2077 ptr = nextArg(cmdline);
2078 if (parseColor(i, ptr)) {
2079 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
2080 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
2081 } else {
2082 cliShowParseError(cmdName);
2084 } else {
2085 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
2090 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
2092 const char *format = "mode_color %u %u %u";
2093 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2094 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
2095 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
2096 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
2097 bool equalsDefault = false;
2098 if (defaultLedStripConfig) {
2099 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
2100 equalsDefault = colorIndex == colorIndexDefault;
2101 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2102 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
2104 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
2108 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
2109 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
2110 bool equalsDefault = false;
2111 if (defaultLedStripConfig) {
2112 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
2113 equalsDefault = colorIndex == colorIndexDefault;
2114 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2115 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
2117 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
2120 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
2121 bool equalsDefault = false;
2122 if (defaultLedStripConfig) {
2123 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
2124 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
2125 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2126 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
2128 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
2131 static void cliModeColor(const char *cmdName, char *cmdline)
2133 if (isEmpty(cmdline)) {
2134 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
2135 } else {
2136 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
2137 int args[ARGS_COUNT];
2138 int argNo = 0;
2139 char *saveptr;
2140 const char* ptr = strtok_r(cmdline, " ", &saveptr);
2141 while (ptr && argNo < ARGS_COUNT) {
2142 args[argNo++] = atoi(ptr);
2143 ptr = strtok_r(NULL, " ", &saveptr);
2146 if (ptr != NULL || argNo != ARGS_COUNT) {
2147 cliShowInvalidArgumentCountError(cmdName);
2148 return;
2151 int modeIdx = args[MODE];
2152 int funIdx = args[FUNCTION];
2153 int color = args[COLOR];
2154 if (!setModeColor(modeIdx, funIdx, color)) {
2155 cliShowParseError(cmdName);
2156 return;
2158 // values are validated
2159 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
2162 #endif
2164 #ifdef USE_SERVOS
2165 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
2167 // print out servo settings
2168 const char *format = "servo %u %d %d %d %d %d";
2169 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2170 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2171 const servoParam_t *servoConf = &servoParams[i];
2172 bool equalsDefault = false;
2173 if (defaultServoParams) {
2174 const servoParam_t *defaultServoConf = &defaultServoParams[i];
2175 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
2176 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2177 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2179 defaultServoConf->min,
2180 defaultServoConf->max,
2181 defaultServoConf->middle,
2182 defaultServoConf->rate,
2183 defaultServoConf->forwardFromChannel
2186 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2188 servoConf->min,
2189 servoConf->max,
2190 servoConf->middle,
2191 servoConf->rate,
2192 servoConf->forwardFromChannel
2195 // print servo directions
2196 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2197 const char *format = "smix reverse %d %d r";
2198 const servoParam_t *servoConf = &servoParams[i];
2199 const servoParam_t *servoConfDefault = &defaultServoParams[i];
2200 if (defaultServoParams) {
2201 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
2202 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2203 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
2204 if (servoConfDefault->reversedSources & (1 << channel)) {
2205 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
2207 if (servoConf->reversedSources & (1 << channel)) {
2208 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
2211 } else {
2212 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2213 if (servoConf->reversedSources & (1 << channel)) {
2214 cliDumpPrintLinef(dumpMask, true, format, i , channel);
2221 static void cliServo(const char *cmdName, char *cmdline)
2223 const char *format = "servo %u %d %d %d %d %d";
2224 enum { SERVO_ARGUMENT_COUNT = 6 };
2225 int16_t arguments[SERVO_ARGUMENT_COUNT];
2227 servoParam_t *servo;
2229 int i;
2230 char *ptr;
2232 if (isEmpty(cmdline)) {
2233 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2234 } else {
2235 int validArgumentCount = 0;
2237 ptr = cmdline;
2239 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2241 // If command line doesn't fit the format, don't modify the config
2242 while (*ptr) {
2243 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2244 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2245 cliShowInvalidArgumentCountError(cmdName);
2246 return;
2249 arguments[validArgumentCount++] = atoi(ptr);
2251 do {
2252 ptr++;
2253 } while (*ptr >= '0' && *ptr <= '9');
2254 } else if (*ptr == ' ') {
2255 ptr++;
2256 } else {
2257 cliShowParseError(cmdName);
2258 return;
2262 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2264 i = arguments[INDEX];
2266 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2267 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2268 cliShowInvalidArgumentCountError(cmdName);
2269 return;
2272 servo = servoParamsMutable(i);
2274 if (
2275 arguments[MIN] < PWM_SERVO_MIN || arguments[MIN] > PWM_SERVO_MAX ||
2276 arguments[MAX] < PWM_SERVO_MIN || arguments[MAX] > PWM_SERVO_MAX ||
2277 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2278 arguments[MIN] > arguments[MAX] ||
2279 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2280 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2282 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2283 return;
2286 servo->min = arguments[MIN];
2287 servo->max = arguments[MAX];
2288 servo->middle = arguments[MIDDLE];
2289 servo->rate = arguments[RATE];
2290 servo->forwardFromChannel = arguments[FORWARD];
2292 cliDumpPrintLinef(0, false, format,
2294 servo->min,
2295 servo->max,
2296 servo->middle,
2297 servo->rate,
2298 servo->forwardFromChannel
2303 #endif
2305 #ifdef USE_SERVOS
2306 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2308 const char *format = "smix %d %d %d %d %d %d %d %d";
2309 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2310 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2311 const servoMixer_t customServoMixer = customServoMixers[i];
2312 if (customServoMixer.rate == 0) {
2313 break;
2316 bool equalsDefault = false;
2317 if (defaultCustomServoMixers) {
2318 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2319 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2321 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2322 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2324 customServoMixerDefault.targetChannel,
2325 customServoMixerDefault.inputSource,
2326 customServoMixerDefault.rate,
2327 customServoMixerDefault.speed,
2328 customServoMixerDefault.min,
2329 customServoMixerDefault.max,
2330 customServoMixerDefault.box
2333 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2335 customServoMixer.targetChannel,
2336 customServoMixer.inputSource,
2337 customServoMixer.rate,
2338 customServoMixer.speed,
2339 customServoMixer.min,
2340 customServoMixer.max,
2341 customServoMixer.box
2346 static void cliServoMix(const char *cmdName, char *cmdline)
2348 int args[8], check = 0;
2349 int len = strlen(cmdline);
2351 if (len == 0) {
2352 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2353 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2354 // erase custom mixer
2355 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2356 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2357 servoParamsMutable(i)->reversedSources = 0;
2359 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2360 const char *ptr = nextArg(cmdline);
2361 if (ptr) {
2362 len = strlen(ptr);
2363 for (uint32_t i = 0; ; i++) {
2364 if (mixerNames[i] == NULL) {
2365 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
2366 break;
2368 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2369 servoMixerLoadMix(i);
2370 cliPrintLinef("Loaded %s", mixerNames[i]);
2371 cliServoMix(cmdName, "");
2372 break;
2376 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2377 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2378 char *ptr = strchr(cmdline, ' ');
2380 if (ptr == NULL) {
2381 cliPrintf("s");
2382 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2383 cliPrintf("\ti%d", inputSource);
2384 cliPrintLinefeed();
2386 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2387 cliPrintf("%d", servoIndex);
2388 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2389 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2391 cliPrintLinefeed();
2393 return;
2396 char *saveptr;
2397 ptr = strtok_r(ptr, " ", &saveptr);
2398 while (ptr != NULL && check < ARGS_COUNT - 1) {
2399 args[check++] = atoi(ptr);
2400 ptr = strtok_r(NULL, " ", &saveptr);
2403 if (ptr == NULL || check != ARGS_COUNT - 1) {
2404 cliShowInvalidArgumentCountError(cmdName);
2405 return;
2408 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2409 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2410 && (*ptr == 'r' || *ptr == 'n')) {
2411 if (*ptr == 'r') {
2412 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2413 } else {
2414 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2416 } else {
2417 cliShowArgumentRangeError(cmdName, "servo", 0, MAX_SUPPORTED_SERVOS);
2418 return;
2421 cliServoMix(cmdName, "reverse");
2422 } else {
2423 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2424 char *saveptr;
2425 char *ptr = strtok_r(cmdline, " ", &saveptr);
2426 while (ptr != NULL && check < ARGS_COUNT) {
2427 args[check++] = atoi(ptr);
2428 ptr = strtok_r(NULL, " ", &saveptr);
2431 if (ptr != NULL || check != ARGS_COUNT) {
2432 cliShowInvalidArgumentCountError(cmdName);
2433 return;
2436 int32_t i = args[RULE];
2437 if (i >= 0 && i < MAX_SERVO_RULES &&
2438 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2439 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2440 args[RATE] >= -100 && args[RATE] <= 100 &&
2441 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2442 args[MIN] >= 0 && args[MIN] <= 100 &&
2443 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2444 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2445 customServoMixersMutable(i)->targetChannel = args[TARGET];
2446 customServoMixersMutable(i)->inputSource = args[INPUT];
2447 customServoMixersMutable(i)->rate = args[RATE];
2448 customServoMixersMutable(i)->speed = args[SPEED];
2449 customServoMixersMutable(i)->min = args[MIN];
2450 customServoMixersMutable(i)->max = args[MAX];
2451 customServoMixersMutable(i)->box = args[BOX];
2452 cliServoMix(cmdName, "");
2453 } else {
2454 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2458 #endif
2460 #ifdef USE_SDCARD
2462 static void cliWriteBytes(const uint8_t *buffer, int count)
2464 while (count > 0) {
2465 cliWrite(*buffer);
2466 buffer++;
2467 count--;
2471 static void cliSdInfo(const char *cmdName, char *cmdline)
2473 UNUSED(cmdName);
2474 UNUSED(cmdline);
2476 cliPrint("SD card: ");
2478 if (sdcardConfig()->mode == SDCARD_MODE_NONE) {
2479 cliPrintLine("Not configured");
2481 return;
2484 if (!sdcard_isInserted()) {
2485 cliPrintLine("None inserted");
2486 return;
2489 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2490 cliPrintLine("Startup failed");
2491 return;
2494 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2496 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2497 metadata->manufacturerID,
2498 metadata->numBlocks / 2, /* One block is half a kB */
2499 metadata->productionMonth,
2500 metadata->productionYear,
2501 metadata->productRevisionMajor,
2502 metadata->productRevisionMinor
2505 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2507 cliPrint("'\r\n" "Filesystem: ");
2509 switch (afatfs_getFilesystemState()) {
2510 case AFATFS_FILESYSTEM_STATE_READY:
2511 cliPrint("Ready");
2512 break;
2513 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2514 cliPrint("Initializing");
2515 break;
2516 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2517 case AFATFS_FILESYSTEM_STATE_FATAL:
2518 cliPrint("Fatal");
2520 switch (afatfs_getLastError()) {
2521 case AFATFS_ERROR_BAD_MBR:
2522 cliPrint(" - no FAT MBR partitions");
2523 break;
2524 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2525 cliPrint(" - bad FAT header");
2526 break;
2527 case AFATFS_ERROR_GENERIC:
2528 case AFATFS_ERROR_NONE:
2529 ; // Nothing more detailed to print
2530 break;
2532 break;
2534 cliPrintLinefeed();
2537 #endif
2539 #ifdef USE_FLASH_CHIP
2540 static void cliFlashInfo(const char *cmdName, char *cmdline)
2542 UNUSED(cmdName);
2543 UNUSED(cmdline);
2545 const flashGeometry_t *layout = flashGetGeometry();
2547 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u JEDEC ID=0x%08x",
2548 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, layout->jedecId);
2550 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2551 const flashPartition_t *partition;
2552 if (index == 0) {
2553 cliPrintLine("Partitions:");
2555 partition = flashPartitionFindByIndex(index);
2556 if (!partition) {
2557 break;
2559 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2561 #ifdef USE_FLASHFS
2562 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2564 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2565 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2566 flashfsGetOffset()
2568 #endif
2570 #endif // USE_FLASH_CHIP
2572 #if defined(USE_FLASHFS) && defined(USE_FLASH_CHIP)
2573 static void cliFlashErase(const char *cmdName, char *cmdline)
2575 UNUSED(cmdName);
2576 UNUSED(cmdline);
2578 if (!flashfsIsSupported()) {
2579 return;
2582 #ifndef MINIMAL_CLI
2583 uint32_t i = 0;
2584 cliPrintLine("Erasing, please wait ... ");
2585 #else
2586 cliPrintLine("Erasing,");
2587 #endif
2589 cliWriterFlush();
2590 flashfsEraseCompletely();
2592 while (!flashfsIsReady()) {
2593 #ifndef MINIMAL_CLI
2594 cliPrintf(".");
2595 if (i++ > 120) {
2596 i=0;
2597 cliPrintLinefeed();
2600 cliWriterFlush();
2601 #endif
2602 delay(100);
2604 beeper(BEEPER_BLACKBOX_ERASE);
2605 cliPrintLinefeed();
2606 cliPrintLine("Done.");
2609 #ifdef USE_FLASH_TOOLS
2610 static void cliFlashVerify(const char *cmdName, char *cmdline)
2612 UNUSED(cmdline);
2614 cliPrintLine("Verifying");
2615 if (flashfsVerifyEntireFlash()) {
2616 cliPrintLine("Success");
2617 } else {
2618 cliPrintErrorLinef(cmdName, "Failed");
2622 static void cliFlashWrite(const char *cmdName, char *cmdline)
2624 const uint32_t address = atoi(cmdline);
2625 const char *text = strchr(cmdline, ' ');
2627 if (!text) {
2628 cliShowInvalidArgumentCountError(cmdName);
2629 } else {
2630 flashfsSeekAbs(address);
2631 flashfsWrite((uint8_t*)text, strlen(text), true);
2632 flashfsFlushSync();
2634 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2638 static void cliFlashRead(const char *cmdName, char *cmdline)
2640 uint32_t address = atoi(cmdline);
2642 const char *nextArg = strchr(cmdline, ' ');
2644 if (!nextArg) {
2645 cliShowInvalidArgumentCountError(cmdName);
2646 } else {
2647 uint32_t length = atoi(nextArg);
2649 cliPrintLinef("Reading %u bytes at %u:", length, address);
2651 uint8_t buffer[32];
2652 while (length > 0) {
2653 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2655 for (int i = 0; i < bytesRead; i++) {
2656 cliWrite(buffer[i]);
2659 length -= bytesRead;
2660 address += bytesRead;
2662 if (bytesRead == 0) {
2663 //Assume we reached the end of the volume or something fatal happened
2664 break;
2667 cliPrintLinefeed();
2670 #endif // USE_FLASH_TOOLS
2671 #endif // USE_FLASHFS
2673 #ifdef USE_VTX_CONTROL
2674 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2676 // print out vtx channel settings
2677 const char *format = "vtx %u %u %u %u %u %u %u";
2678 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2679 bool equalsDefault = false;
2680 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2681 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2682 if (vtxConfigDefault) {
2683 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2684 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2685 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2686 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2688 cacDefault->auxChannelIndex,
2689 cacDefault->band,
2690 cacDefault->channel,
2691 cacDefault->power,
2692 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2693 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2696 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2698 cac->auxChannelIndex,
2699 cac->band,
2700 cac->channel,
2701 cac->power,
2702 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2703 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2708 static void cliVtx(const char *cmdName, char *cmdline)
2710 const char *format = "vtx %u %u %u %u %u %u %u";
2711 const char *ptr;
2713 if (isEmpty(cmdline)) {
2714 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2715 } else {
2716 #ifdef USE_VTX_TABLE
2717 const uint8_t maxBandIndex = vtxTableConfig()->bands;
2718 const uint8_t maxChannelIndex = vtxTableConfig()->channels;
2719 const uint8_t maxPowerIndex = vtxTableConfig()->powerLevels;
2720 #else
2721 const uint8_t maxBandIndex = VTX_TABLE_MAX_BANDS;
2722 const uint8_t maxChannelIndex = VTX_TABLE_MAX_CHANNELS;
2723 const uint8_t maxPowerIndex = VTX_TABLE_MAX_POWER_LEVELS;
2724 #endif
2725 ptr = cmdline;
2726 int i = atoi(ptr++);
2727 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2728 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2729 uint8_t validArgumentCount = 0;
2730 ptr = nextArg(ptr);
2731 if (ptr) {
2732 int val = atoi(ptr);
2733 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2734 cac->auxChannelIndex = val;
2735 validArgumentCount++;
2738 ptr = nextArg(ptr);
2739 if (ptr) {
2740 int val = atoi(ptr);
2741 if (val >= 0 && val <= maxBandIndex) {
2742 cac->band = val;
2743 validArgumentCount++;
2746 ptr = nextArg(ptr);
2747 if (ptr) {
2748 int val = atoi(ptr);
2749 if (val >= 0 && val <= maxChannelIndex) {
2750 cac->channel = val;
2751 validArgumentCount++;
2754 ptr = nextArg(ptr);
2755 if (ptr) {
2756 int val = atoi(ptr);
2757 if (val >= 0 && val <= maxPowerIndex) {
2758 cac->power= val;
2759 validArgumentCount++;
2762 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2764 if (validArgumentCount != 6) {
2765 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2766 cliShowInvalidArgumentCountError(cmdName);
2767 } else {
2768 cliDumpPrintLinef(0, false, format,
2770 cac->auxChannelIndex,
2771 cac->band,
2772 cac->channel,
2773 cac->power,
2774 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2775 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2778 } else {
2779 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2784 #endif // VTX_CONTROL
2786 #ifdef USE_VTX_TABLE
2788 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2790 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2791 char freqtmp[5 + 1];
2792 freqbuf[0] = 0;
2793 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2794 for (int channel = 0; channel < channels; channel++) {
2795 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2796 strcat(freqbuf, freqtmp);
2798 return freqbuf;
2801 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2803 char *fmt = "vtxtable band %d %s %c%s";
2804 bool equalsDefault = false;
2806 if (defaultConfig) {
2807 equalsDefault = true;
2808 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2809 equalsDefault = false;
2811 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2812 equalsDefault = false;
2814 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2815 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2816 equalsDefault = false;
2819 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2820 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2821 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2824 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2825 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2826 return headingStr;
2829 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2831 // (max 4 digit + 1 space) per level
2832 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2833 char pwrtmp[5 + 1];
2834 pwrbuf[0] = 0;
2835 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2836 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2837 strcat(pwrbuf, pwrtmp);
2839 return pwrbuf;
2842 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2844 char *fmt = "vtxtable powervalues%s";
2845 bool equalsDefault = false;
2846 if (defaultConfig) {
2847 equalsDefault = true;
2848 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2849 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2850 equalsDefault = false;
2853 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2854 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2855 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2858 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2859 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2860 return headingStr;
2863 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2865 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2866 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2867 pwrbuf[0] = 0;
2868 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2869 strcat(pwrbuf, " ");
2870 strcpy(pwrtmp, labels[pwrindex]);
2871 // trim trailing space
2872 char *sp;
2873 while ((sp = strchr(pwrtmp, ' '))) {
2874 *sp = 0;
2876 strcat(pwrbuf, pwrtmp);
2878 return pwrbuf;
2881 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2883 char *fmt = "vtxtable powerlabels%s";
2884 bool equalsDefault = false;
2885 if (defaultConfig) {
2886 equalsDefault = true;
2887 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2888 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2889 equalsDefault = false;
2892 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2893 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2894 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2897 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2898 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2899 return headingStr;
2902 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2904 bool equalsDefault;
2905 char *fmt;
2907 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2909 // bands
2910 equalsDefault = false;
2911 fmt = "vtxtable bands %d";
2912 if (defaultConfig) {
2913 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2914 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2915 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2917 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2919 // channels
2920 equalsDefault = false;
2921 fmt = "vtxtable channels %d";
2922 if (defaultConfig) {
2923 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2924 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2925 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2927 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2929 // band
2931 for (int band = 0; band < currentConfig->bands; band++) {
2932 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2935 // powerlevels
2937 equalsDefault = false;
2938 fmt = "vtxtable powerlevels %d";
2939 if (defaultConfig) {
2940 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2941 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2942 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2944 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2946 // powervalues
2948 // powerlabels
2949 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2950 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2953 static void cliVtxTable(const char *cmdName, char *cmdline)
2955 char *tok;
2956 char *saveptr;
2958 // Band number or nothing
2959 tok = strtok_r(cmdline, " ", &saveptr);
2961 if (!tok) {
2962 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2963 return;
2966 if (strcasecmp(tok, "bands") == 0) {
2967 tok = strtok_r(NULL, " ", &saveptr);
2968 int bands = atoi(tok);
2969 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2970 cliShowArgumentRangeError(cmdName, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS);
2971 return;
2973 if (bands < vtxTableConfigMutable()->bands) {
2974 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2975 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2978 vtxTableConfigMutable()->bands = bands;
2980 } else if (strcasecmp(tok, "channels") == 0) {
2981 tok = strtok_r(NULL, " ", &saveptr);
2983 int channels = atoi(tok);
2984 if (channels < 0 || channels > VTX_TABLE_MAX_CHANNELS) {
2985 cliShowArgumentRangeError(cmdName, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS);
2986 return;
2988 if (channels < vtxTableConfigMutable()->channels) {
2989 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2990 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2993 vtxTableConfigMutable()->channels = channels;
2995 } else if (strcasecmp(tok, "powerlevels") == 0) {
2996 // Number of power levels
2997 tok = strtok_r(NULL, " ", &saveptr);
2998 if (tok) {
2999 int levels = atoi(tok);
3000 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
3001 cliShowArgumentRangeError(cmdName, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS);
3002 } else {
3003 if (levels < vtxTableConfigMutable()->powerLevels) {
3004 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
3005 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
3007 vtxTableConfigMutable()->powerLevels = levels;
3009 } else {
3010 // XXX Show current level count?
3012 return;
3014 } else if (strcasecmp(tok, "powervalues") == 0) {
3015 // Power values
3016 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
3017 int count;
3018 int levels = vtxTableConfigMutable()->powerLevels;
3020 memset(power, 0, sizeof(power));
3022 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
3023 int value = atoi(tok);
3024 power[count] = value;
3027 // Check remaining tokens
3029 if (count < levels) {
3030 cliPrintErrorLinef(cmdName, "NOT ENOUGH VALUES (EXPECTED %d)", levels);
3031 return;
3032 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3033 cliPrintErrorLinef(cmdName, "TOO MANY VALUES (EXPECTED %d)", levels);
3034 return;
3037 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
3038 vtxTableConfigMutable()->powerValues[i] = power[i];
3041 } else if (strcasecmp(tok, "powerlabels") == 0) {
3042 // Power labels
3043 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
3044 int levels = vtxTableConfigMutable()->powerLevels;
3045 int count;
3046 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
3047 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
3048 for (unsigned i = 0; i < strlen(label[count]); i++) {
3049 label[count][i] = toupper(label[count][i]);
3053 // Check remaining tokens
3055 if (count < levels) {
3056 cliPrintErrorLinef(cmdName, "NOT ENOUGH LABELS (EXPECTED %d)", levels);
3057 return;
3058 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3059 cliPrintErrorLinef(cmdName, "TOO MANY LABELS (EXPECTED %d)", levels);
3060 return;
3063 for (int i = 0; i < count; i++) {
3064 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
3066 } else if (strcasecmp(tok, "band") == 0) {
3068 int bands = vtxTableConfigMutable()->bands;
3070 tok = strtok_r(NULL, " ", &saveptr);
3071 if (!tok) {
3072 return;
3075 int band = atoi(tok);
3076 --band;
3078 if (band < 0 || band >= bands) {
3079 cliShowArgumentRangeError(cmdName, "BAND NUMBER", 1, bands);
3080 return;
3083 // Band name
3084 tok = strtok_r(NULL, " ", &saveptr);
3086 if (!tok) {
3087 return;
3090 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
3091 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
3092 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
3093 for (unsigned i = 0; i < strlen(bandname); i++) {
3094 bandname[i] = toupper(bandname[i]);
3097 // Band letter
3098 tok = strtok_r(NULL, " ", &saveptr);
3100 if (!tok) {
3101 return;
3104 char bandletter = toupper(tok[0]);
3106 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
3107 int channel = 0;
3108 int channels = vtxTableConfigMutable()->channels;
3109 bool isFactory = false;
3111 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
3112 if (channel == 0 && !isdigit(tok[0])) {
3113 channel -= 1;
3114 if (strcasecmp(tok, "FACTORY") == 0) {
3115 isFactory = true;
3116 } else if (strcasecmp(tok, "CUSTOM") == 0) {
3117 isFactory = false;
3118 } else {
3119 cliPrintErrorLinef(cmdName, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
3120 return;
3123 int freq = atoi(tok);
3124 if (freq < 0) {
3125 cliPrintErrorLinef(cmdName, "INVALID FREQUENCY %s", tok);
3126 return;
3128 bandfreq[channel] = freq;
3131 if (channel < channels) {
3132 cliPrintErrorLinef(cmdName, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
3133 return;
3134 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3135 cliPrintErrorLinef(cmdName, "TOO MANY FREQUENCIES (EXPECTED %d)", channels);
3136 return;
3139 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
3140 vtxTableConfigMutable()->bandLetters[band] = bandletter;
3142 for (int i = 0; i < channel; i++) {
3143 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
3145 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
3146 } else {
3147 // Bad subcommand
3148 cliPrintErrorLinef(cmdName, "INVALID SUBCOMMAND %s", tok);
3152 static void cliVtxInfo(const char *cmdName, char *cmdline)
3154 UNUSED(cmdline);
3156 // Display the available power levels
3157 uint16_t levels[VTX_TABLE_MAX_POWER_LEVELS];
3158 uint16_t powers[VTX_TABLE_MAX_POWER_LEVELS];
3159 vtxDevice_t *vtxDevice = vtxCommonDevice();
3160 if (vtxDevice) {
3161 uint8_t level_count = vtxCommonGetVTXPowerLevels(vtxDevice, levels, powers);
3163 if (level_count) {
3164 for (int i = 0; i < level_count; i++) {
3165 cliPrintLinef("level %d dBm, power %d mW", levels[i], powers[i]);
3167 } else {
3168 cliPrintErrorLinef(cmdName, "NO POWER VALUES DEFINED");
3170 } else {
3171 cliPrintErrorLinef(cmdName, "NO VTX");
3174 #endif // USE_VTX_TABLE
3176 #if defined(USE_SIMPLIFIED_TUNING)
3177 static void applySimplifiedTuningAllProfiles(void)
3179 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3180 applySimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3184 static void cliSimplifiedTuning(const char *cmdName, char *cmdline)
3186 if (strcasecmp(cmdline, "apply") == 0) {
3187 applySimplifiedTuningAllProfiles();
3189 cliPrintLine("Applied simplified tuning.");
3190 } else if (strcasecmp(cmdline, "disable") == 0) {
3191 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3192 disableSimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3195 cliPrintLine("Disabled simplified tuning.");
3196 } else {
3197 cliShowParseError(cmdName);
3200 #endif
3202 static void printCraftName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
3204 const bool equalsDefault = strlen(pilotConfig->craftName) == 0;
3205 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->craftName);
3208 #if defined(USE_BOARD_INFO)
3210 static void printBoardName(dumpFlags_t dumpMask)
3212 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
3213 cliPrintLinef("board_name %s", getBoardName());
3217 static void cliBoardName(const char *cmdName, char *cmdline)
3219 const unsigned int len = strlen(cmdline);
3220 const char *boardName = getBoardName();
3221 if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
3222 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "BOARD_NAME", boardName);
3223 } else {
3224 if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
3225 boardInformationUpdated = true;
3227 cliPrintHashLine("Set board_name.");
3229 printBoardName(DUMP_ALL);
3233 static void printManufacturerId(dumpFlags_t dumpMask)
3235 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
3236 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3240 static void cliManufacturerId(const char *cmdName, char *cmdline)
3242 const unsigned int len = strlen(cmdline);
3243 const char *manufacturerId = getManufacturerId();
3244 if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
3245 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
3246 } else {
3247 if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
3248 boardInformationUpdated = true;
3250 cliPrintHashLine("Set manufacturer_id.");
3252 printManufacturerId(DUMP_ALL);
3256 #if defined(USE_SIGNATURE)
3257 static void writeSignature(char *signatureStr, uint8_t *signature)
3259 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3260 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
3264 static void cliSignature(const char *cmdName, char *cmdline)
3266 const int len = strlen(cmdline);
3268 uint8_t signature[SIGNATURE_LENGTH] = {0};
3269 if (len > 0) {
3270 if (len != 2 * SIGNATURE_LENGTH) {
3271 cliPrintErrorLinef(cmdName, "INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
3273 return;
3276 #define BLOCK_SIZE 2
3277 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3278 char temp[BLOCK_SIZE + 1];
3279 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3280 temp[BLOCK_SIZE] = '\0';
3281 char *end;
3282 unsigned result = strtoul(temp, &end, 16);
3283 if (end == &temp[BLOCK_SIZE]) {
3284 signature[i] = result;
3285 } else {
3286 cliPrintErrorLinef(cmdName, "INVALID CHARACTER FOUND: %c", end[0]);
3288 return;
3291 #undef BLOCK_SIZE
3294 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3295 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3296 writeSignature(signatureStr, getSignature());
3297 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "SIGNATURE", signatureStr);
3298 } else {
3299 if (len > 0 && !configIsInCopy && setSignature(signature)) {
3300 signatureUpdated = true;
3302 writeSignature(signatureStr, getSignature());
3304 cliPrintHashLine("Set signature.");
3305 } else if (signatureUpdated || signatureIsSet()) {
3306 writeSignature(signatureStr, getSignature());
3309 cliPrintLinef("signature %s", signatureStr);
3312 #endif
3314 #undef ERROR_MESSAGE
3316 #endif // USE_BOARD_INFO
3318 static void cliMcuId(const char *cmdName, char *cmdline)
3320 UNUSED(cmdName);
3321 UNUSED(cmdline);
3323 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3326 static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
3328 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3329 for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) { // disabled features first
3330 if (featureNames[i]) { //Skip unused
3331 const char *format = "feature -%s";
3332 const bool equalsDefault = (~defaultMask | mask) & (1U << i);
3333 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3334 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1U << i), format, featureNames[i]);
3335 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3338 for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) { // enabled features
3339 if (featureNames[i]) { //Skip unused
3340 const char *format = "feature %s";
3341 if (defaultMask & (1U << i)) {
3342 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1U << i), format, featureNames[i]);
3344 if (mask & (1U << i)) {
3345 const bool equalsDefault = (defaultMask | ~mask) & (1U << i);
3346 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3347 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3353 static void printFeatureList(const char* header, uint32_t mask, const char* delimiter, bool lineFeed)
3355 if (header) {
3356 cliPrint(header);
3358 for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) {
3359 if (featureNames[i] && (mask & (1U << i))) {
3360 cliPrintf("%s%s", i ? delimiter : "", featureNames[i]);
3363 if (lineFeed) {
3364 cliPrintLinefeed();
3368 static void cliFeature(const char *cmdName, char *cmdline)
3370 uint32_t len = strlen(cmdline);
3371 const uint32_t mask = featureConfig()->enabledFeatures;
3372 if (len == 0 // `feature`
3373 || strncasecmp(cmdline, "list", len) == 0) { // old `feature list` invocation
3374 printFeatureList("Enabled: ", mask, " ", true);
3375 printFeatureList("Available: ", ~mask & featuresSupportedByBuild, " ", true);
3376 printFeatureList("Unavailable: ", ~featuresSupportedByBuild, " ", true);
3377 } else {
3378 bool remove = false;
3379 if (cmdline[0] == '-') {
3380 // remove feature
3381 remove = true;
3382 cmdline++; // skip over -
3383 len--;
3386 unsigned found = 0;
3387 int featureIdx = -1;
3388 for (unsigned i = 0; !found && i < ARRAYLEN(featureNames); i++) {
3389 if (featureNames[i] && strncasecmp(cmdline, featureNames[i], len) == 0) {
3390 found++;
3391 featureIdx = i;
3394 if (found == 1) {
3395 uint32_t feature = 1U << featureIdx;
3396 const char *verb;
3397 if ((feature & featuresSupportedByBuild) == 0) {
3398 verb = "Unavailable";
3399 } else if (remove) {
3400 featureConfigClear(feature);
3401 verb = (mask & feature) ? "Disabled" : "AlreadyDisabled";
3402 } else {
3403 featureConfigSet(feature);
3404 verb = (mask & feature) ? "AlreadyEnabled" : "Enabled";
3406 cliPrintLinef("%s %s", verb, featureNames[featureIdx]);
3407 } else if (found > 1) {
3408 cliPrintErrorLinef(cmdName, "Multiple features match %s", cmdline);
3409 } else /* found <= 0 */ {
3410 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
3415 #if defined(USE_BEEPER)
3416 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3418 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3419 const uint8_t beeperCount = beeperTableEntryCount();
3420 for (int32_t i = 0; i < beeperCount - 1; i++) {
3421 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3422 if (beeperModeMask & allowedFlags) {
3423 const char *formatOff = "%s -%s";
3424 const char *formatOn = "%s %s";
3425 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3426 cliDefaultPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3427 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3428 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3433 static void processBeeperCommand(const char *cmdName, char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3435 uint32_t len = strlen(cmdline);
3436 uint8_t beeperCount = beeperTableEntryCount();
3438 if (len == 0) {
3439 cliPrintf("Disabled:");
3440 for (int32_t i = 0; ; i++) {
3441 if (i == beeperCount - 1) {
3442 if (*offFlags == 0)
3443 cliPrint(" none");
3444 break;
3447 if (beeperModeMaskForTableIndex(i) & *offFlags)
3448 cliPrintf(" %s", beeperNameForTableIndex(i));
3450 cliPrintLinefeed();
3451 } else if (strncasecmp(cmdline, "list", len) == 0) {
3452 cliPrint("Available:");
3453 for (uint32_t i = 0; i < beeperCount; i++) {
3454 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3455 cliPrintf(" %s", beeperNameForTableIndex(i));
3458 cliPrintLinefeed();
3459 } else {
3460 bool remove = false;
3461 if (cmdline[0] == '-') {
3462 remove = true; // this is for beeper OFF condition
3463 cmdline++;
3464 len--;
3467 for (uint32_t i = 0; ; i++) {
3468 if (i == beeperCount) {
3469 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
3470 break;
3472 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3473 if (remove) { // beeper off
3474 if (i == BEEPER_ALL - 1) {
3475 *offFlags = allowedFlags;
3476 } else {
3477 *offFlags |= beeperModeMaskForTableIndex(i);
3479 cliPrint("Disabled");
3481 else { // beeper on
3482 if (i == BEEPER_ALL - 1) {
3483 *offFlags = 0;
3484 } else {
3485 *offFlags &= ~beeperModeMaskForTableIndex(i);
3487 cliPrint("Enabled");
3489 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3490 break;
3496 #if defined(USE_DSHOT)
3497 static void cliBeacon(const char *cmdName, char *cmdline)
3499 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3501 #endif
3503 static void cliBeeper(const char *cmdName, char *cmdline)
3505 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3507 #endif
3509 #if defined(USE_RX_BIND)
3510 static void cliRxBind(const char *cmdName, char *cmdline)
3512 UNUSED(cmdline);
3513 if (!startRxBind()) {
3514 cliPrintErrorLinef(cmdName, "Not supported.");
3515 } else {
3516 cliPrintLinef("Binding...");
3519 #endif
3521 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3523 bool equalsDefault = true;
3524 char buf[16];
3525 char bufDefault[16];
3526 uint32_t i;
3528 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3529 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3530 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3531 if (defaultRxConfig) {
3532 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3533 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3536 buf[i] = '\0';
3538 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3539 const char *formatMap = "map %s";
3540 if (defaultRxConfig) {
3541 bufDefault[i] = '\0';
3542 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3544 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3547 static void cliMap(const char *cmdName, char *cmdline)
3549 uint32_t i;
3550 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3552 uint32_t len = strlen(cmdline);
3553 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3555 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3556 buf[i] = toupper((unsigned char)cmdline[i]);
3558 buf[i] = '\0';
3560 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3561 buf[i] = toupper((unsigned char)cmdline[i]);
3563 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3564 continue;
3566 cliShowParseError(cmdName);
3567 return;
3569 parseRcChannels(buf, rxConfigMutable());
3570 } else if (len > 0) {
3571 cliShowInvalidArgumentCountError(cmdName);
3572 return;
3575 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3576 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3579 buf[i] = '\0';
3580 cliPrintLinef("map %s", buf);
3583 static char *skipSpace(char *buffer)
3585 while (*(buffer) == ' ') {
3586 buffer++;
3589 return buffer;
3592 static char *checkCommand(char *cmdline, const char *command)
3594 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3595 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3596 return skipSpace(cmdline + strlen(command) + 1);
3597 } else {
3598 return 0;
3602 static void cliRebootEx(rebootTarget_e rebootTarget)
3604 cliPrint("\r\nRebooting");
3605 cliWriterFlush();
3606 waitForSerialPortToFinishTransmitting(cliPort);
3607 motorShutdown();
3609 switch (rebootTarget) {
3610 case REBOOT_TARGET_BOOTLOADER_ROM:
3611 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3613 break;
3614 #if defined(USE_FLASH_BOOT_LOADER)
3615 case REBOOT_TARGET_BOOTLOADER_FLASH:
3616 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3618 break;
3619 #endif
3620 case REBOOT_TARGET_FIRMWARE:
3621 default:
3622 systemReset();
3624 break;
3628 static void cliReboot(void)
3630 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3633 static void cliBootloader(const char *cmdName, char *cmdline)
3635 rebootTarget_e rebootTarget;
3636 if (
3637 #if !defined(USE_FLASH_BOOT_LOADER)
3638 isEmpty(cmdline) ||
3639 #endif
3640 strncasecmp(cmdline, "rom", 3) == 0) {
3641 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3643 cliPrintHashLine("restarting in ROM bootloader mode");
3644 #if defined(USE_FLASH_BOOT_LOADER)
3645 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3646 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3648 cliPrintHashLine("restarting in flash bootloader mode");
3649 #endif
3650 } else {
3651 cliPrintErrorLinef(cmdName, "Invalid option");
3653 return;
3656 cliRebootEx(rebootTarget);
3659 static void cliExitCmd(const char *cmdName, char *cmdline)
3661 UNUSED(cmdName);
3663 const bool reboot = strcasecmp(cmdline, "noreboot") != 0;
3664 if (reboot) {
3665 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3666 } else {
3667 cliPrintHashLine("leaving CLI mode, no reboot");
3669 cliExit(reboot);
3672 #ifdef USE_GPS
3673 static void cliGpsPassthrough(const char *cmdName, char *cmdline)
3675 UNUSED(cmdName);
3676 UNUSED(cmdline);
3678 if (!gpsPassthrough(cliPort)) {
3679 cliPrintErrorLinef(cmdName, "GPS forwarding failed");
3682 #endif
3684 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3685 static void cliPrintGyroRegisters(uint8_t whichSensor)
3687 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3688 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3689 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3692 static void cliDumpGyroRegisters(const char *cmdName, char *cmdline)
3694 UNUSED(cmdName);
3695 UNUSED(cmdline);
3697 #ifdef USE_MULTI_GYRO
3698 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3699 cliPrintLinef("\r\n# Gyro 1");
3700 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3702 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3703 cliPrintLinef("\r\n# Gyro 2");
3704 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3706 #else
3707 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3708 #endif
3710 #endif
3712 static int parseOutputIndex(const char *cmdName, char *pch, bool allowAllEscs)
3714 int outputIndex = atoi(pch);
3715 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3716 cliPrintLinef("Using output %d.", outputIndex);
3717 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3718 cliPrintLinef("Using all outputs.");
3719 } else {
3720 cliPrintErrorLinef(cmdName, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3722 return -1;
3725 return outputIndex;
3728 #if defined(USE_DSHOT)
3729 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3731 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3732 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3733 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3735 enum {
3736 ESC_INFO_KISS_V1,
3737 ESC_INFO_KISS_V2,
3738 ESC_INFO_BLHELI32
3741 #define ESC_INFO_VERSION_POSITION 12
3743 static void printEscInfo(const char *cmdName, const uint8_t *escInfoBuffer, uint8_t bytesRead)
3745 bool escInfoReceived = false;
3746 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3747 uint8_t escInfoVersion;
3748 uint8_t frameLength;
3749 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3750 escInfoVersion = ESC_INFO_BLHELI32;
3751 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3752 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3753 escInfoVersion = ESC_INFO_KISS_V2;
3754 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3755 } else {
3756 escInfoVersion = ESC_INFO_KISS_V1;
3757 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3760 if (bytesRead == frameLength) {
3761 escInfoReceived = true;
3763 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3764 uint8_t firmwareVersion = 0;
3765 uint8_t firmwareSubVersion = 0;
3766 uint8_t escType = 0;
3767 switch (escInfoVersion) {
3768 case ESC_INFO_KISS_V1:
3769 firmwareVersion = escInfoBuffer[12];
3770 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3771 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3773 break;
3774 case ESC_INFO_KISS_V2:
3775 firmwareVersion = escInfoBuffer[13];
3776 firmwareSubVersion = escInfoBuffer[14];
3777 escType = escInfoBuffer[15];
3779 break;
3780 case ESC_INFO_BLHELI32:
3781 firmwareVersion = escInfoBuffer[13];
3782 firmwareSubVersion = escInfoBuffer[14];
3783 escType = escInfoBuffer[15];
3785 break;
3788 cliPrint("ESC Type: ");
3789 switch (escInfoVersion) {
3790 case ESC_INFO_KISS_V1:
3791 case ESC_INFO_KISS_V2:
3792 switch (escType) {
3793 case 1:
3794 cliPrintLine("KISS8A");
3796 break;
3797 case 2:
3798 cliPrintLine("KISS16A");
3800 break;
3801 case 3:
3802 cliPrintLine("KISS24A");
3804 break;
3805 case 5:
3806 cliPrintLine("KISS Ultralite");
3808 break;
3809 default:
3810 cliPrintLine("unknown");
3812 break;
3815 break;
3816 case ESC_INFO_BLHELI32:
3818 char *escType = (char *)(escInfoBuffer + 31);
3819 escType[32] = 0;
3820 cliPrintLine(escType);
3823 break;
3826 cliPrint("MCU Serial No: 0x");
3827 for (int i = 0; i < 12; i++) {
3828 if (i && (i % 3 == 0)) {
3829 cliPrint("-");
3831 cliPrintf("%02x", escInfoBuffer[i]);
3833 cliPrintLinefeed();
3835 switch (escInfoVersion) {
3836 case ESC_INFO_KISS_V1:
3837 case ESC_INFO_KISS_V2:
3838 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3840 break;
3841 case ESC_INFO_BLHELI32:
3842 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3844 break;
3846 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3847 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3848 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3849 if (escInfoVersion == ESC_INFO_BLHELI32) {
3850 uint8_t setting = escInfoBuffer[18];
3851 cliPrint("Low voltage Limit: ");
3852 switch (setting) {
3853 case 0:
3854 cliPrintLine("off");
3856 break;
3857 case 255:
3858 cliPrintLine("unsupported");
3860 break;
3861 default:
3862 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3864 break;
3867 setting = escInfoBuffer[19];
3868 cliPrint("Current Limit: ");
3869 switch (setting) {
3870 case 0:
3871 cliPrintLine("off");
3873 break;
3874 case 255:
3875 cliPrintLine("unsupported");
3877 break;
3878 default:
3879 cliPrintLinef("%d", setting);
3881 break;
3884 for (int i = 0; i < 4; i++) {
3885 setting = escInfoBuffer[i + 20];
3886 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3890 } else {
3891 cliPrintErrorLinef(cmdName, "CHECKSUM ERROR.");
3896 if (!escInfoReceived) {
3897 cliPrintLine("No Info.");
3901 static void executeEscInfoCommand(const char *cmdName, uint8_t escIndex)
3903 cliPrintLinef("Info for ESC %d:", escIndex);
3905 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3907 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3909 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, DSHOT_CMD_TYPE_BLOCKING);
3911 delay(10);
3913 printEscInfo(cmdName, escInfoBuffer, getNumberEscBytesRead());
3915 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3917 static void cliDshotProg(const char *cmdName, char *cmdline)
3919 if (isEmpty(cmdline) || !isMotorProtocolDshot()) {
3920 cliShowParseError(cmdName);
3922 return;
3925 char *saveptr;
3926 char *pch = strtok_r(cmdline, " ", &saveptr);
3927 int pos = 0;
3928 int escIndex = 0;
3929 bool firstCommand = true;
3930 while (pch != NULL) {
3931 switch (pos) {
3932 case 0:
3933 escIndex = parseOutputIndex(cmdName, pch, true);
3934 if (escIndex == -1) {
3935 return;
3938 break;
3939 default:
3941 int command = atoi(pch);
3942 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3943 if (firstCommand) {
3944 // pwmDisableMotors();
3945 motorDisable();
3947 firstCommand = false;
3950 if (command != DSHOT_CMD_ESC_INFO) {
3951 dshotCommandWrite(escIndex, getMotorCount(), command, DSHOT_CMD_TYPE_BLOCKING);
3952 } else {
3953 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3954 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3955 if (escIndex != ALL_MOTORS) {
3956 executeEscInfoCommand(cmdName, escIndex);
3957 } else {
3958 for (uint8_t i = 0; i < getMotorCount(); i++) {
3959 executeEscInfoCommand(cmdName, i);
3962 } else
3963 #endif
3965 cliPrintLine("Not supported.");
3969 cliPrintLinef("Command Sent: %d", command);
3971 } else {
3972 cliPrintErrorLinef(cmdName, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3976 break;
3979 pos++;
3980 pch = strtok_r(NULL, " ", &saveptr);
3983 motorEnable();
3985 #endif // USE_DSHOT
3987 #ifdef USE_ESCSERIAL
3988 static void cliEscPassthrough(const char *cmdName, char *cmdline)
3990 if (isEmpty(cmdline)) {
3991 cliShowInvalidArgumentCountError(cmdName);
3993 return;
3996 char *saveptr;
3997 char *pch = strtok_r(cmdline, " ", &saveptr);
3998 int pos = 0;
3999 uint8_t mode = 0;
4000 int escIndex = 0;
4001 while (pch != NULL) {
4002 switch (pos) {
4003 case 0:
4004 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
4005 mode = PROTOCOL_SIMONK;
4006 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
4007 mode = PROTOCOL_BLHELI;
4008 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
4009 mode = PROTOCOL_KISS;
4010 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
4011 mode = PROTOCOL_CASTLE;
4012 } else {
4013 cliShowParseError(cmdName);
4015 return;
4017 break;
4018 case 1:
4019 escIndex = parseOutputIndex(cmdName, pch, mode == PROTOCOL_KISS);
4020 if (escIndex == -1) {
4021 return;
4024 break;
4025 default:
4026 cliShowInvalidArgumentCountError(cmdName);
4028 return;
4030 break;
4033 pos++;
4034 pch = strtok_r(NULL, " ", &saveptr);
4037 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
4038 cliPrintErrorLinef(cmdName, "Error starting ESC connection");
4041 #endif
4043 #ifndef USE_QUAD_MIXER_ONLY
4044 static void cliMixer(const char *cmdName, char *cmdline)
4046 int len;
4048 len = strlen(cmdline);
4050 if (len == 0) {
4051 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
4052 return;
4053 } else if (strncasecmp(cmdline, "list", len) == 0) {
4054 cliPrint("Available:");
4055 for (uint32_t i = 0; ; i++) {
4056 if (mixerNames[i] == NULL)
4057 break;
4058 cliPrintf(" %s", mixerNames[i]);
4060 cliPrintLinefeed();
4061 return;
4064 for (uint32_t i = 0; ; i++) {
4065 if (mixerNames[i] == NULL) {
4066 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
4067 return;
4069 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
4070 mixerConfigMutable()->mixerMode = i + 1;
4071 break;
4075 cliMixer(cmdName, "");
4077 #endif
4079 static void cliMotor(const char *cmdName, char *cmdline)
4081 if (isEmpty(cmdline)) {
4082 cliShowInvalidArgumentCountError(cmdName);
4084 return;
4087 int motorIndex = 0;
4088 int motorValue = 0;
4090 char *saveptr;
4091 char *pch = strtok_r(cmdline, " ", &saveptr);
4092 int index = 0;
4093 while (pch != NULL) {
4094 switch (index) {
4095 case 0:
4096 motorIndex = parseOutputIndex(cmdName, pch, true);
4097 if (motorIndex == -1) {
4098 return;
4101 break;
4102 case 1:
4103 motorValue = atoi(pch);
4105 break;
4107 index++;
4108 pch = strtok_r(NULL, " ", &saveptr);
4111 if (index == 2) {
4112 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
4113 cliShowArgumentRangeError(cmdName, "VALUE", 1000, 2000);
4114 } else {
4115 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
4117 if (motorIndex != ALL_MOTORS) {
4118 motor_disarmed[motorIndex] = motorOutputValue;
4120 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
4121 } else {
4122 for (int i = 0; i < getMotorCount(); i++) {
4123 motor_disarmed[i] = motorOutputValue;
4126 cliPrintLinef("all motors: %d", motorOutputValue);
4129 } else {
4130 cliShowInvalidArgumentCountError(cmdName);
4134 #ifndef MINIMAL_CLI
4135 static void cliPlaySound(const char *cmdName, char *cmdline)
4137 int i;
4138 const char *name;
4139 static int lastSoundIdx = -1;
4141 if (isEmpty(cmdline)) {
4142 i = lastSoundIdx + 1; //next sound index
4143 if ((name=beeperNameForTableIndex(i)) == NULL) {
4144 while (true) { //no name for index; try next one
4145 if (++i >= beeperTableEntryCount())
4146 i = 0; //if end then wrap around to first entry
4147 if ((name=beeperNameForTableIndex(i)) != NULL)
4148 break; //if name OK then play sound below
4149 if (i == lastSoundIdx + 1) { //prevent infinite loop
4150 cliPrintErrorLinef(cmdName, "ERROR PLAYING SOUND");
4151 return;
4155 } else { //index value was given
4156 i = atoi(cmdline);
4157 if ((name=beeperNameForTableIndex(i)) == NULL) {
4158 cliPrintLinef("No sound for index %d", i);
4159 return;
4162 lastSoundIdx = i;
4163 beeperSilence();
4164 cliPrintLinef("Playing sound %d: %s", i, name);
4165 beeper(beeperModeForTableIndex(i));
4167 #endif
4169 static void cliProfile(const char *cmdName, char *cmdline)
4171 if (isEmpty(cmdline)) {
4172 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4173 return;
4174 } else {
4175 const int i = atoi(cmdline);
4176 if (i >= 0 && i < PID_PROFILE_COUNT) {
4177 changePidProfile(i);
4178 cliProfile(cmdName, "");
4179 } else {
4180 cliPrintErrorLinef(cmdName, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
4185 static void cliRateProfile(const char *cmdName, char *cmdline)
4187 if (isEmpty(cmdline)) {
4188 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4189 return;
4190 } else {
4191 const int i = atoi(cmdline);
4192 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
4193 changeControlRateProfile(i);
4194 cliRateProfile(cmdName, "");
4195 } else {
4196 cliPrintErrorLinef(cmdName, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
4201 static void cliDumpPidProfile(const char *cmdName, uint8_t pidProfileIndex, dumpFlags_t dumpMask)
4203 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4204 // Faulty values
4205 return;
4208 pidProfileIndexToUse = pidProfileIndex;
4210 cliPrintLinefeed();
4211 cliProfile(cmdName, "");
4213 char profileStr[10];
4214 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4215 dumpAllValues(cmdName, PROFILE_VALUE, dumpMask, profileStr);
4217 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4220 static void cliDumpRateProfile(const char *cmdName, uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4222 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4223 // Faulty values
4224 return;
4227 rateProfileIndexToUse = rateProfileIndex;
4229 cliPrintLinefeed();
4230 cliRateProfile(cmdName, "");
4232 char rateProfileStr[14];
4233 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4234 dumpAllValues(cmdName, PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4236 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4239 #ifdef USE_CLI_BATCH
4240 static void cliPrintCommandBatchWarning(const char *cmdName, const char *warning)
4242 cliPrintErrorLinef(cmdName, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4243 if (warning) {
4244 cliPrintErrorLinef(cmdName, warning);
4248 static void resetCommandBatch(void)
4250 commandBatchActive = false;
4251 commandBatchError = false;
4254 static void cliBatch(const char *cmdName, char *cmdline)
4256 if (strncasecmp(cmdline, "start", 5) == 0) {
4257 if (!commandBatchActive) {
4258 commandBatchActive = true;
4259 commandBatchError = false;
4261 cliPrintLine("Command batch started");
4262 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4263 if (commandBatchActive && commandBatchError) {
4264 cliPrintCommandBatchWarning(cmdName, NULL);
4265 } else {
4266 cliPrintLine("Command batch ended");
4268 resetCommandBatch();
4269 } else {
4270 cliPrintErrorLinef(cmdName, "Invalid option");
4273 #endif
4275 static bool prepareSave(void)
4278 #ifdef USE_CLI_BATCH
4279 if (commandBatchActive && commandBatchError) {
4280 return false;
4282 #endif
4284 #if defined(USE_BOARD_INFO)
4285 if (boardInformationUpdated) {
4286 persistBoardInformation();
4288 #if defined(USE_SIGNATURE)
4289 if (signatureUpdated) {
4290 persistSignature();
4292 #endif
4293 #endif // USE_BOARD_INFO
4295 return true;
4298 bool tryPrepareSave(const char *cmdName)
4300 bool success = prepareSave();
4301 #if defined(USE_CLI_BATCH)
4302 if (!success) {
4303 cliPrintCommandBatchWarning(cmdName, "PLEASE FIX ERRORS THEN 'SAVE'");
4304 resetCommandBatch();
4306 return false;
4308 #else
4309 UNUSED(cmdName);
4310 UNUSED(success);
4311 #endif
4313 return true;
4316 static void cliSave(const char *cmdName, char *cmdline)
4318 UNUSED(cmdline);
4320 if (tryPrepareSave(cmdName)) {
4321 writeEEPROM();
4322 cliPrintHashLine("saving");
4324 if (strcasecmp(cmdline, "noreboot") == 0) {
4325 return;
4327 cliReboot();
4331 static void cliDefaults(const char *cmdName, char *cmdline)
4333 bool saveConfigs = true;
4334 uint16_t parameterGroupId = 0;
4336 char *saveptr;
4337 char* tok = strtok_r(cmdline, " ", &saveptr);
4338 bool expectParameterGroupId = false;
4339 while (tok != NULL) {
4340 if (expectParameterGroupId) {
4341 parameterGroupId = atoi(tok);
4342 expectParameterGroupId = false;
4344 if (!parameterGroupId) {
4345 cliShowParseError(cmdName);
4346 return;
4348 } else if (strcasestr(tok, "group_id")) {
4349 expectParameterGroupId = true;
4350 } else if (strcasestr(tok, "nosave")) {
4351 saveConfigs = false;
4352 } else {
4353 cliShowParseError(cmdName);
4355 return;
4358 tok = strtok_r(NULL, " ", &saveptr);
4361 if (expectParameterGroupId) {
4362 cliShowParseError(cmdName);
4364 return;
4367 if (parameterGroupId) {
4368 cliPrintLinef("\r\n# resetting group %d to defaults", parameterGroupId);
4369 backupConfigs();
4370 } else {
4371 cliPrintHashLine("resetting to defaults");
4374 resetConfig();
4376 #ifdef USE_CLI_BATCH
4377 // Reset only the error state and allow the batch active state to remain.
4378 // This way if a "defaults nosave" was issued after the "batch on" we'll
4379 // only reset the current error state but the batch will still be active
4380 // for subsequent commands.
4381 commandBatchError = false;
4382 #endif
4384 #if defined(USE_SIMPLIFIED_TUNING)
4385 applySimplifiedTuningAllProfiles();
4386 #endif
4388 if (parameterGroupId) {
4389 restoreConfigs(parameterGroupId);
4392 if (saveConfigs && tryPrepareSave(cmdName)) {
4393 writeUnmodifiedConfigToEEPROM();
4395 cliReboot();
4399 static void cliPrintVarDefault(const char *cmdName, const clivalue_t *value)
4401 const pgRegistry_t *pg = pgFind(value->pgn);
4402 if (pg) {
4403 const char *defaultFormat = "Default value: ";
4404 const int valueOffset = getValueOffset(value);
4405 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
4406 if (!equalsDefault) {
4407 cliPrintf(defaultFormat, value->name);
4408 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
4409 cliPrintLinefeed();
4414 STATIC_UNIT_TESTED void cliGet(const char *cmdName, char *cmdline)
4416 const clivalue_t *val;
4417 int matchedCommands = 0;
4419 pidProfileIndexToUse = getCurrentPidProfileIndex();
4420 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4422 backupAndResetConfigs();
4424 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4425 if (strcasestr(valueTable[i].name, cmdline)) {
4426 val = &valueTable[i];
4427 if (matchedCommands > 0) {
4428 cliPrintLinefeed();
4430 cliPrintf("%s = ", valueTable[i].name);
4431 cliPrintVar(cmdName, val, 0);
4432 cliPrintLinefeed();
4433 switch (val->type & VALUE_SECTION_MASK) {
4434 case PROFILE_VALUE:
4435 cliProfile(cmdName, "");
4437 break;
4438 case PROFILE_RATE_VALUE:
4439 cliRateProfile(cmdName, "");
4441 break;
4442 default:
4444 break;
4446 cliPrintVarRange(val);
4447 cliPrintVarDefault(cmdName, val);
4449 matchedCommands++;
4453 restoreConfigs(0);
4455 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4456 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4458 if (!matchedCommands) {
4459 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
4463 static uint8_t getWordLength(const char *bufBegin, const char *bufEnd)
4465 while (*(bufEnd - 1) == ' ') {
4466 bufEnd--;
4469 return bufEnd - bufBegin;
4472 uint16_t cliGetSettingIndex(const char *name, size_t length)
4474 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4475 const char *settingName = valueTable[i].name;
4477 // ensure exact match when setting to prevent setting variables with longer names
4478 if (strncasecmp(name, settingName, length) == 0 && length == strlen(settingName)) {
4479 return i;
4482 return valueTableEntryCount;
4485 STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
4487 const uint32_t len = strlen(cmdline);
4488 char *eqptr;
4490 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4491 cliPrintLine("Current settings: ");
4493 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4494 const clivalue_t *val = &valueTable[i];
4495 cliPrintf("%s = ", valueTable[i].name);
4496 cliPrintVar(cmdName, val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4497 cliPrintLinefeed();
4499 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4500 // has equals
4502 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4504 // skip the '=' and any ' ' characters
4505 eqptr++;
4506 eqptr = skipSpace(eqptr);
4508 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4509 if (index >= valueTableEntryCount) {
4510 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
4511 return;
4513 const clivalue_t *val = &valueTable[index];
4515 bool valueChanged = false;
4516 int16_t value = 0;
4517 switch (val->type & VALUE_MODE_MASK) {
4518 case MODE_DIRECT: {
4519 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4520 uint32_t value = strtoul(eqptr, NULL, 10);
4522 if (value <= val->config.u32Max) {
4523 cliSetVar(val, value);
4524 valueChanged = true;
4526 } else if ((val->type & VALUE_TYPE_MASK) == VAR_INT32) {
4527 int32_t value = strtol(eqptr, NULL, 10);
4529 // INT32s are limited to being symmetric, so we test both bounds with the same magnitude
4530 if (value <= val->config.d32Max && value >= -val->config.d32Max) {
4531 cliSetVar(val, value);
4532 valueChanged = true;
4534 } else {
4535 int value = atoi(eqptr);
4537 int min;
4538 int max;
4539 getMinMax(val, &min, &max);
4541 if (value >= min && value <= max) {
4542 cliSetVar(val, value);
4543 valueChanged = true;
4548 break;
4549 case MODE_LOOKUP:
4550 case MODE_BITSET: {
4551 int tableIndex;
4552 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4553 tableIndex = TABLE_OFF_ON;
4554 } else {
4555 tableIndex = val->config.lookup.tableIndex;
4557 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4558 bool matched = false;
4559 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4560 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4562 if (matched) {
4563 value = tableValueIndex;
4565 cliSetVar(val, value);
4566 valueChanged = true;
4571 break;
4573 case MODE_ARRAY: {
4574 const uint8_t arrayLength = val->config.array.length;
4575 char *valPtr = eqptr;
4577 int i = 0;
4578 while (i < arrayLength && valPtr != NULL) {
4579 // skip spaces
4580 valPtr = skipSpace(valPtr);
4582 // process substring starting at valPtr
4583 // note: no need to copy substrings for atoi()
4584 // it stops at the first character that cannot be converted...
4585 switch (val->type & VALUE_TYPE_MASK) {
4586 default:
4587 case VAR_UINT8:
4589 // fetch data pointer
4590 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4591 // store value
4592 *data = (uint8_t)atoi((const char*) valPtr);
4595 break;
4596 case VAR_INT8:
4598 // fetch data pointer
4599 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4600 // store value
4601 *data = (int8_t)atoi((const char*) valPtr);
4604 break;
4605 case VAR_UINT16:
4607 // fetch data pointer
4608 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4609 // store value
4610 *data = (uint16_t)atoi((const char*) valPtr);
4613 break;
4614 case VAR_INT16:
4616 // fetch data pointer
4617 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4618 // store value
4619 *data = (int16_t)atoi((const char*) valPtr);
4622 break;
4623 case VAR_UINT32:
4625 // fetch data pointer
4626 uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
4627 // store value
4628 *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
4631 break;
4632 case VAR_INT32:
4634 // fetch data pointer
4635 int32_t *data = (int32_t *)cliGetValuePointer(val) + i;
4636 // store value
4637 *data = (int32_t)strtol((const char*) valPtr, NULL, 10);
4640 break;
4643 // find next comma (or end of string)
4644 valPtr = strchr(valPtr, ',') + 1;
4646 i++;
4650 // mark as changed
4651 valueChanged = true;
4653 break;
4654 case MODE_STRING: {
4655 char *valPtr = eqptr;
4656 valPtr = skipSpace(valPtr);
4658 const unsigned int len = strlen(valPtr);
4659 const uint8_t min = val->config.string.minlength;
4660 const uint8_t max = val->config.string.maxlength;
4661 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4662 strlen((char *)cliGetValuePointer(val)) == 0 ||
4663 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4665 if (updatable && len > 0 && len <= max) {
4666 memset((char *)cliGetValuePointer(val), 0, max);
4667 if (len >= min && strncmp(valPtr, emptyName, len)) {
4668 memcpy((char *)cliGetValuePointer(val), valPtr, len);
4670 valueChanged = true;
4671 } else {
4672 cliPrintErrorLinef(cmdName, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4675 break;
4678 if (valueChanged) {
4679 cliPrintf("%s set to ", val->name);
4680 cliPrintVar(cmdName, val, 0);
4681 } else {
4682 cliPrintErrorLinef(cmdName, "INVALID VALUE");
4683 cliPrintVarRange(val);
4686 return;
4687 } else {
4688 // no equals, check for matching variables.
4689 cliGet(cmdName, cmdline);
4693 static const char *getMcuTypeById(mcuTypeId_e id)
4695 if (id < ARRAYLEN(mcuTypeNames)) {
4696 return mcuTypeNames[id];
4697 } else {
4698 return "UNKNOWN";
4702 static void cliStatus(const char *cmdName, char *cmdline)
4704 UNUSED(cmdName);
4705 UNUSED(cmdline);
4707 // MCU type, clock, vrefint, core temperature
4709 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock / 1000000));
4711 #if defined(STM32F4) || defined(STM32G4) || defined(APM32F4)
4712 // Only F4 and G4 is capable of switching between HSE/HSI (for now)
4713 int sysclkSource = SystemSYSCLKSource();
4715 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4716 const char *PLLSource[] = { "-HSI", "-HSE" };
4718 int pllSource;
4720 if (sysclkSource >= 2) {
4721 pllSource = SystemPLLSource();
4724 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4725 #endif
4727 #ifdef USE_ADC_INTERNAL
4728 uint16_t vrefintMv = getVrefMv();
4729 int16_t coretemp = getCoreTemperatureCelsius();
4730 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4731 #else
4732 cliPrintLinefeed();
4733 #endif
4735 // Stack and config sizes and usages
4737 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4738 #ifdef USE_STACK_CHECK
4739 cliPrintf(", Stack used: %d", stackUsedSize());
4740 #endif
4741 cliPrintLinefeed();
4743 cliPrintLinef("Configuration: %s, size: %d, max available: %d", configurationStates[systemConfigMutable()->configurationState], getEEPROMConfigSize(), getEEPROMStorageSize());
4745 // Devices
4746 #if defined(USE_SPI) || defined(USE_I2C)
4747 cliPrint("Devices detected:");
4748 #if defined(USE_SPI)
4749 cliPrintf(" SPI:%d", spiGetRegisteredDeviceCount());
4750 #if defined(USE_I2C)
4751 cliPrint(",");
4752 #endif
4753 #endif
4754 #if defined(USE_I2C)
4755 cliPrintf(" I2C:%d", i2cGetRegisteredDeviceCount());
4756 #endif
4757 cliPrintLinefeed();
4758 #endif
4760 // Sensors
4761 cliPrint("Gyros detected:");
4762 bool found = false;
4763 for (unsigned pos = 0; pos < 7; pos++) {
4764 if (gyroConfig()->gyrosDetected & BIT(pos)) {
4765 if (found) {
4766 cliPrint(",");
4767 } else {
4768 found = true;
4770 cliPrintf(" gyro %d", pos + 1);
4773 #ifdef USE_SPI
4774 if (gyroActiveDev()->gyroModeSPI != GYRO_EXTI_NO_INT) {
4775 cliPrintf(" locked");
4777 if (gyroActiveDev()->gyroModeSPI == GYRO_EXTI_INT_DMA) {
4778 cliPrintf(" dma");
4780 if (spiGetExtDeviceCount(&gyroActiveDev()->dev) > 1) {
4781 cliPrintf(" shared");
4783 #endif
4784 cliPrintLinefeed();
4786 #if defined(USE_SENSOR_NAMES)
4787 const uint32_t detectedSensorsMask = sensorsMask();
4788 for (uint32_t i = 0; ; i++) {
4789 if (sensorTypeNames[i] == NULL) {
4790 break;
4792 const uint32_t mask = (1 << i);
4793 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4794 const uint8_t sensorHardwareIndex = detectedSensors[i];
4795 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4796 if (i) {
4797 cliPrint(", ");
4799 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4800 #if defined(USE_ACC)
4801 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4802 cliPrintf(".%c", acc.dev.revisionCode);
4804 #endif
4807 cliPrintLinefeed();
4808 #endif /* USE_SENSOR_NAMES */
4810 #if defined(USE_OSD)
4811 osdDisplayPortDevice_e displayPortDeviceType;
4812 displayPort_t *osdDisplayPort = osdGetDisplayPort(&displayPortDeviceType);
4814 cliPrintLinef("OSD: %s (%u x %u)", lookupTableOsdDisplayPortDevice[displayPortDeviceType], osdDisplayPort->cols, osdDisplayPort->rows);
4815 #endif
4817 if (buildKey) {
4818 cliPrintf("BUILD KEY: %s", buildKey);
4819 if (releaseName) {
4820 cliPrintf(" (%s)", releaseName);
4822 cliPrintLinefeed();
4824 // Uptime and wall clock
4826 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4828 #ifdef USE_RTC_TIME
4829 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4830 dateTime_t dt;
4831 if (rtcGetDateTime(&dt)) {
4832 dateTimeFormatLocal(buf, &dt);
4833 cliPrintf(", Current Time: %s", buf);
4835 #endif
4836 cliPrintLinefeed();
4838 // Run status
4840 const int gyroRate = getTaskDeltaTimeUs(TASK_GYRO) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_GYRO)));
4842 int rxRate = getRxRateValid() ? getCurrentRxRateHz() : 0;
4844 const int systemRate = getTaskDeltaTimeUs(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_SYSTEM)));
4845 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4846 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE), getTaskDeltaTimeUs(TASK_GYRO), gyroRate, rxRate, systemRate);
4848 // Battery meter
4850 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4852 // Other devices and status
4854 #ifdef USE_I2C
4855 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4856 #else
4857 const uint16_t i2cErrorCounter = 0;
4858 #endif
4859 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4861 #ifdef USE_SDCARD
4862 cliSdInfo(cmdName, "");
4863 #endif
4865 #ifdef USE_FLASH_CHIP
4866 const flashGeometry_t *layout = flashGetGeometry();
4867 if (layout->jedecId != 0) {
4868 cliPrintLinef("FLASH: JEDEC ID=0x%08x %uM", layout->jedecId, layout->totalSize >> 20);
4870 #endif
4872 #ifdef USE_GPS
4873 cliPrint("GPS: ");
4874 if (featureIsEnabled(FEATURE_GPS)) {
4875 if (gpsData.state >= GPS_STATE_CONFIGURE) {
4876 cliPrint("connected, ");
4877 } else {
4878 cliPrint("NOT CONNECTED, ");
4880 if (gpsConfig()->provider == GPS_MSP) {
4881 cliPrint("MSP, ");
4882 } else {
4883 const serialPortConfig_t *gpsPortConfig = findSerialPortConfig(FUNCTION_GPS);
4884 if (!gpsPortConfig) {
4885 cliPrint("NO PORT, ");
4886 } else {
4887 cliPrintf("UART%d %ld (set to ", (gpsPortConfig->identifier + 1), baudRates[getGpsPortActualBaudRateIndex()]);
4888 if (gpsConfig()->autoBaud == GPS_AUTOBAUD_ON) {
4889 cliPrint("AUTO");
4890 } else {
4891 cliPrintf("%ld", baudRates[gpsPortConfig->gps_baudrateIndex]);
4893 cliPrint("), ");
4896 if (gpsData.state <= GPS_STATE_CONFIGURE) {
4897 cliPrint("NOT CONFIGURED");
4898 } else {
4899 if (gpsConfig()->autoConfig == GPS_AUTOCONFIG_OFF) {
4900 cliPrint("auto config OFF");
4901 } else {
4902 cliPrint("configured");
4905 cliPrintf(", version = %s", gpsData.platformVersion != UBX_VERSION_UNDEF ? ubloxVersionMap[gpsData.platformVersion].str : "unknown");
4906 } else {
4907 cliPrint("NOT ENABLED");
4909 cliPrintLinefeed();
4910 #endif // USE_GPS
4912 cliPrint("Arming disable flags:");
4913 armingDisableFlags_e flags = getArmingDisableFlags();
4914 while (flags) {
4915 const armingDisableFlags_e flag = 1 << (ffs(flags) - 1);
4916 flags &= ~flag;
4917 cliPrintf(" %s", getArmingDisableFlagName(flag));
4919 cliPrintLinefeed();
4922 static void cliTasks(const char *cmdName, char *cmdline)
4924 UNUSED(cmdName);
4925 UNUSED(cmdline);
4926 int averageLoadSum = 0;
4928 #ifndef MINIMAL_CLI
4929 if (systemConfig()->task_statistics) {
4930 #if defined(USE_LATE_TASK_STATISTICS)
4931 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms late run reqd/us");
4932 #else
4933 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4934 #endif
4935 } else {
4936 cliPrintLine("Task list");
4938 #endif
4939 for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4940 taskInfo_t taskInfo;
4941 getTaskInfo(taskId, &taskInfo);
4942 if (taskInfo.isEnabled) {
4943 int taskFrequency = taskInfo.averageDeltaTime10thUs == 0 ? 0 : lrintf(1e7f / taskInfo.averageDeltaTime10thUs);
4944 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4945 const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 : (taskInfo.maxExecutionTimeUs * taskFrequency) / 1000;
4946 const int averageLoad = taskInfo.averageExecutionTime10thUs == 0 ? 0 : (taskInfo.averageExecutionTime10thUs * taskFrequency) / 10000;
4947 if (taskId != TASK_SERIAL) {
4948 averageLoadSum += averageLoad;
4950 if (systemConfig()->task_statistics) {
4951 #if defined(USE_LATE_TASK_STATISTICS)
4952 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d %6d %6d %7d",
4953 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
4954 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4955 taskInfo.totalExecutionTimeUs / 1000,
4956 taskInfo.lateCount, taskInfo.runCount, taskInfo.execTime);
4957 #else
4958 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4959 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
4960 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4961 taskInfo.totalExecutionTimeUs / 1000);
4962 #endif
4963 } else {
4964 cliPrintLinef("%6d", taskFrequency);
4967 schedulerResetTaskMaxExecutionTime(taskId);
4970 if (systemConfig()->task_statistics) {
4971 cfCheckFuncInfo_t checkFuncInfo;
4972 getCheckFuncInfo(&checkFuncInfo);
4973 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTimeUs, checkFuncInfo.averageExecutionTimeUs, checkFuncInfo.totalExecutionTimeUs / 1000);
4974 cliPrintLinef("Total (excluding SERIAL) %33d.%1d%%", averageLoadSum/10, averageLoadSum%10);
4975 if (debugMode == DEBUG_SCHEDULER_DETERMINISM) {
4976 extern int32_t schedLoopStartCycles, taskGuardCycles;
4978 cliPrintLinef("Scheduler start cycles %d guard cycles %d", schedLoopStartCycles, taskGuardCycles);
4980 schedulerResetCheckFunctionMaxExecutionTime();
4984 static void printVersion(bool printBoardInfo)
4986 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4987 FC_FIRMWARE_NAME,
4988 targetName,
4989 systemConfig()->boardIdentifier,
4990 FC_VERSION_STRING,
4991 buildDate,
4992 buildTime,
4993 shortGitRevision,
4994 MSP_API_VERSION_STRING
4997 cliPrintLinefeed();
4999 #if defined(__CONFIG_REVISION__)
5000 cliPrintLinef("# config rev: %s", shortConfigGitRevision);
5001 #endif
5003 #if defined(USE_BOARD_INFO)
5004 if (printBoardInfo && strlen(getManufacturerId()) && strlen(getBoardName())) {
5005 cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
5007 #else
5008 UNUSED(printBoardInfo);
5009 #endif
5012 static void cliVersion(const char *cmdName, char *cmdline)
5014 UNUSED(cmdName);
5015 UNUSED(cmdline);
5017 printVersion(true);
5020 #ifdef USE_RC_SMOOTHING_FILTER
5021 static void cliRcSmoothing(const char *cmdName, char *cmdline)
5023 UNUSED(cmdName);
5024 UNUSED(cmdline);
5025 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
5026 cliPrint("# RC Smoothing Type: ");
5027 if (rxConfig()->rc_smoothing_mode) {
5028 cliPrintLine("FILTER");
5029 if (rcSmoothingAutoCalculate()) {
5030 cliPrint("# Detected Rx frequency: ");
5031 if (getRxRateValid()) {
5032 cliPrintLinef("%dHz", lrintf(rcSmoothingData->smoothedRxRateHz));
5033 } else {
5034 cliPrintLine("NO SIGNAL");
5037 cliPrintf("# Active setpoint cutoff: %dhz ", rcSmoothingData->setpointCutoffFrequency);
5038 if (rcSmoothingData->setpointCutoffSetting) {
5039 cliPrintLine("(manual)");
5040 } else {
5041 cliPrintLine("(auto)");
5043 cliPrintf("# Active FF cutoff: %dhz ", rcSmoothingData->feedforwardCutoffFrequency);
5044 if (rcSmoothingData->feedforwardCutoffSetting) {
5045 cliPrintLine("(manual)");
5046 } else {
5047 cliPrintLine("(auto)");
5049 cliPrintf("# Active throttle cutoff: %dhz ", rcSmoothingData->throttleCutoffFrequency);
5050 if (rcSmoothingData->throttleCutoffSetting) {
5051 cliPrintLine("(manual)");
5052 } else {
5053 cliPrintLine("(auto)");
5055 } else {
5056 cliPrintLine("OFF");
5059 #endif // USE_RC_SMOOTHING_FILTER
5061 #if defined(USE_RESOURCE_MGMT)
5063 #define RESOURCE_VALUE_MAX_INDEX(x) ((x) == 0 ? 1 : (x))
5065 typedef struct {
5066 const uint8_t owner;
5067 pgn_t pgn;
5068 uint8_t stride;
5069 uint8_t offset;
5070 const uint8_t maxIndex;
5071 } cliResourceValue_t;
5073 // Handy macros for keeping the table tidy.
5074 // DEFS : Single entry
5075 // DEFA : Array of uint8_t (stride = 1)
5076 // DEFW : Wider stride case; array of structs.
5078 #define DEFS(owner, pgn, type, member) \
5079 { owner, pgn, 0, offsetof(type, member), 0 }
5081 #define DEFA(owner, pgn, type, member, max) \
5082 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
5084 #define DEFW(owner, pgn, type, member, max) \
5085 { owner, pgn, sizeof(type), offsetof(type, member), max }
5087 const cliResourceValue_t resourceTable[] = {
5088 #if defined(USE_BEEPER)
5089 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag ),
5090 #endif
5091 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
5092 #if defined(USE_SERVOS)
5093 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
5094 #endif
5095 #if defined(USE_RX_PPM)
5096 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
5097 #endif
5098 #if defined(USE_RX_PWM)
5099 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
5100 #endif
5101 #if defined(USE_RANGEFINDER_HCSR04)
5102 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
5103 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
5104 #endif
5105 #if defined(USE_LED_STRIP)
5106 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
5107 #endif
5108 #if defined(USE_UART)
5109 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
5110 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
5111 #endif
5112 #if defined(USE_INVERTER)
5113 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
5114 // LPUART and SOFTSERIAL don't need external inversion
5115 #endif
5116 #if defined(USE_SOFTSERIAL)
5117 DEFA( OWNER_SOFTSERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_SOFTSERIAL_OFFSET], RESOURCE_SOFTSERIAL_COUNT ),
5118 DEFA( OWNER_SOFTSERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_SOFTSERIAL_OFFSET], RESOURCE_SOFTSERIAL_COUNT ),
5119 #endif
5120 #if defined(USE_LPUART)
5121 DEFA( OWNER_LPUART_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_LPUART_OFFSET], RESOURCE_LPUART_COUNT ),
5122 DEFA( OWNER_LPUART_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_LPUART_OFFSET], RESOURCE_LPUART_COUNT ),
5123 #endif
5124 #ifdef USE_I2C
5125 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
5126 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
5127 #endif
5128 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
5129 #ifdef USE_SPEKTRUM_BIND
5130 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
5131 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
5132 #endif
5133 #ifdef USE_TRANSPONDER
5134 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
5135 #endif
5136 #ifdef USE_SPI
5137 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
5138 DEFW( OWNER_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
5139 DEFW( OWNER_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
5140 #endif
5141 #ifdef USE_ESCSERIAL
5142 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
5143 #endif
5144 #ifdef USE_CAMERA_CONTROL
5145 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
5146 #endif
5147 #ifdef USE_ADC
5148 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
5149 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
5150 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
5151 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
5152 #endif
5153 #ifdef USE_BARO
5154 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
5155 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
5156 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
5157 #endif
5158 #ifdef USE_MAG
5159 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
5160 #ifdef USE_MAG_DATA_READY_SIGNAL
5161 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
5162 #endif
5163 #endif
5164 #ifdef USE_SDCARD_SPI
5165 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
5166 #endif
5167 #ifdef USE_SDCARD
5168 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
5169 #endif
5170 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
5171 DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
5172 DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
5173 DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
5174 DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
5175 DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
5176 DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
5177 #endif
5178 #ifdef USE_PINIO
5179 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
5180 #endif
5181 #if defined(USE_USB_MSC)
5182 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
5183 #endif
5184 #ifdef USE_FLASH_CHIP
5185 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
5186 #endif
5187 #ifdef USE_MAX7456
5188 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
5189 #endif
5190 #ifdef USE_RX_SPI
5191 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
5192 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
5193 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
5194 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
5195 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
5196 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
5197 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
5198 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
5199 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
5200 #endif
5201 #endif
5202 #if defined(USE_RX_EXPRESSLRS)
5203 DEFS( OWNER_RX_SPI_EXPRESSLRS_RESET, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, resetIoTag ),
5204 DEFS( OWNER_RX_SPI_EXPRESSLRS_BUSY, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, busyIoTag ),
5205 #endif
5206 #endif
5207 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
5208 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
5209 #if defined(USE_GYRO_CLKIN)
5210 DEFW( OWNER_GYRO_CLKIN, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, clkIn, MAX_GYRODEV_COUNT),
5211 #endif
5212 #ifdef USE_USB_DETECT
5213 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
5214 #endif
5215 #ifdef USE_VTX_RTC6705
5216 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
5217 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
5218 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
5219 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
5220 #endif
5221 #ifdef USE_PIN_PULL_UP_DOWN
5222 DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5223 DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5224 #endif
5227 #undef DEFS
5228 #undef DEFA
5229 #undef DEFW
5231 static ioTag_t* getIoTag(const cliResourceValue_t value, uint8_t index)
5233 const pgRegistry_t* rec = pgFind(value.pgn);
5234 return (ioTag_t *)(rec->address + value.stride * index + value.offset);
5237 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
5239 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5240 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
5241 const char* owner = ownerNames[resourceTable[i].owner];
5242 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
5243 const void *currentConfig;
5244 const void *defaultConfig;
5245 if (isReadingConfigFromCopy()) {
5246 currentConfig = pg->copy;
5247 defaultConfig = pg->address;
5248 } else {
5249 currentConfig = pg->address;
5250 defaultConfig = NULL;
5253 for (int index = 0; index < RESOURCE_VALUE_MAX_INDEX(resourceTable[i].maxIndex); index++) {
5254 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5255 ioTag_t ioTagDefault = 0;
5256 if (defaultConfig) {
5257 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5260 const bool equalsDefault = ioTag == ioTagDefault;
5261 const char *format = "resource %s %d %c%02d";
5262 const char *formatUnassigned = "resource %s %d NONE";
5263 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5264 if (ioTagDefault) {
5265 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
5266 } else if (defaultConfig) {
5267 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5269 if (ioTag) {
5270 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
5271 } else if (!(dumpMask & HIDE_UNUSED)) {
5272 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5278 static void printResourceOwner(uint8_t owner, uint8_t index)
5280 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
5282 if (resourceTable[owner].maxIndex > 0) {
5283 cliPrintf(" %d", RESOURCE_INDEX(index));
5287 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
5289 if (!newTag) {
5290 return;
5293 const char * format = "\r\nNOTE: %c%02d already assigned to ";
5294 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
5295 for (int i = 0; i < RESOURCE_VALUE_MAX_INDEX(resourceTable[r].maxIndex); i++) {
5296 ioTag_t *tag = getIoTag(resourceTable[r], i);
5297 if (*tag == newTag) {
5298 bool cleared = false;
5299 if (r == resourceIndex) {
5300 if (i == index) {
5301 continue;
5303 *tag = IO_TAG_NONE;
5304 cleared = true;
5307 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
5309 printResourceOwner(r, i);
5311 if (cleared) {
5312 cliPrintf(". ");
5313 printResourceOwner(r, i);
5314 cliPrintf(" disabled");
5317 cliPrintLine(".");
5323 static bool strToPin(char *ptr, ioTag_t *tag)
5325 if (strcasecmp(ptr, "NONE") == 0) {
5326 *tag = IO_TAG_NONE;
5328 return true;
5329 } else {
5330 const unsigned port = (*ptr >= 'a') ? *ptr - 'a' : *ptr - 'A';
5331 if (port < 8) {
5332 ptr++;
5334 char *end;
5335 const long pin = strtol(ptr, &end, 10);
5336 if (end != ptr && pin >= 0 && pin < 16) {
5337 *tag = DEFIO_TAG_MAKE(port, pin);
5339 return true;
5344 return false;
5347 #ifdef USE_DMA
5348 static void showDma(void)
5350 cliPrintLinefeed();
5352 #ifdef MINIMAL_CLI
5353 cliPrintLine("DMA:");
5354 #else
5355 cliPrintLine("Currently active DMA:");
5356 cliRepeat('-', 20);
5357 #endif
5358 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
5359 const resourceOwner_t *owner = dmaGetOwner(i);
5361 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
5362 if (owner->resourceIndex > 0) {
5363 cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
5364 } else {
5365 cliPrintLinef(" %s", ownerNames[owner->owner]);
5369 #endif
5371 #ifdef USE_DMA_SPEC
5373 typedef struct dmaoptEntry_s {
5374 char *device;
5375 dmaPeripheral_e peripheral;
5376 pgn_t pgn;
5377 uint8_t stride;
5378 uint8_t offset;
5379 uint8_t maxIndex;
5380 uint32_t presenceMask;
5381 } dmaoptEntry_t;
5383 #define MASK_IGNORED (0)
5385 // Handy macros for keeping the table tidy.
5386 // DEFS : Single entry
5387 // DEFA : Array of uint8_t (stride = 1)
5388 // DEFW : Wider stride case; array of structs.
5389 // DEFW_OFS: array of structs, starting at offset ofs
5391 #define DEFS(device, peripheral, pgn, type, member) \
5392 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5394 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5395 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5397 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5398 DEFW_OFS(device, peripheral, pgn, type, member, 0, max, mask)
5400 #define DEFW_OFS(device, peripheral, pgn, type, member, ofs, max, mask) \
5401 { device, peripheral, pgn, sizeof(type), offsetof(type, member) + (ofs) * sizeof(type), max, mask }
5403 dmaoptEntry_t dmaoptEntryTable[] = {
5404 DEFW("SPI_SDO", DMA_PERIPH_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5405 DEFW("SPI_SDI", DMA_PERIPH_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5406 // SPI_TX/SPI_RX for backwards compatibility with unified configs defined for 4.2.x
5407 DEFW("SPI_TX", DMA_PERIPH_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5408 DEFW("SPI_RX", DMA_PERIPH_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5409 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
5410 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
5411 #ifdef USE_UART
5412 DEFW_OFS("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, RESOURCE_UART_OFFSET, RESOURCE_UART_COUNT, MASK_IGNORED),
5413 DEFW_OFS("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, RESOURCE_UART_OFFSET, RESOURCE_UART_COUNT, MASK_IGNORED),
5414 #endif
5415 #ifdef USE_LPUART
5416 DEFW_OFS("LPUART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, RESOURCE_LPUART_OFFSET, RESOURCE_LPUART_COUNT, MASK_IGNORED),
5417 DEFW_OFS("LPUART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, RESOURCE_LPUART_OFFSET, RESOURCE_LPUART_COUNT, MASK_IGNORED),
5418 #endif
5419 #if defined(STM32H7) || defined(STM32G4)
5420 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
5421 #endif
5424 #undef DEFS
5425 #undef DEFA
5426 #undef DEFW
5428 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5429 #define DMA_OPT_STRING_BUFSIZE 5
5431 #if defined(STM32H7) || defined(STM32G4) || defined(AT32F435)
5432 #define DMA_CHANREQ_STRING "Request"
5433 #else
5434 #define DMA_CHANREQ_STRING "Channel"
5435 #endif
5437 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(APM32F4)
5438 #define DMA_STCH_STRING "Stream"
5439 #else
5440 #define DMA_STCH_STRING "Channel"
5441 #endif
5443 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5445 static void optToString(int optval, char *buf)
5447 if (optval == DMA_OPT_UNUSED) {
5448 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
5449 } else {
5450 tfp_sprintf(buf, "%d", optval);
5454 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5456 // We compute number to display for different peripherals in advance.
5457 // This is done to deal with TIMUP which numbered non-contiguously.
5458 // Note that using timerGetNumberByIndex is not a generic solution,
5459 // but we are lucky that TIMUP is the only peripheral with non-contiguous numbering.
5461 int uiIndex;
5463 if (entry->presenceMask) {
5464 uiIndex = timerGetNumberByIndex(index);
5465 } else {
5466 uiIndex = DMA_OPT_UI_INDEX(index);
5469 if (dmaopt != DMA_OPT_UNUSED) {
5470 printValue(dumpMask, equalsDefault,
5471 "dma %s %d %d",
5472 entry->device, uiIndex, dmaopt);
5474 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5475 dmaCode_t dmaCode = 0;
5476 if (dmaChannelSpec) {
5477 dmaCode = dmaChannelSpec->code;
5479 printValue(dumpMask, equalsDefault,
5480 "# %s %d: " DMASPEC_FORMAT_STRING,
5481 entry->device, uiIndex, DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5482 } else if (!(dumpMask & HIDE_UNUSED)) {
5483 printValue(dumpMask, equalsDefault,
5484 "dma %s %d NONE",
5485 entry->device, uiIndex);
5489 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5491 const pgRegistry_t* pg = pgFind(entry->pgn);
5492 const void *currentConfig;
5493 const void *defaultConfig;
5495 if (isReadingConfigFromCopy()) {
5496 currentConfig = pg->copy;
5497 defaultConfig = pg->address;
5498 } else {
5499 currentConfig = pg->address;
5500 defaultConfig = NULL;
5503 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5504 dmaoptValue_t defaultOpt;
5506 if (defaultConfig) {
5507 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5508 } else {
5509 defaultOpt = DMA_OPT_UNUSED;
5512 bool equalsDefault = currentOpt == defaultOpt;
5513 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5515 if (defaultConfig) {
5516 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5519 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5520 return headingStr;
5523 #if defined(USE_TIMER_MGMT)
5524 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5526 const char *format = "dma pin %c%02d %d";
5528 if (dmaopt != DMA_OPT_UNUSED) {
5529 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5530 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5531 dmaopt
5534 if (printDetails) {
5535 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5536 if (dmaChannelSpec) {
5537 dmaCode_t dmaCode = dmaChannelSpec->code;
5538 printValue(dumpMask, false,
5539 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5540 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5541 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5545 } else if (!(dumpMask & HIDE_UNUSED)) {
5546 printValue(dumpMask, equalsDefault,
5547 "dma pin %c%02d NONE",
5548 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5553 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5555 const ioTag_t ioTag = currentConfig[index].ioTag;
5557 if (!ioTag) {
5558 return headingStr;
5561 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5562 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5564 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5565 bool equalsDefault = defaultDmaopt == dmaopt;
5566 if (defaultConfig) {
5567 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5568 if (defaultConfig[i].ioTag == ioTag) {
5569 defaultDmaopt = defaultConfig[i].dmaopt;
5571 // 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.
5572 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5574 tagsInUse[index] = true;
5576 break;
5581 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5583 if (defaultConfig) {
5584 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5587 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5588 return headingStr;
5590 #endif
5592 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5594 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5595 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5596 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5597 for (int index = 0; index < entry->maxIndex; index++) {
5598 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5602 #if defined(USE_TIMER_MGMT)
5603 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5604 const timerIOConfig_t *currentConfig;
5605 const timerIOConfig_t *defaultConfig;
5607 if (isReadingConfigFromCopy()) {
5608 currentConfig = (timerIOConfig_t *)pg->copy;
5609 defaultConfig = (timerIOConfig_t *)pg->address;
5610 } else {
5611 currentConfig = (timerIOConfig_t *)pg->address;
5612 defaultConfig = NULL;
5615 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5616 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5617 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5620 if (defaultConfig) {
5621 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5622 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5623 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5624 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5625 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5627 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5631 #endif
5634 static void cliDmaopt(const char *cmdName, char *cmdline)
5636 char *pch = NULL;
5637 char *saveptr;
5639 // Peripheral name or command option
5640 pch = strtok_r(cmdline, " ", &saveptr);
5641 if (!pch) {
5642 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5644 return;
5645 } else if (strcasecmp(pch, "list") == 0) {
5646 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5648 return;
5651 dmaoptEntry_t *entry = NULL;
5652 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5653 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5654 entry = &dmaoptEntryTable[i];
5658 if (!entry && strcasecmp(pch, "pin") != 0) {
5659 cliPrintErrorLinef(cmdName, "BAD DEVICE: %s", pch);
5660 return;
5663 // Index
5664 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5666 int index = 0;
5667 dmaoptValue_t *optaddr = NULL;
5669 ioTag_t ioTag = IO_TAG_NONE;
5670 #if defined(USE_TIMER_MGMT)
5671 timerIOConfig_t *timerIoConfig = NULL;
5672 #endif
5673 const timerHardware_t *timer = NULL;
5674 pch = strtok_r(NULL, " ", &saveptr);
5675 if (entry) {
5676 index = pch ? (atoi(pch) - 1) : -1;
5677 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5678 cliPrintErrorLinef(cmdName, "BAD INDEX: '%s'", pch ? pch : "");
5679 return;
5682 const pgRegistry_t* pg = pgFind(entry->pgn);
5683 const void *currentConfig;
5684 if (isWritingConfigToCopy()) {
5685 currentConfig = pg->copy;
5686 } else {
5687 currentConfig = pg->address;
5689 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5690 orgval = *optaddr;
5691 } else {
5692 // It's a pin
5693 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5694 cliPrintErrorLinef(cmdName, "INVALID PIN: '%s'", pch ? pch : "");
5696 return;
5699 orgval = dmaoptByTag(ioTag);
5700 #if defined(USE_TIMER_MGMT)
5701 timerIoConfig = timerIoConfigByTag(ioTag);
5702 #endif
5703 timer = timerGetConfiguredByTag(ioTag);
5706 // opt or list
5707 pch = strtok_r(NULL, " ", &saveptr);
5708 if (!pch) {
5709 if (entry) {
5710 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5712 #if defined(USE_TIMER_MGMT)
5713 else {
5714 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5716 #endif
5718 return;
5719 } else if (strcasecmp(pch, "list") == 0) {
5720 // Show possible opts
5721 const dmaChannelSpec_t *dmaChannelSpec;
5722 if (entry) {
5723 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5724 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5726 } else {
5727 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5728 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5732 return;
5733 } else if (pch) {
5734 int optval;
5735 if (strcasecmp(pch, "none") == 0) {
5736 optval = DMA_OPT_UNUSED;
5737 } else {
5738 optval = atoi(pch);
5740 if (entry) {
5741 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5742 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5744 return;
5746 } else {
5747 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5748 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5750 return;
5755 char optvalString[DMA_OPT_STRING_BUFSIZE];
5756 optToString(optval, optvalString);
5758 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5759 optToString(orgval, orgvalString);
5761 if (optval != orgval) {
5762 if (entry) {
5763 *optaddr = optval;
5765 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5766 } else {
5767 #if defined(USE_TIMER_MGMT)
5768 timerIoConfig->dmaopt = optval;
5769 #endif
5771 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5773 } else {
5774 if (entry) {
5775 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5776 } else {
5777 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5782 #endif // USE_DMA_SPEC
5784 #ifdef USE_DMA
5785 static void cliDma(const char *cmdName, char* cmdline)
5787 int len = strlen(cmdline);
5788 if (len && strncasecmp(cmdline, "show", len) == 0) {
5789 showDma();
5791 return;
5794 #if defined(USE_DMA_SPEC)
5795 cliDmaopt(cmdName, cmdline);
5796 #else
5797 cliShowParseError(cmdName);
5798 #endif
5800 #endif
5801 #endif // USE_RESOURCE_MGMT
5803 #ifdef USE_TIMER_MGMT
5804 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5806 const char *format = "timer %c%02d AF%d";
5807 const char *emptyFormat = "timer %c%02d NONE";
5809 if (timerIndex > 0) {
5810 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5811 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5812 IO_GPIOPortIdxByTag(ioTag) + 'A',
5813 IO_GPIOPinIdxByTag(ioTag),
5814 timer->alternateFunction
5816 if (printDetails) {
5817 printValue(dumpMask, false,
5818 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5819 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5820 timerGetTIMNumber(timer->tim),
5821 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5822 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5823 timer->alternateFunction
5826 } else {
5827 printValue(dumpMask, equalsDefault, emptyFormat,
5828 IO_GPIOPortIdxByTag(ioTag) + 'A',
5829 IO_GPIOPinIdxByTag(ioTag)
5834 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5836 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5837 const timerIOConfig_t *currentConfig;
5838 const timerIOConfig_t *defaultConfig;
5840 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5841 if (isReadingConfigFromCopy()) {
5842 currentConfig = (timerIOConfig_t *)pg->copy;
5843 defaultConfig = (timerIOConfig_t *)pg->address;
5844 } else {
5845 currentConfig = (timerIOConfig_t *)pg->address;
5846 defaultConfig = NULL;
5849 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5850 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5851 const ioTag_t ioTag = currentConfig[i].ioTag;
5853 if (!ioTag) {
5854 continue;
5857 const uint8_t timerIndex = currentConfig[i].index;
5859 uint8_t defaultTimerIndex = 0;
5860 if (defaultConfig) {
5861 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5862 if (defaultConfig[i].ioTag == ioTag) {
5863 defaultTimerIndex = defaultConfig[i].index;
5864 tagsInUse[i] = true;
5866 break;
5871 const bool equalsDefault = defaultTimerIndex == timerIndex;
5872 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5873 if (defaultConfig && defaultTimerIndex) {
5874 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5877 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5880 if (defaultConfig) {
5881 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5882 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5883 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5884 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5886 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5892 #define TIMER_INDEX_UNDEFINED -1
5893 #define TIMER_AF_STRING_BUFSIZE 5
5895 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5897 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5898 if (!timer) {
5899 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5900 } else {
5901 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5905 #ifdef USE_TIMER_MAP_PRINT
5906 static void showTimerMap(void)
5908 cliPrintLinefeed();
5909 cliPrintLine("Timer Mapping:");
5910 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5911 const ioTag_t ioTag = timerIOConfig(i)->ioTag;
5913 if (!ioTag) {
5914 continue;
5917 cliPrintLinef(" TIMER_PIN_MAP(%d, P%c%d, %d, %d)",
5919 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5920 timerIOConfig(i)->index,
5921 timerIOConfig(i)->dmaopt
5925 #endif
5927 static void showTimers(void)
5929 cliPrintLinefeed();
5931 #ifdef MINIMAL_CLI
5932 cliPrintLine("Timers:");
5933 #else
5934 cliPrintLine("Currently active Timers:");
5935 cliRepeat('-', 23);
5936 #endif
5938 int8_t timerNumber;
5939 for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
5940 cliPrintf("TIM%d:", timerNumber);
5941 bool timerUsed = false;
5942 for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
5943 const timerHardware_t *timer = timerGetAllocatedByNumberAndChannel(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
5944 const resourceOwner_t *timerOwner = timerGetOwner(timer);
5945 if (timerOwner->owner) {
5946 if (!timerUsed) {
5947 timerUsed = true;
5949 cliPrintLinefeed();
5952 if (timerOwner->resourceIndex > 0) {
5953 cliPrintLinef(" CH%d%s: %s %d", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner], timerOwner->resourceIndex);
5954 } else {
5955 cliPrintLinef(" CH%d%s: %s", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner]);
5960 if (!timerUsed) {
5961 cliPrintLine(" FREE");
5966 static void cliTimer(const char *cmdName, char *cmdline)
5968 int len = strlen(cmdline);
5970 if (len == 0) {
5971 printTimer(DUMP_MASTER, NULL);
5973 return;
5974 } else if (strncasecmp(cmdline, "list", len) == 0) {
5975 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5977 return;
5978 #ifdef USE_TIMER_MAP_PRINT
5979 } else if (strncasecmp(cmdline, "map", len) == 0) {
5980 showTimerMap();
5982 return;
5983 #endif
5984 } else if (strncasecmp(cmdline, "show", len) == 0) {
5985 showTimers();
5987 return;
5990 char *pch = NULL;
5991 char *saveptr;
5993 ioTag_t ioTag = IO_TAG_NONE;
5994 pch = strtok_r(cmdline, " ", &saveptr);
5995 if (!pch || !strToPin(pch, &ioTag)) {
5996 cliShowParseError(cmdName);
5998 return;
5999 } else if (!IOGetByTag(ioTag)) {
6000 cliPrintErrorLinef(cmdName, "PIN NOT USED ON BOARD.");
6002 return;
6005 int timerIOIndex = TIMER_INDEX_UNDEFINED;
6006 bool isExistingTimerOpt = false;
6007 /* find existing entry, or go for next available */
6008 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
6009 if (timerIOConfig(i)->ioTag == ioTag) {
6010 timerIOIndex = i;
6011 isExistingTimerOpt = true;
6013 break;
6016 /* first available empty slot */
6017 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
6018 timerIOIndex = i;
6022 if (timerIOIndex < 0) {
6023 cliPrintErrorLinef(cmdName, "PIN TIMER MAP FULL.");
6025 return;
6028 pch = strtok_r(NULL, " ", &saveptr);
6029 if (pch) {
6030 int timerIndex = TIMER_INDEX_UNDEFINED;
6031 if (strcasecmp(pch, "list") == 0) {
6032 /* output the list of available options */
6033 const timerHardware_t *timer;
6034 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
6035 cliPrintLinef("# AF%d: TIM%d CH%d%s",
6036 timer->alternateFunction,
6037 timerGetTIMNumber(timer->tim),
6038 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
6039 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
6043 return;
6044 } else if (strncasecmp(pch, "af", 2) == 0) {
6045 unsigned alternateFunction = atoi(&pch[2]);
6047 const timerHardware_t *timer;
6048 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
6049 if (timer->alternateFunction == alternateFunction) {
6050 timerIndex = index;
6052 break;
6056 if (!timer) {
6057 cliPrintErrorLinef(cmdName, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
6059 return;
6061 } else if (strcasecmp(pch, "none") != 0) {
6062 cliPrintErrorLinef(cmdName, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
6064 return;
6067 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
6068 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
6069 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
6070 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
6072 char optvalString[DMA_OPT_STRING_BUFSIZE];
6073 alternateFunctionToString(ioTag, timerIndex, optvalString);
6075 char orgvalString[DMA_OPT_STRING_BUFSIZE];
6076 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
6078 if (timerIndex == oldTimerIndex) {
6079 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
6080 } else {
6081 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
6084 return;
6085 } else {
6086 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
6088 return;
6091 #endif
6093 #if defined(USE_RESOURCE_MGMT)
6094 static void cliResource(const char *cmdName, char *cmdline)
6096 char *pch = NULL;
6097 char *saveptr;
6099 pch = strtok_r(cmdline, " ", &saveptr);
6100 if (!pch) {
6101 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
6103 return;
6104 } else if (strcasecmp(pch, "show") == 0) {
6105 #ifdef MINIMAL_CLI
6106 cliPrintLine("IO");
6107 #else
6108 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
6109 cliRepeat('-', 20);
6110 #endif
6111 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
6112 const char* owner;
6113 owner = ownerNames[ioRecs[i].owner];
6115 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
6116 if (ioRecs[i].index > 0) {
6117 cliPrintf(" %d", ioRecs[i].index);
6119 cliPrintLinefeed();
6122 pch = strtok_r(NULL, " ", &saveptr);
6123 if (strcasecmp(pch, "all") == 0) {
6124 #if defined(USE_TIMER_MGMT)
6125 cliTimer(cmdName, "show");
6126 #endif
6127 #if defined(USE_DMA)
6128 cliDma(cmdName, "show");
6129 #endif
6132 return;
6135 unsigned resourceIndex = 0;
6136 for (; ; resourceIndex++) {
6137 if (resourceIndex >= ARRAYLEN(resourceTable)) {
6138 cliPrintErrorLinef(cmdName, "INVALID RESOURCE NAME: '%s'", pch);
6139 return;
6142 const char *resourceName = ownerNames[resourceTable[resourceIndex].owner];
6143 if (strcasecmp(pch, resourceName) == 0) {
6144 break;
6148 pch = strtok_r(NULL, " ", &saveptr);
6149 int index = pch ? atoi(pch) : 0;
6151 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
6152 if (index <= 0 || index > RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex)) {
6153 cliShowArgumentRangeError(cmdName, "INDEX", 1, RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex));
6154 return;
6156 index -= 1;
6158 pch = strtok_r(NULL, " ", &saveptr);
6161 ioTag_t *resourceTag = getIoTag(resourceTable[resourceIndex], index);
6163 if (pch && strlen(pch) > 0) {
6164 ioTag_t tag;
6165 if (strToPin(pch, &tag)) {
6166 if (!tag) {
6167 *resourceTag = tag;
6168 #ifdef MINIMAL_CLI
6169 cliPrintLine("Freed");
6170 #else
6171 cliPrintLine("Resource is freed");
6172 #endif
6173 return;
6174 } else {
6175 ioRec_t *rec = IO_Rec(IOGetByTag(tag));
6176 if (rec) {
6177 *resourceTag = tag;
6178 resourceCheck(resourceIndex, index, tag);
6179 #ifdef MINIMAL_CLI
6180 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6181 #else
6182 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6183 #endif
6184 } else {
6185 cliShowParseError(cmdName);
6188 } else {
6189 cliPrintErrorLinef(cmdName, "Failed to parse '%s' as pin", pch);
6191 } else {
6192 ioTag_t tag = *resourceTag;
6193 char ioName[5];
6194 if (tag) {
6195 tfp_sprintf(ioName, "%c%02d", IO_GPIOPortIdxByTag(tag) + 'A', IO_GPIOPinIdxByTag(tag));
6197 cliPrintLinef("# resource %s %d %s", ownerNames[resourceTable[resourceIndex].owner], RESOURCE_INDEX(index), tag ? ioName : "NONE");
6200 #endif
6202 #ifdef USE_DSHOT_TELEMETRY
6204 static void cliDshotTelemetryInfo(const char *cmdName, char *cmdline)
6206 UNUSED(cmdName);
6207 UNUSED(cmdline);
6209 if (useDshotTelemetry) {
6210 cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
6211 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
6212 int32_t directionChangeCycles = cmp32(dshotDMAHandlerCycleCounters.changeDirectionCompletedAt, dshotDMAHandlerCycleCounters.irqAt);
6213 int32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
6214 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
6215 cliPrintLinefeed();
6217 #ifdef USE_DSHOT_TELEMETRY_STATS
6218 cliPrintLine("Motor Type eRPM RPM Hz Invalid TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6219 cliPrintLine("===== ====== ====== ====== ====== ======= ====== ====== ====== ====== ====== ====== ======");
6220 #else
6221 cliPrintLine("Motor Type eRPM RPM Hz TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6222 cliPrintLine("===== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======");
6223 #endif
6225 for (uint8_t i = 0; i < getMotorCount(); i++) {
6226 const uint16_t erpm = getDshotErpm(i);
6227 const uint16_t rpm = lrintf(getDshotRpm(i));
6229 cliPrintf("%5d %c%c%c%c%c %6d %6d %6d",
6230 i + 1,
6231 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_eRPM)) ? 'R' : '-'),
6232 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE)) ? 'T' : '-'),
6233 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_VOLTAGE)) ? 'V' : '-'),
6234 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_CURRENT)) ? 'C' : '-'),
6235 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_STATE_EVENTS)) ? 'S' : '-'),
6236 erpm * 100, rpm, rpm / 60);
6238 #ifdef USE_DSHOT_TELEMETRY_STATS
6239 if (isDshotMotorTelemetryActive(i)) {
6240 int32_t calcPercent = getDshotTelemetryMotorInvalidPercent(i);
6241 cliPrintf(" %3d.%02d%%", calcPercent / 100, calcPercent % 100);
6242 } else {
6243 cliPrint(" NO DATA");
6245 #endif
6247 cliPrintLinef(" %6d %3d.%02d %6d %6d %6d %6d %6d",
6248 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_TEMPERATURE],
6249 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] / 4,
6250 25 * (dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] % 4),
6251 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_CURRENT],
6252 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_STATE_EVENTS],
6253 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG1],
6254 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG2],
6255 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG3]
6258 cliPrintLinefeed();
6260 const int len = MAX_GCR_EDGES;
6261 #ifdef DEBUG_BBDECODE
6262 extern uint16_t bbBuffer[134];
6263 for (int i = 0; i < 134; i++) {
6264 cliPrintf("%u ", (int)bbBuffer[i]);
6266 cliPrintLinefeed();
6267 #endif
6268 for (int i = 0; i < len; i++) {
6269 cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
6271 cliPrintLinefeed();
6272 for (int i = 1; i < len; i++) {
6273 cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
6275 cliPrintLinefeed();
6276 } else {
6277 cliPrintLine("Dshot telemetry not enabled");
6281 #endif
6283 static void printConfig(const char *cmdName, char *cmdline, bool doDiff)
6285 dumpFlags_t dumpMask = DUMP_MASTER;
6286 char *options;
6287 if ((options = checkCommand(cmdline, "master"))) {
6288 dumpMask = DUMP_MASTER; // only
6289 } else if ((options = checkCommand(cmdline, "profile"))) {
6290 dumpMask = DUMP_PROFILE; // only
6291 } else if ((options = checkCommand(cmdline, "rates"))) {
6292 dumpMask = DUMP_RATES; // only
6293 } else if ((options = checkCommand(cmdline, "hardware"))) {
6294 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
6295 } else if ((options = checkCommand(cmdline, "all"))) {
6296 dumpMask = DUMP_ALL; // all profiles and rates
6297 } else {
6298 options = cmdline;
6301 if (doDiff) {
6302 dumpMask = dumpMask | DO_DIFF;
6305 if (checkCommand(options, "defaults")) {
6306 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
6307 } else if (checkCommand(options, "bare")) {
6308 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
6311 backupAndResetConfigs();
6313 #ifdef USE_CLI_BATCH
6314 bool batchModeEnabled = false;
6315 #endif
6316 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
6317 cliPrintHashLine("version");
6318 printVersion(false);
6320 if (!(dumpMask & BARE)) {
6321 #ifdef USE_CLI_BATCH
6322 cliPrintHashLine("start the command batch");
6323 cliPrintLine("batch start");
6324 batchModeEnabled = true;
6325 #endif
6327 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
6328 cliPrintHashLine("reset configuration to default settings");
6329 cliPrintLine("defaults nosave");
6333 #if defined(USE_BOARD_INFO)
6334 cliPrintLinefeed();
6335 printBoardName(dumpMask);
6336 printManufacturerId(dumpMask);
6337 #endif
6339 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
6340 cliMcuId(cmdName, "");
6341 #if defined(USE_SIGNATURE)
6342 cliSignature(cmdName, "");
6343 #endif
6346 if (!(dumpMask & HARDWARE_ONLY)) {
6347 printCraftName(dumpMask, &pilotConfig_Copy);
6350 #ifdef USE_RESOURCE_MGMT
6351 printResource(dumpMask, "resources");
6352 #if defined(USE_TIMER_MGMT)
6353 printTimer(dumpMask, "timer");
6354 #endif
6355 #ifdef USE_DMA_SPEC
6356 printDmaopt(dumpMask, "dma");
6357 #endif
6358 #endif
6360 printFeature(dumpMask, featureConfig_Copy.enabledFeatures, featureConfig()->enabledFeatures, "feature");
6362 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
6364 if (!(dumpMask & HARDWARE_ONLY)) {
6365 #ifndef USE_QUAD_MIXER_ONLY
6366 const char *mixerHeadingStr = "mixer";
6367 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
6368 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
6369 const char *formatMixer = "mixer %s";
6370 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
6371 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
6373 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
6375 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
6377 #ifdef USE_SERVOS
6378 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
6380 const char *servoMixHeadingStr = "servo mixer";
6381 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
6382 cliPrintHashLine(servoMixHeadingStr);
6383 cliPrintLine("smix reset\r\n");
6384 servoMixHeadingStr = NULL;
6386 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
6387 #endif
6388 #endif
6390 #if defined(USE_BEEPER)
6391 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
6393 #if defined(USE_DSHOT)
6394 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
6395 #endif
6396 #endif // USE_BEEPER
6398 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
6400 #ifdef USE_LED_STRIP_STATUS_MODE
6401 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
6403 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
6405 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
6406 #endif
6408 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
6410 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
6412 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
6414 #ifdef USE_VTX_TABLE
6415 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
6416 #endif
6418 #ifdef USE_VTX_CONTROL
6419 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
6420 #endif
6422 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
6425 if (dumpMask & HARDWARE_ONLY) {
6426 dumpAllValues(cmdName, HARDWARE_VALUE, dumpMask, "master");
6427 } else {
6428 dumpAllValues(cmdName, MASTER_VALUE, dumpMask, "master");
6430 if (dumpMask & DUMP_ALL) {
6431 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
6432 cliDumpPidProfile(cmdName, pidProfileIndex, dumpMask);
6435 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
6437 if (!(dumpMask & BARE)) {
6438 cliPrintHashLine("restore original profile selection");
6440 cliProfile(cmdName, "");
6443 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
6445 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
6446 cliDumpRateProfile(cmdName, rateIndex, dumpMask);
6449 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
6451 if (!(dumpMask & BARE)) {
6452 cliPrintHashLine("restore original rateprofile selection");
6454 cliRateProfile(cmdName, "");
6456 cliPrintHashLine("save configuration");
6457 cliPrint("save");
6458 #ifdef USE_CLI_BATCH
6459 batchModeEnabled = false;
6460 #endif
6463 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
6464 } else {
6465 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6467 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6470 } else if (dumpMask & DUMP_PROFILE) {
6471 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6472 } else if (dumpMask & DUMP_RATES) {
6473 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6476 #ifdef USE_CLI_BATCH
6477 if (batchModeEnabled) {
6478 cliPrintHashLine("end the command batch");
6479 cliPrintLine("batch end");
6481 #endif
6483 // restore configs from copies
6484 restoreConfigs(0);
6487 static void cliDump(const char *cmdName, char *cmdline)
6489 printConfig(cmdName, cmdline, false);
6492 static void cliDiff(const char *cmdName, char *cmdline)
6494 printConfig(cmdName, cmdline, true);
6497 #if defined(USE_USB_MSC)
6498 static void cliMsc(const char *cmdName, char *cmdline)
6500 if (mscCheckFilesystemReady()) {
6501 #ifdef USE_RTC_TIME
6502 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
6503 if (!isEmpty(cmdline)) {
6504 timezoneOffsetMinutes = atoi(cmdline);
6505 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
6506 cliPrintErrorLinef(cmdName, "INVALID TIMEZONE OFFSET");
6507 return;
6510 #else
6511 int timezoneOffsetMinutes = 0;
6512 UNUSED(cmdline);
6513 #endif
6514 cliPrintHashLine("Restarting in mass storage mode");
6515 cliPrint("\r\nRebooting");
6516 cliWriterFlush();
6517 waitForSerialPortToFinishTransmitting(cliPort);
6518 motorShutdown();
6520 systemResetToMsc(timezoneOffsetMinutes);
6521 } else {
6522 cliPrintHashLine("Storage not present or failed to initialize!");
6525 #endif
6527 typedef void cliCommandFn(const char* name, char *cmdline);
6529 typedef struct {
6530 const char *name;
6531 #ifndef MINIMAL_CLI
6532 const char *description;
6533 const char *args;
6534 #endif
6535 cliCommandFn *cliCommand;
6536 } clicmd_t;
6538 #ifndef MINIMAL_CLI
6539 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6541 name , \
6542 description , \
6543 args , \
6544 cliCommand \
6546 #else
6547 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6549 name, \
6550 cliCommand \
6552 #endif
6554 static void cliHelp(const char *cmdName, char *cmdline);
6556 // should be sorted a..z for bsearch()
6557 const clicmd_t cmdTable[] = {
6558 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
6559 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
6560 #ifdef USE_CLI_BATCH
6561 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
6562 #endif
6563 #if defined(USE_BEEPER)
6564 #if defined(USE_DSHOT)
6565 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6566 "\t<->[name]", cliBeacon),
6567 #endif
6568 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6569 "\t<->[name]", cliBeeper),
6570 #endif // USE_BEEPER
6571 #if defined(USE_RX_BIND)
6572 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI, SRXL2 or CRSF", NULL, cliRxBind),
6573 #endif
6574 #if defined(USE_FLASH_BOOT_LOADER)
6575 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6576 #else
6577 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6578 #endif
6579 #if defined(USE_BOARD_INFO)
6580 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6581 #endif
6582 #ifdef USE_LED_STRIP_STATUS_MODE
6583 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6584 #endif
6585 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{nosave}", cliDefaults),
6586 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6587 #ifdef USE_RESOURCE_MGMT
6589 #ifdef USE_DMA
6590 #ifdef USE_DMA_SPEC
6591 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6592 #else
6593 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6594 #endif
6595 #endif
6597 #endif
6598 #ifdef USE_DSHOT_TELEMETRY
6599 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6600 #endif
6601 #ifdef USE_DSHOT
6602 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6603 #endif
6604 CLI_COMMAND_DEF("dump", "dump configuration",
6605 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6606 #ifdef USE_ESCSERIAL
6607 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6608 #endif
6609 CLI_COMMAND_DEF("exit", "exit command line interface and reboot (default)", "[noreboot]", cliExitCmd),
6610 CLI_COMMAND_DEF("feature", "configure features",
6611 "list\r\n"
6612 "\t<->[name]", cliFeature),
6613 #ifdef USE_FLASH_CHIP
6614 #ifdef USE_FLASHFS
6615 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6616 #endif
6617 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6618 #if defined(USE_FLASH_TOOLS) && defined(USE_FLASHFS)
6619 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6620 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6621 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6622 #endif
6623 #endif
6624 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6625 #ifdef USE_GPS
6626 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6627 #endif
6628 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6629 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6630 #endif
6631 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp),
6632 #ifdef USE_LED_STRIP_STATUS_MODE
6633 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6634 #endif
6635 #if defined(USE_BOARD_INFO)
6636 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6637 #endif
6638 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6639 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6640 #ifndef USE_QUAD_MIXER_ONLY
6641 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6642 #endif
6643 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6644 #ifdef USE_LED_STRIP_STATUS_MODE
6645 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6646 #endif
6647 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6648 #ifdef USE_USB_MSC
6649 #ifdef USE_RTC_TIME
6650 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6651 #else
6652 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6653 #endif
6654 #endif
6655 #ifndef MINIMAL_CLI
6656 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6657 #endif
6658 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6659 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6660 #ifdef USE_RC_SMOOTHING_FILTER
6661 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6662 #endif // USE_RC_SMOOTHING_FILTER
6663 #ifdef USE_RESOURCE_MGMT
6664 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6665 #endif
6666 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6667 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6668 CLI_COMMAND_DEF("save", "save and reboot (default)", "[noreboot]", cliSave),
6669 #ifdef USE_SDCARD
6670 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6671 #endif
6672 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6673 #if defined(USE_SERIAL_PASSTHROUGH)
6674 #if defined(USE_PINIO)
6675 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),
6676 #else
6677 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6678 #endif
6679 #endif
6680 #ifdef USE_SERVOS
6681 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6682 #endif
6683 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6684 #if defined(USE_SIGNATURE)
6685 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6686 #endif
6687 #if defined(USE_SIMPLIFIED_TUNING)
6688 CLI_COMMAND_DEF("simplified_tuning", "applies or disables simplified tuning", "apply | disable", cliSimplifiedTuning),
6689 #endif
6690 #ifdef USE_SERVOS
6691 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6692 "\treset\r\n"
6693 "\tload <mixer>\r\n"
6694 "\treverse <servo> <source> r|n", cliServoMix),
6695 #endif
6696 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6697 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6698 #ifdef USE_TIMER_MGMT
6699 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6700 #endif
6701 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6702 #ifdef USE_VTX_CONTROL
6703 #ifdef MINIMAL_CLI
6704 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6705 #else
6706 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6707 #endif
6708 #endif
6709 #ifdef USE_VTX_TABLE
6710 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL, cliVtxInfo),
6711 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6712 #endif
6715 static void cliHelp(const char *cmdName, char *cmdline)
6717 bool anyMatches = false;
6719 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6720 bool printEntry = false;
6721 if (isEmpty(cmdline)) {
6722 printEntry = true;
6723 } else {
6724 if (strcasestr(cmdTable[i].name, cmdline)
6725 #ifndef MINIMAL_CLI
6726 || strcasestr(cmdTable[i].description, cmdline)
6727 #endif
6729 printEntry = true;
6733 if (printEntry) {
6734 anyMatches = true;
6735 cliPrint(cmdTable[i].name);
6736 #ifndef MINIMAL_CLI
6737 if (cmdTable[i].description) {
6738 cliPrintf(" - %s", cmdTable[i].description);
6740 if (cmdTable[i].args) {
6741 cliPrintf("\r\n\t%s", cmdTable[i].args);
6743 #endif
6744 cliPrintLinefeed();
6747 if (!isEmpty(cmdline) && !anyMatches) {
6748 cliPrintErrorLinef(cmdName, "NO MATCHES FOR '%s'", cmdline);
6752 static void processCharacter(const char c)
6754 if (bufferIndex && (c == '\n' || c == '\r')) {
6755 if (cliInteractive) {
6756 // echo new line back to terminal
6757 cliPrintLinefeed();
6760 // Strip comment starting with # from line
6761 char *p = cliBuffer;
6762 p = strchr(p, '#');
6763 if (NULL != p) {
6764 bufferIndex = (uint32_t)(p - cliBuffer);
6767 // Strip trailing whitespace
6768 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6769 bufferIndex--;
6772 // Process non-empty lines
6773 if (bufferIndex > 0) {
6774 cliBuffer[bufferIndex] = 0; // null terminate
6776 const clicmd_t *cmd;
6777 char *options;
6778 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6779 if ((options = checkCommand(cliBuffer, cmd->name))) {
6780 break;
6783 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6784 cmd->cliCommand(cmd->name, options);
6785 if (!cliMode) {
6786 // cli session ended
6787 return;
6789 } else {
6790 if (cliInteractive) {
6791 cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
6792 } else {
6793 cliPrint("ERR_CMD_NA: ");
6794 cliPrintLine(cliBuffer);
6799 cliClearInputBuffer();
6801 // prompt if in interactive mode
6802 if (cliInteractive) {
6803 cliPrompt();
6806 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6807 if (!bufferIndex && c == ' ') {
6808 return; // Ignore leading spaces
6810 cliBuffer[bufferIndex++] = c;
6812 // echo the character if interactive
6813 if (cliInteractive) {
6814 cliWrite(c);
6819 static void processCharacterInteractive(const char c)
6821 if (c == '\t' || c == '?') {
6822 // do tab completion
6823 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6824 uint32_t i = bufferIndex;
6825 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6826 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) {
6827 continue;
6829 if (!pstart) {
6830 pstart = cmd;
6832 pend = cmd;
6834 if (pstart) { /* Buffer matches one or more commands */
6835 for (; ; bufferIndex++) {
6836 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6837 break;
6838 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6839 /* Unambiguous -- append a space */
6840 cliBuffer[bufferIndex++] = ' ';
6841 cliBuffer[bufferIndex] = '\0';
6842 break;
6844 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6847 if (!bufferIndex || pstart != pend) {
6848 /* Print list of ambiguous matches */
6849 cliPrint("\r\n\033[K");
6850 for (cmd = pstart; cmd <= pend; cmd++) {
6851 cliPrint(cmd->name);
6852 cliWrite('\t');
6854 cliPrompt();
6855 i = 0; /* Redraw prompt */
6857 for (; i < bufferIndex; i++)
6858 cliWrite(cliBuffer[i]);
6859 } else if (!bufferIndex && c == 4) { // CTRL-D
6860 cliExit(true);
6861 return;
6862 } else if (c == 12) { // NewPage / CTRL-L
6863 // clear screen
6864 cliPrint("\033[2J\033[1;1H");
6865 cliPrompt();
6866 } else if (c == 127) {
6867 // backspace
6868 if (bufferIndex) {
6869 cliBuffer[--bufferIndex] = 0;
6870 cliPrint("\010 \010");
6872 } else {
6873 processCharacter(c);
6877 bool cliProcess(void)
6879 if (!cliWriter || !cliMode) {
6880 return false;
6883 while (serialRxBytesWaiting(cliPort)) {
6884 uint8_t c = serialRead(cliPort);
6885 if (cliInteractive) {
6886 processCharacterInteractive(c);
6887 } else {
6888 // handle terminating flow control character
6889 if (c == 0x3 || (cmp32(millis(), cliEntryTime) > 2000)) { // CTRL-C (ETX) or 2 seconds timeout
6890 cliWrite(0x3); // send end of text, terminating flow control
6891 cliExit(false);
6892 return cliMode;
6894 processCharacter(c);
6897 cliWriterFlush();
6898 return cliMode;
6901 static void cliExit(const bool reboot)
6903 cliWriterFlush();
6904 waitForSerialPortToFinishTransmitting(cliPort);
6905 cliClearInputBuffer();
6906 cliMode = false;
6907 cliInteractive = false;
6908 // incase a motor was left running during motortest, clear it here
6909 mixerResetDisarmedMotors();
6911 if (reboot) {
6912 cliReboot();
6916 void cliEnter(serialPort_t *serialPort, bool interactive)
6918 cliMode = true;
6919 cliInteractive = interactive;
6920 cliPort = serialPort;
6921 cliEntryTime = millis();
6922 cliClearInputBuffer();
6924 if (interactive) {
6925 setPrintfSerialPort(cliPort);
6928 bufWriterInit(&cliWriterDesc, cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6929 cliErrorWriter = cliWriter = &cliWriterDesc;
6931 if (interactive) {
6932 #ifndef MINIMAL_CLI
6933 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to reboot, or 'help'");
6934 #else
6935 cliPrintLine("\r\nCLI");
6936 #endif
6937 // arming flag not released if exiting cli with no reboot for safety
6938 setArmingDisabled(ARMING_DISABLED_CLI);
6939 cliPrompt();
6941 #ifdef USE_CLI_BATCH
6942 resetCommandBatch();
6943 #endif
6944 } else {
6945 cliWrite(0x2); // send start of text, initiating flow control
6949 #endif // USE_CLI