Fix flash defines
[betaflight.git] / src / main / cli / cli.c
blob2720424c5707875af26861fde05265d24f2c28e3
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.h"
74 #include "drivers/compass/compass.h"
75 #include "drivers/display.h"
76 #include "drivers/dma.h"
77 #include "drivers/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/pinio.h"
143 #include "pg/pin_pull_up_down.h"
144 #include "pg/pg.h"
145 #include "pg/pg_ids.h"
146 #include "pg/rx.h"
147 #include "pg/rx_pwm.h"
148 #include "pg/rx_spi_cc2500.h"
149 #include "pg/rx_spi_expresslrs.h"
150 #include "pg/serial_uart.h"
151 #include "pg/sdio.h"
152 #include "pg/timerio.h"
153 #include "pg/timerup.h"
154 #include "pg/usb.h"
155 #include "pg/vtx_table.h"
157 #include "rx/rx_bind.h"
158 #include "rx/rx_spi.h"
160 #include "scheduler/scheduler.h"
162 #include "sensors/acceleration.h"
163 #include "sensors/adcinternal.h"
164 #include "sensors/barometer.h"
165 #include "sensors/battery.h"
166 #include "sensors/boardalignment.h"
167 #include "sensors/compass.h"
168 #include "sensors/gyro.h"
169 #include "sensors/gyro_init.h"
170 #include "sensors/sensors.h"
172 #include "telemetry/frsky_hub.h"
173 #include "telemetry/telemetry.h"
175 #include "cli.h"
177 static serialPort_t *cliPort = NULL;
179 // Space required to set array parameters
180 #define CLI_IN_BUFFER_SIZE 256
181 #define CLI_OUT_BUFFER_SIZE 64
183 static bufWriter_t cliWriterDesc;
184 static bufWriter_t *cliWriter = NULL;
185 static bufWriter_t *cliErrorWriter = NULL;
186 static uint8_t cliWriteBuffer[CLI_OUT_BUFFER_SIZE];
188 static char cliBuffer[CLI_IN_BUFFER_SIZE];
189 static uint32_t bufferIndex = 0;
191 static bool configIsInCopy = false;
193 #define CURRENT_PROFILE_INDEX -1
194 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
195 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
197 #ifdef USE_CLI_BATCH
198 static bool commandBatchActive = false;
199 static bool commandBatchError = false;
200 #endif
202 #if defined(USE_BOARD_INFO)
203 static bool boardInformationUpdated = false;
204 #if defined(USE_SIGNATURE)
205 static bool signatureUpdated = false;
206 #endif
207 #endif // USE_BOARD_INFO
209 static const char* const emptyName = "-";
210 static const char* const emptyString = "";
212 #if !defined(USE_CUSTOM_DEFAULTS)
213 #define CUSTOM_DEFAULTS_START ((char*)0)
214 #define CUSTOM_DEFAULTS_END ((char *)0)
215 #else
216 extern char __custom_defaults_start;
217 extern char __custom_defaults_end;
218 #define CUSTOM_DEFAULTS_START (&__custom_defaults_start)
219 #define CUSTOM_DEFAULTS_END (&__custom_defaults_end)
221 static bool processingCustomDefaults = false;
222 static char cliBufferTemp[CLI_IN_BUFFER_SIZE];
224 #define CUSTOM_DEFAULTS_START_PREFIX ("# " FC_FIRMWARE_NAME)
225 #define CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX "# config: manufacturer_id: "
226 #define CUSTOM_DEFAULTS_BOARD_NAME_PREFIX ", board_name: "
227 #define CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX ", version: "
228 #define CUSTOM_DEFAULTS_DATE_PREFIX ", date: "
230 #define MAX_CHANGESET_ID_LENGTH 8
231 #define MAX_DATE_LENGTH 20
233 static bool customDefaultsHeaderParsed = false;
234 static bool customDefaultsFound = false;
235 static char customDefaultsManufacturerId[MAX_MANUFACTURER_ID_LENGTH + 1] = { 0 };
236 static char customDefaultsBoardName[MAX_BOARD_NAME_LENGTH + 1] = { 0 };
237 static char customDefaultsChangesetId[MAX_CHANGESET_ID_LENGTH + 1] = { 0 };
238 static char customDefaultsDate[MAX_DATE_LENGTH + 1] = { 0 };
239 #endif
241 #if defined(USE_CUSTOM_DEFAULTS_ADDRESS)
242 static char __attribute__ ((section(".custom_defaults_start_address"))) *customDefaultsStart = CUSTOM_DEFAULTS_START;
243 static char __attribute__ ((section(".custom_defaults_end_address"))) *customDefaultsEnd = CUSTOM_DEFAULTS_END;
244 #endif
246 #ifndef USE_QUAD_MIXER_ONLY
247 // sync this with mixerMode_e
248 static const char * const mixerNames[] = {
249 "TRI", "QUADP", "QUADX", "BI",
250 "GIMBAL", "Y6", "HEX6",
251 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
252 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
253 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
254 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
256 #endif
258 // sync this with features_e
259 static const char * const featureNames[] = {
260 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
261 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
262 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
263 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
264 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
265 "", "", "RX_SPI", "", "ESC_SENSOR", "ANTI_GRAVITY", "", NULL
268 // sync this with rxFailsafeChannelMode_e
269 static const char rxFailsafeModeCharacters[] = "ahs";
271 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
272 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
273 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
276 #if defined(USE_SENSOR_NAMES)
277 // sync this with sensors_e
278 static const char *const sensorTypeNames[] = {
279 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
282 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
284 static const char * const *sensorHardwareNames[] = {
285 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
287 #endif // USE_SENSOR_NAMES
289 // Needs to be aligned with mcuTypeId_e
290 static const char *mcuTypeNames[] = {
291 "SIMULATOR",
292 "F40X",
293 "F411",
294 "F446",
295 "F722",
296 "F745",
297 "F746",
298 "F765",
299 "H750",
300 "H743 (Rev Unknown)",
301 "H743 (Rev.Y)",
302 "H743 (Rev.X)",
303 "H743 (Rev.V)",
304 "H7A3",
305 "H723/H725",
306 "G474",
307 "H730",
310 static const char *configurationStates[] = { "UNCONFIGURED", "CUSTOM DEFAULTS", "CONFIGURED" };
312 typedef enum dumpFlags_e {
313 DUMP_MASTER = (1 << 0),
314 DUMP_PROFILE = (1 << 1),
315 DUMP_RATES = (1 << 2),
316 DUMP_ALL = (1 << 3),
317 DO_DIFF = (1 << 4),
318 SHOW_DEFAULTS = (1 << 5),
319 HIDE_UNUSED = (1 << 6),
320 HARDWARE_ONLY = (1 << 7),
321 BARE = (1 << 8),
322 } dumpFlags_t;
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 int id;
334 uint32_t baud;
335 unsigned mode;
336 serialPort_t *port;
337 } serialPassthroughPort_t;
339 static void cliWriterFlushInternal(bufWriter_t *writer)
341 if (writer) {
342 bufWriterFlush(writer);
346 static void cliPrintInternal(bufWriter_t *writer, const char *str)
348 if (writer) {
349 while (*str) {
350 bufWriterAppend(writer, *str++);
352 cliWriterFlushInternal(writer);
356 static void cliWriterFlush(void)
358 cliWriterFlushInternal(cliWriter);
361 void cliPrint(const char *str)
363 cliPrintInternal(cliWriter, str);
366 void cliPrintLinefeed(void)
368 cliPrint("\r\n");
371 void cliPrintLine(const char *str)
373 cliPrint(str);
374 cliPrintLinefeed();
377 #ifdef MINIMAL_CLI
378 #define cliPrintHashLine(str)
379 #else
380 static void cliPrintHashLine(const char *str)
382 cliPrint("\r\n# ");
383 cliPrintLine(str);
385 #endif
387 static void cliPutp(void *p, char ch)
389 bufWriterAppend(p, ch);
392 static void cliPrintfva(const char *format, va_list va)
394 if (cliWriter) {
395 tfp_format(cliWriter, cliPutp, format, va);
396 cliWriterFlush();
400 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
402 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
403 va_list va;
404 va_start(va, format);
405 cliPrintfva(format, va);
406 va_end(va);
407 cliPrintLinefeed();
408 return true;
409 } else {
410 return false;
414 static void cliWrite(uint8_t ch)
416 if (cliWriter) {
417 bufWriterAppend(cliWriter, ch);
421 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
423 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
424 cliWrite('#');
426 va_list va;
427 va_start(va, format);
428 cliPrintfva(format, va);
429 va_end(va);
430 cliPrintLinefeed();
431 return true;
432 } else {
433 return false;
437 void cliPrintf(const char *format, ...)
439 va_list va;
440 va_start(va, format);
441 cliPrintfva(format, va);
442 va_end(va);
446 void cliPrintLinef(const char *format, ...)
448 va_list va;
449 va_start(va, format);
450 cliPrintfva(format, va);
451 va_end(va);
452 cliPrintLinefeed();
455 static void cliPrintErrorVa(const char *cmdName, const char *format, va_list va)
457 if (cliErrorWriter) {
458 cliPrintInternal(cliErrorWriter, "###ERROR IN ");
459 cliPrintInternal(cliErrorWriter, cmdName);
460 cliPrintInternal(cliErrorWriter, ": ");
462 tfp_format(cliErrorWriter, cliPutp, format, va);
463 va_end(va);
465 cliPrintInternal(cliErrorWriter, "###");
468 #ifdef USE_CLI_BATCH
469 if (commandBatchActive) {
470 commandBatchError = true;
472 #endif
475 static void cliPrintError(const char *cmdName, const char *format, ...)
477 va_list va;
478 va_start(va, format);
479 cliPrintErrorVa(cmdName, format, va);
481 if (!cliWriter) {
482 // Supply our own linefeed in case we are printing inside a custom defaults operation
483 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
484 // instead of expecting the directly following command to supply the line feed.
485 cliPrintInternal(cliErrorWriter, "\r\n");
489 static void cliPrintErrorLinef(const char *cmdName, const char *format, ...)
491 va_list va;
492 va_start(va, format);
493 cliPrintErrorVa(cmdName, format, va);
494 cliPrintInternal(cliErrorWriter, "\r\n");
497 static void getMinMax(const clivalue_t *var, int *min, int *max)
499 switch (var->type & VALUE_TYPE_MASK) {
500 case VAR_UINT8:
501 case VAR_UINT16:
502 *min = var->config.minmaxUnsigned.min;
503 *max = var->config.minmaxUnsigned.max;
505 break;
506 default:
507 *min = var->config.minmax.min;
508 *max = var->config.minmax.max;
510 break;
514 static void printValuePointer(const char *cmdName, const clivalue_t *var, const void *valuePointer, bool full)
516 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
517 for (int i = 0; i < var->config.array.length; i++) {
518 switch (var->type & VALUE_TYPE_MASK) {
519 default:
520 case VAR_UINT8:
521 // uint8_t array
522 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
523 break;
525 case VAR_INT8:
526 // int8_t array
527 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
528 break;
530 case VAR_UINT16:
531 // uin16_t array
532 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
533 break;
535 case VAR_INT16:
536 // int16_t array
537 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
538 break;
540 case VAR_UINT32:
541 // uin32_t array
542 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
543 break;
546 if (i < var->config.array.length - 1) {
547 cliPrint(",");
550 } else {
551 int value = 0;
553 switch (var->type & VALUE_TYPE_MASK) {
554 case VAR_UINT8:
555 value = *(uint8_t *)valuePointer;
557 break;
558 case VAR_INT8:
559 value = *(int8_t *)valuePointer;
561 break;
562 case VAR_UINT16:
563 value = *(uint16_t *)valuePointer;
565 break;
566 case VAR_INT16:
567 value = *(int16_t *)valuePointer;
569 break;
570 case VAR_UINT32:
571 value = *(uint32_t *)valuePointer;
573 break;
576 bool valueIsCorrupted = false;
577 switch (var->type & VALUE_MODE_MASK) {
578 case MODE_DIRECT:
579 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
580 cliPrintf("%u", (uint32_t)value);
581 if ((uint32_t)value > var->config.u32Max) {
582 valueIsCorrupted = true;
583 } else if (full) {
584 cliPrintf(" 0 %u", var->config.u32Max);
586 } else {
587 int min;
588 int max;
589 getMinMax(var, &min, &max);
591 cliPrintf("%d", value);
592 if ((value < min) || (value > max)) {
593 valueIsCorrupted = true;
594 } else if (full) {
595 cliPrintf(" %d %d", min, max);
598 break;
599 case MODE_LOOKUP:
600 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
601 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
602 } else {
603 valueIsCorrupted = true;
605 break;
606 case MODE_BITSET:
607 if (value & 1 << var->config.bitpos) {
608 cliPrintf("ON");
609 } else {
610 cliPrintf("OFF");
612 break;
613 case MODE_STRING:
614 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
615 break;
618 if (valueIsCorrupted) {
619 cliPrintLinefeed();
620 cliPrintError(cmdName, "CORRUPTED CONFIG: %s = %d", var->name, value);
626 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
628 bool result = true;
629 int elementCount = 1;
630 uint32_t mask = 0xffffffff;
632 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
633 elementCount = var->config.array.length;
635 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
636 mask = 1 << var->config.bitpos;
638 for (int i = 0; i < elementCount; i++) {
639 switch (var->type & VALUE_TYPE_MASK) {
640 case VAR_UINT8:
641 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
642 break;
644 case VAR_INT8:
645 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
646 break;
648 case VAR_UINT16:
649 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
650 break;
651 case VAR_INT16:
652 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
653 break;
654 case VAR_UINT32:
655 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
656 break;
660 return result;
663 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
665 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
666 cliPrintHashLine(headingStr);
667 return NULL;
668 } else {
669 return headingStr;
673 static void backupPgConfig(const pgRegistry_t *pg)
675 memcpy(pg->copy, pg->address, pg->size);
678 static void restorePgConfig(const pgRegistry_t *pg, uint16_t notToRestoreGroupId)
680 if (!notToRestoreGroupId || pgN(pg) != notToRestoreGroupId) {
681 memcpy(pg->address, pg->copy, pg->size);
685 static void backupConfigs(void)
687 if (configIsInCopy) {
688 return;
691 PG_FOREACH(pg) {
692 backupPgConfig(pg);
695 configIsInCopy = true;
698 static void restoreConfigs(uint16_t notToRestoreGroupId)
700 if (!configIsInCopy) {
701 return;
704 PG_FOREACH(pg) {
705 restorePgConfig(pg, notToRestoreGroupId);
708 configIsInCopy = false;
711 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
712 static bool isReadingConfigFromCopy(void)
714 return configIsInCopy;
716 #endif
718 static bool isWritingConfigToCopy(void)
720 return configIsInCopy
721 #if defined(USE_CUSTOM_DEFAULTS)
722 && !processingCustomDefaults
723 #endif
727 #if defined(USE_CUSTOM_DEFAULTS)
728 static bool cliProcessCustomDefaults(bool quiet);
729 #endif
731 static void backupAndResetConfigs(const bool useCustomDefaults)
733 backupConfigs();
735 // reset all configs to defaults to do differencing
736 resetConfig();
738 #if defined(USE_CUSTOM_DEFAULTS)
739 if (useCustomDefaults) {
740 if (!cliProcessCustomDefaults(true)) {
741 cliPrintLine("###WARNING: NO CUSTOM DEFAULTS FOUND###");
744 #else
745 UNUSED(useCustomDefaults);
746 #endif
749 static uint8_t getPidProfileIndexToUse(void)
751 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
754 static uint8_t getRateProfileIndexToUse(void)
756 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
760 static uint16_t getValueOffset(const clivalue_t *value)
762 switch (value->type & VALUE_SECTION_MASK) {
763 case MASTER_VALUE:
764 case HARDWARE_VALUE:
765 return value->offset;
766 case PROFILE_VALUE:
767 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
768 case PROFILE_RATE_VALUE:
769 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
771 return 0;
774 STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
776 const pgRegistry_t* rec = pgFind(value->pgn);
777 if (isWritingConfigToCopy()) {
778 return CONST_CAST(void *, rec->copy + getValueOffset(value));
779 } else {
780 return CONST_CAST(void *, rec->address + getValueOffset(value));
784 static const char *dumpPgValue(const char *cmdName, const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
786 const pgRegistry_t *pg = pgFind(value->pgn);
787 #ifdef DEBUG
788 if (!pg) {
789 cliPrintLinef("VALUE %s ERROR", value->name);
790 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
792 #endif
794 const char *format = "set %s = ";
795 const char *defaultFormat = "#set %s = ";
796 const int valueOffset = getValueOffset(value);
797 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
799 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
800 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
801 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
802 cliPrintf(defaultFormat, value->name);
803 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
804 cliPrintLinefeed();
806 cliPrintf(format, value->name);
807 printValuePointer(cmdName, value, pg->copy + valueOffset, false);
808 cliPrintLinefeed();
810 return headingStr;
813 static void dumpAllValues(const char *cmdName, uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
815 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
817 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
818 const clivalue_t *value = &valueTable[i];
819 cliWriterFlush();
820 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
821 headingStr = dumpPgValue(cmdName, value, dumpMask, headingStr);
826 static void cliPrintVar(const char *cmdName, const clivalue_t *var, bool full)
828 const void *ptr = cliGetValuePointer(var);
830 printValuePointer(cmdName, var, ptr, full);
833 static void cliPrintVarRange(const clivalue_t *var)
835 switch (var->type & VALUE_MODE_MASK) {
836 case (MODE_DIRECT): {
837 switch (var->type & VALUE_TYPE_MASK) {
838 case VAR_UINT32:
839 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
841 break;
842 case VAR_UINT8:
843 case VAR_UINT16:
844 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
846 break;
847 default:
848 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
850 break;
853 break;
854 case (MODE_LOOKUP): {
855 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
856 cliPrint("Allowed values: ");
857 bool firstEntry = true;
858 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
859 if (tableEntry->values[i]) {
860 if (!firstEntry) {
861 cliPrint(", ");
863 cliPrintf("%s", tableEntry->values[i]);
864 firstEntry = false;
867 cliPrintLinefeed();
869 break;
870 case (MODE_ARRAY): {
871 cliPrintLinef("Array length: %d", var->config.array.length);
873 break;
874 case (MODE_STRING): {
875 cliPrintLinef("String length: %d - %d", var->config.string.minlength, var->config.string.maxlength);
877 break;
878 case (MODE_BITSET): {
879 cliPrintLinef("Allowed values: OFF, ON");
881 break;
885 static void cliSetVar(const clivalue_t *var, const uint32_t value)
887 void *ptr = cliGetValuePointer(var);
888 uint32_t workValue;
889 uint32_t mask;
891 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
892 switch (var->type & VALUE_TYPE_MASK) {
893 case VAR_UINT8:
894 mask = (1 << var->config.bitpos) & 0xff;
895 if (value) {
896 workValue = *(uint8_t *)ptr | mask;
897 } else {
898 workValue = *(uint8_t *)ptr & ~mask;
900 *(uint8_t *)ptr = workValue;
901 break;
903 case VAR_UINT16:
904 mask = (1 << var->config.bitpos) & 0xffff;
905 if (value) {
906 workValue = *(uint16_t *)ptr | mask;
907 } else {
908 workValue = *(uint16_t *)ptr & ~mask;
910 *(uint16_t *)ptr = workValue;
911 break;
913 case VAR_UINT32:
914 mask = 1 << var->config.bitpos;
915 if (value) {
916 workValue = *(uint32_t *)ptr | mask;
917 } else {
918 workValue = *(uint32_t *)ptr & ~mask;
920 *(uint32_t *)ptr = workValue;
921 break;
923 } else {
924 switch (var->type & VALUE_TYPE_MASK) {
925 case VAR_UINT8:
926 *(uint8_t *)ptr = value;
927 break;
929 case VAR_INT8:
930 *(int8_t *)ptr = value;
931 break;
933 case VAR_UINT16:
934 *(uint16_t *)ptr = value;
935 break;
937 case VAR_INT16:
938 *(int16_t *)ptr = value;
939 break;
941 case VAR_UINT32:
942 *(uint32_t *)ptr = value;
943 break;
948 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
949 static void cliRepeat(char ch, uint8_t len)
951 if (cliWriter) {
952 for (int i = 0; i < len; i++) {
953 bufWriterAppend(cliWriter, ch);
955 cliPrintLinefeed();
958 #endif
960 static void cliPrompt(void)
962 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
963 if (processingCustomDefaults) {
964 cliPrint("\r\nd: #");
965 } else
966 #endif
968 cliPrint("\r\n# ");
972 static void cliShowParseError(const char *cmdName)
974 cliPrintErrorLinef(cmdName, "PARSING FAILED");
977 static void cliShowInvalidArgumentCountError(const char *cmdName)
979 cliPrintErrorLinef(cmdName, "INVALID ARGUMENT COUNT", cmdName);
982 static void cliShowArgumentRangeError(const char *cmdName, char *name, int min, int max)
984 if (name) {
985 cliPrintErrorLinef(cmdName, "%s NOT BETWEEN %d AND %d", name, min, max);
986 } else {
987 cliPrintErrorLinef(cmdName, "ARGUMENT OUT OF RANGE");
991 static const char *nextArg(const char *currentArg)
993 const char *ptr = strchr(currentArg, ' ');
994 while (ptr && *ptr == ' ') {
995 ptr++;
998 return ptr;
1001 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
1003 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
1004 ptr = nextArg(ptr);
1005 if (ptr) {
1006 int val = atoi(ptr);
1007 val = CHANNEL_VALUE_TO_STEP(val);
1008 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
1009 if (argIndex == 0) {
1010 range->startStep = val;
1011 } else {
1012 range->endStep = val;
1014 (*validArgumentCount)++;
1019 return ptr;
1022 // Check if a string's length is zero
1023 static bool isEmpty(const char *string)
1025 return (string == NULL || *string == '\0') ? true : false;
1028 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
1030 // print out rxConfig failsafe settings
1031 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1032 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1033 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
1034 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
1035 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
1036 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1037 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1038 if (requireValue) {
1039 const char *format = "rxfail %u %c %d";
1040 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1041 channel,
1042 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
1043 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
1045 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1046 channel,
1047 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
1048 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1050 } else {
1051 const char *format = "rxfail %u %c";
1052 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1053 channel,
1054 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
1056 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1057 channel,
1058 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
1064 static void cliRxFailsafe(const char *cmdName, char *cmdline)
1066 uint8_t channel;
1067 char buf[3];
1069 if (isEmpty(cmdline)) {
1070 // print out rxConfig failsafe settings
1071 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1072 cliRxFailsafe(cmdName, itoa(channel, buf, 10));
1074 } else {
1075 const char *ptr = cmdline;
1076 channel = atoi(ptr++);
1077 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
1079 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
1081 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
1082 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
1083 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1085 ptr = nextArg(ptr);
1086 if (ptr) {
1087 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
1088 if (p) {
1089 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
1090 mode = rxFailsafeModesTable[type][requestedMode];
1091 } else {
1092 mode = RX_FAILSAFE_MODE_INVALID;
1094 if (mode == RX_FAILSAFE_MODE_INVALID) {
1095 cliShowParseError(cmdName);
1096 return;
1099 requireValue = mode == RX_FAILSAFE_MODE_SET;
1101 ptr = nextArg(ptr);
1102 if (ptr) {
1103 if (!requireValue) {
1104 cliShowParseError(cmdName);
1105 return;
1107 uint16_t value = atoi(ptr);
1108 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
1109 if (value > MAX_RXFAIL_RANGE_STEP) {
1110 cliPrintErrorLinef(cmdName, "value out of range: %d", value);
1111 return;
1114 channelFailsafeConfig->step = value;
1115 } else if (requireValue) {
1116 cliShowInvalidArgumentCountError(cmdName);
1117 return;
1119 channelFailsafeConfig->mode = mode;
1122 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
1124 // double use of cliPrintf below
1125 // 1. acknowledge interpretation on command,
1126 // 2. query current setting on single item,
1128 if (requireValue) {
1129 cliPrintLinef("rxfail %u %c %d",
1130 channel,
1131 modeCharacter,
1132 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1134 } else {
1135 cliPrintLinef("rxfail %u %c",
1136 channel,
1137 modeCharacter
1140 } else {
1141 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1146 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1148 const char *format = "aux %u %u %u %u %u %u %u";
1149 // print out aux channel settings
1150 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1151 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1152 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1153 bool equalsDefault = false;
1154 if (defaultModeActivationConditions) {
1155 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1156 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1157 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1158 const box_t *box = findBoxByBoxId(macDefault->modeId);
1159 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1160 if (box) {
1161 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1163 box->permanentId,
1164 macDefault->auxChannelIndex,
1165 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1166 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1167 macDefault->modeLogic,
1168 linkedTo ? linkedTo->permanentId : 0
1172 const box_t *box = findBoxByBoxId(mac->modeId);
1173 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1174 if (box) {
1175 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1177 box->permanentId,
1178 mac->auxChannelIndex,
1179 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1180 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1181 mac->modeLogic,
1182 linkedTo ? linkedTo->permanentId : 0
1188 static void cliAux(const char *cmdName, char *cmdline)
1190 int i, val = 0;
1191 const char *ptr;
1193 if (isEmpty(cmdline)) {
1194 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1195 } else {
1196 ptr = cmdline;
1197 i = atoi(ptr++);
1198 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1199 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1200 uint8_t validArgumentCount = 0;
1201 ptr = nextArg(ptr);
1202 if (ptr) {
1203 val = atoi(ptr);
1204 const box_t *box = findBoxByPermanentId(val);
1205 if (box) {
1206 mac->modeId = box->boxId;
1207 validArgumentCount++;
1210 ptr = nextArg(ptr);
1211 if (ptr) {
1212 val = atoi(ptr);
1213 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1214 mac->auxChannelIndex = val;
1215 validArgumentCount++;
1218 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1219 ptr = nextArg(ptr);
1220 if (ptr) {
1221 val = atoi(ptr);
1222 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1223 mac->modeLogic = val;
1224 validArgumentCount++;
1227 ptr = nextArg(ptr);
1228 if (ptr) {
1229 val = atoi(ptr);
1230 const box_t *box = findBoxByPermanentId(val);
1231 if (box) {
1232 mac->linkedTo = box->boxId;
1233 validArgumentCount++;
1236 if (validArgumentCount == 4) { // for backwards compatibility
1237 mac->modeLogic = MODELOGIC_OR;
1238 mac->linkedTo = 0;
1239 } else if (validArgumentCount == 5) { // for backwards compatibility
1240 mac->linkedTo = 0;
1241 } else if (validArgumentCount != 6) {
1242 memset(mac, 0, sizeof(modeActivationCondition_t));
1244 analyzeModeActivationConditions();
1245 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1247 findBoxByBoxId(mac->modeId)->permanentId,
1248 mac->auxChannelIndex,
1249 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1250 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1251 mac->modeLogic,
1252 findBoxByBoxId(mac->linkedTo)->permanentId
1254 } else {
1255 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1260 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1262 const char *format = "serial %d %d %ld %ld %ld %ld";
1263 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1264 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
1265 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1266 continue;
1268 bool equalsDefault = false;
1269 if (serialConfigDefault) {
1270 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1271 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1272 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1273 serialConfigDefault->portConfigs[i].identifier,
1274 serialConfigDefault->portConfigs[i].functionMask,
1275 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1276 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1277 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1278 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1281 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1282 serialConfig->portConfigs[i].identifier,
1283 serialConfig->portConfigs[i].functionMask,
1284 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1285 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1286 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1287 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1292 static void cliSerial(const char *cmdName, char *cmdline)
1294 const char *format = "serial %d %d %ld %ld %ld %ld";
1295 if (isEmpty(cmdline)) {
1296 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1297 return;
1299 serialPortConfig_t portConfig;
1300 memset(&portConfig, 0 , sizeof(portConfig));
1303 uint8_t validArgumentCount = 0;
1305 const char *ptr = cmdline;
1307 int val = atoi(ptr++);
1308 serialPortConfig_t *currentConfig = serialFindPortConfigurationMutable(val);
1310 if (currentConfig) {
1311 portConfig.identifier = val;
1312 validArgumentCount++;
1315 ptr = nextArg(ptr);
1316 if (ptr) {
1317 val = strtoul(ptr, NULL, 10);
1318 portConfig.functionMask = val;
1319 validArgumentCount++;
1322 for (int i = 0; i < 4; i ++) {
1323 ptr = nextArg(ptr);
1324 if (!ptr) {
1325 break;
1328 val = atoi(ptr);
1330 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1331 if (baudRates[baudRateIndex] != (uint32_t) val) {
1332 break;
1335 switch (i) {
1336 case 0:
1337 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1338 continue;
1340 portConfig.msp_baudrateIndex = baudRateIndex;
1341 break;
1342 case 1:
1343 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1344 continue;
1346 portConfig.gps_baudrateIndex = baudRateIndex;
1347 break;
1348 case 2:
1349 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1350 continue;
1352 portConfig.telemetry_baudrateIndex = baudRateIndex;
1353 break;
1354 case 3:
1355 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1356 continue;
1358 portConfig.blackbox_baudrateIndex = baudRateIndex;
1359 break;
1362 validArgumentCount++;
1365 if (validArgumentCount < 6) {
1366 cliShowInvalidArgumentCountError(cmdName);
1367 return;
1370 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1372 cliDumpPrintLinef(0, false, format,
1373 portConfig.identifier,
1374 portConfig.functionMask,
1375 baudRates[portConfig.msp_baudrateIndex],
1376 baudRates[portConfig.gps_baudrateIndex],
1377 baudRates[portConfig.telemetry_baudrateIndex],
1378 baudRates[portConfig.blackbox_baudrateIndex]
1383 #if defined(USE_SERIAL_PASSTHROUGH)
1384 static void cbCtrlLine(void *context, uint16_t ctrl)
1386 #ifdef USE_PINIO
1387 int contextValue = (int)(long)context;
1388 if (contextValue) {
1389 pinioSet(contextValue - 1, !(ctrl & CTRL_LINE_STATE_DTR));
1390 } else
1391 #endif /* USE_PINIO */
1392 UNUSED(context);
1394 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1395 systemReset();
1399 static int cliParseSerialMode(const char *tok)
1401 int mode = 0;
1403 if (strcasestr(tok, "rx")) {
1404 mode |= MODE_RX;
1406 if (strcasestr(tok, "tx")) {
1407 mode |= MODE_TX;
1410 return mode;
1413 static void cliSerialPassthrough(const char *cmdName, char *cmdline)
1415 if (isEmpty(cmdline)) {
1416 cliShowInvalidArgumentCountError(cmdName);
1417 return;
1420 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, NULL}, {cliPort->identifier, 0, 0, cliPort} };
1421 bool enableBaudCb = false;
1422 int port1PinioDtr = 0;
1423 bool port1ResetOnDtr = false;
1424 bool escSensorPassthrough = false;
1425 char *saveptr;
1426 char* tok = strtok_r(cmdline, " ", &saveptr);
1427 int index = 0;
1429 while (tok != NULL) {
1430 switch (index) {
1431 case 0:
1432 if (strcasestr(tok, "esc_sensor")) {
1433 escSensorPassthrough = true;
1434 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1435 ports[0].id = portConfig->identifier;
1436 } else {
1437 ports[0].id = atoi(tok);
1439 break;
1440 case 1:
1441 ports[0].baud = atoi(tok);
1442 break;
1443 case 2:
1444 ports[0].mode = cliParseSerialMode(tok);
1445 break;
1446 case 3:
1447 if (strncasecmp(tok, "reset", strlen(tok)) == 0) {
1448 port1ResetOnDtr = true;
1449 #ifdef USE_PINIO
1450 } else if (strncasecmp(tok, "none", strlen(tok)) == 0) {
1451 port1PinioDtr = 0;
1452 } else {
1453 port1PinioDtr = atoi(tok);
1454 if (port1PinioDtr < 0 || port1PinioDtr > PINIO_COUNT) {
1455 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1456 return ;
1458 #endif /* USE_PINIO */
1460 break;
1461 case 4:
1462 ports[1].id = atoi(tok);
1463 ports[1].port = NULL;
1464 break;
1465 case 5:
1466 ports[1].baud = atoi(tok);
1467 break;
1468 case 6:
1469 ports[1].mode = cliParseSerialMode(tok);
1470 break;
1472 index++;
1473 tok = strtok_r(NULL, " ", &saveptr);
1476 // Port checks
1477 if (ports[0].id == ports[1].id) {
1478 cliPrintLinef("Port1 and port2 are same");
1479 return ;
1482 for (int i = 0; i < 2; i++) {
1483 if (findSerialPortIndexByIdentifier(ports[i].id) == -1) {
1484 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1485 return ;
1486 } else {
1487 cliPrintLinef("Port%d: %d ", i + 1, ports[i].id);
1491 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1492 enableBaudCb = true;
1495 for (int i = 0; i < 2; i++) {
1496 serialPort_t **port = &(ports[i].port);
1497 if (*port != NULL) {
1498 continue;
1501 int portIndex = i + 1;
1502 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(ports[i].id);
1503 if (!portUsage || portUsage->serialPort == NULL) {
1504 bool isUseDefaultBaud = false;
1505 if (ports[i].baud == 0) {
1506 // Set default baud
1507 ports[i].baud = 57600;
1508 isUseDefaultBaud = true;
1511 if (!ports[i].mode) {
1512 ports[i].mode = MODE_RXTX;
1515 *port = openSerialPort(ports[i].id, FUNCTION_NONE, NULL, NULL,
1516 ports[i].baud, ports[i].mode,
1517 SERIAL_NOT_INVERTED);
1518 if (!*port) {
1519 cliPrintLinef("Port%d could not be opened.", portIndex);
1520 return;
1523 if (isUseDefaultBaud) {
1524 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex, ports[i].baud);
1525 } else {
1526 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex, ports[i].baud);
1528 } else {
1529 *port = portUsage->serialPort;
1530 // If the user supplied a mode, override the port's mode, otherwise
1531 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1532 // Set the baud rate if specified
1533 if (ports[i].baud) {
1534 cliPrintf("Port%d is already open, setting baud = %d.\r\n", portIndex, ports[i].baud);
1535 serialSetBaudRate(*port, ports[i].baud);
1536 } else {
1537 cliPrintf("Port%d is already open, baud = %d.\r\n", portIndex, (*port)->baudRate);
1540 if (ports[i].mode && (*port)->mode != ports[i].mode) {
1541 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1542 portIndex, (*port)->mode, ports[i].mode);
1543 serialSetMode(*port, ports[i].mode);
1546 // If this port has a rx callback associated we need to remove it now.
1547 // Otherwise no data will be pushed in the serial port buffer!
1548 if ((*port)->rxCallback) {
1549 (*port)->rxCallback = NULL;
1554 // If no baud rate is specified allow to be set via USB
1555 if (enableBaudCb) {
1556 cliPrintLine("Port1 baud rate change over USB enabled.");
1557 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1558 // baud rate over USB without setting it using the serialpassthrough command
1559 serialSetBaudRateCb(ports[1].port, serialSetBaudRate, ports[0].port);
1562 char *resetMessage = "";
1563 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1564 resetMessage = "or drop DTR ";
1567 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1569 if ((ports[1].id == SERIAL_PORT_USB_VCP) && (port1ResetOnDtr
1570 #ifdef USE_PINIO
1571 || port1PinioDtr
1572 #endif /* USE_PINIO */
1573 )) {
1574 // Register control line state callback
1575 serialSetCtrlLineStateCb(ports[0].port, cbCtrlLine, (void *)(intptr_t)(port1PinioDtr));
1578 // XXX Review ESC pass through under refactored motor handling
1579 #ifdef USE_PWM_OUTPUT
1580 if (escSensorPassthrough) {
1581 // pwmDisableMotors();
1582 motorDisable();
1583 delay(5);
1584 for (unsigned i = 0; i < getMotorCount(); i++) {
1585 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1586 if (tag) {
1587 const timerHardware_t *timerHardware = timerGetConfiguredByTag(tag);
1588 if (timerHardware) {
1589 IO_t io = IOGetByTag(tag);
1590 IOInit(io, OWNER_MOTOR, 0);
1591 IOConfigGPIO(io, IOCFG_OUT_PP);
1592 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1593 IOLo(io);
1594 } else {
1595 IOHi(io);
1601 #endif
1603 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1605 #endif
1607 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1609 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1610 // print out adjustment ranges channel settings
1611 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1612 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1613 const adjustmentRange_t *ar = &adjustmentRanges[i];
1614 bool equalsDefault = false;
1615 if (defaultAdjustmentRanges) {
1616 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1617 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1618 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1619 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1621 arDefault->auxChannelIndex,
1622 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1623 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1624 arDefault->adjustmentConfig,
1625 arDefault->auxSwitchChannelIndex,
1626 arDefault->adjustmentCenter,
1627 arDefault->adjustmentScale
1630 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1632 ar->auxChannelIndex,
1633 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1634 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1635 ar->adjustmentConfig,
1636 ar->auxSwitchChannelIndex,
1637 ar->adjustmentCenter,
1638 ar->adjustmentScale
1643 static void cliAdjustmentRange(const char *cmdName, char *cmdline)
1645 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1646 int i, val = 0;
1647 const char *ptr;
1649 if (isEmpty(cmdline)) {
1650 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1651 } else {
1652 ptr = cmdline;
1653 i = atoi(ptr++);
1654 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1655 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1656 uint8_t validArgumentCount = 0;
1658 ptr = nextArg(ptr);
1659 if (ptr) {
1660 val = atoi(ptr);
1661 // Was: slot
1662 // Keeping the parameter to retain backwards compatibility for the command format.
1663 validArgumentCount++;
1665 ptr = nextArg(ptr);
1666 if (ptr) {
1667 val = atoi(ptr);
1668 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1669 ar->auxChannelIndex = val;
1670 validArgumentCount++;
1674 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1676 ptr = nextArg(ptr);
1677 if (ptr) {
1678 val = atoi(ptr);
1679 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1680 ar->adjustmentConfig = val;
1681 validArgumentCount++;
1684 ptr = nextArg(ptr);
1685 if (ptr) {
1686 val = atoi(ptr);
1687 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1688 ar->auxSwitchChannelIndex = val;
1689 validArgumentCount++;
1693 if (validArgumentCount != 6) {
1694 memset(ar, 0, sizeof(adjustmentRange_t));
1695 cliShowInvalidArgumentCountError(cmdName);
1696 return;
1699 // Optional arguments
1700 ar->adjustmentCenter = 0;
1701 ar->adjustmentScale = 0;
1703 ptr = nextArg(ptr);
1704 if (ptr) {
1705 val = atoi(ptr);
1706 ar->adjustmentCenter = val;
1707 validArgumentCount++;
1709 ptr = nextArg(ptr);
1710 if (ptr) {
1711 val = atoi(ptr);
1712 ar->adjustmentScale = val;
1713 validArgumentCount++;
1716 activeAdjustmentRangeReset();
1718 cliDumpPrintLinef(0, false, format,
1720 ar->auxChannelIndex,
1721 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1722 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1723 ar->adjustmentConfig,
1724 ar->auxSwitchChannelIndex,
1725 ar->adjustmentCenter,
1726 ar->adjustmentScale
1729 } else {
1730 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1735 #ifndef USE_QUAD_MIXER_ONLY
1736 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1738 const char *format = "mmix %d %s %s %s %s";
1739 char buf0[FTOA_BUFFER_LENGTH];
1740 char buf1[FTOA_BUFFER_LENGTH];
1741 char buf2[FTOA_BUFFER_LENGTH];
1742 char buf3[FTOA_BUFFER_LENGTH];
1743 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1744 if (customMotorMixer[i].throttle == 0.0f)
1745 break;
1746 const float thr = customMotorMixer[i].throttle;
1747 const float roll = customMotorMixer[i].roll;
1748 const float pitch = customMotorMixer[i].pitch;
1749 const float yaw = customMotorMixer[i].yaw;
1750 bool equalsDefault = false;
1751 if (defaultCustomMotorMixer) {
1752 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1753 const float rollDefault = defaultCustomMotorMixer[i].roll;
1754 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1755 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1756 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1758 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1759 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1761 ftoa(thrDefault, buf0),
1762 ftoa(rollDefault, buf1),
1763 ftoa(pitchDefault, buf2),
1764 ftoa(yawDefault, buf3));
1766 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1768 ftoa(thr, buf0),
1769 ftoa(roll, buf1),
1770 ftoa(pitch, buf2),
1771 ftoa(yaw, buf3));
1774 #endif // USE_QUAD_MIXER_ONLY
1776 static void cliMotorMix(const char *cmdName, char *cmdline)
1778 #ifdef USE_QUAD_MIXER_ONLY
1779 UNUSED(cmdName);
1780 UNUSED(cmdline);
1781 #else
1782 int check = 0;
1783 uint8_t len;
1784 const char *ptr;
1786 if (isEmpty(cmdline)) {
1787 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1788 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1789 // erase custom mixer
1790 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1791 customMotorMixerMutable(i)->throttle = 0.0f;
1793 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1794 ptr = nextArg(cmdline);
1795 if (ptr) {
1796 len = strlen(ptr);
1797 for (uint32_t i = 0; ; i++) {
1798 if (mixerNames[i] == NULL) {
1799 cliPrintErrorLinef(cmdName, "INVALID NAME");
1800 break;
1802 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1803 mixerLoadMix(i, customMotorMixerMutable(0));
1804 cliPrintLinef("Loaded %s", mixerNames[i]);
1805 cliMotorMix(cmdName, "");
1806 break;
1810 } else {
1811 ptr = cmdline;
1812 uint32_t i = atoi(ptr); // get motor number
1813 if (i < MAX_SUPPORTED_MOTORS) {
1814 ptr = nextArg(ptr);
1815 if (ptr) {
1816 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1817 check++;
1819 ptr = nextArg(ptr);
1820 if (ptr) {
1821 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1822 check++;
1824 ptr = nextArg(ptr);
1825 if (ptr) {
1826 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1827 check++;
1829 ptr = nextArg(ptr);
1830 if (ptr) {
1831 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1832 check++;
1834 if (check != 4) {
1835 cliShowInvalidArgumentCountError(cmdName);
1836 } else {
1837 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1839 } else {
1840 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1843 #endif
1846 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1848 const char *format = "rxrange %u %u %u";
1849 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1850 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1851 bool equalsDefault = false;
1852 if (defaultChannelRangeConfigs) {
1853 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1854 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1855 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1857 defaultChannelRangeConfigs[i].min,
1858 defaultChannelRangeConfigs[i].max
1861 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1863 channelRangeConfigs[i].min,
1864 channelRangeConfigs[i].max
1869 static void cliRxRange(const char *cmdName, char *cmdline)
1871 const char *format = "rxrange %u %u %u";
1872 int i, validArgumentCount = 0;
1873 const char *ptr;
1875 if (isEmpty(cmdline)) {
1876 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1877 } else if (strcasecmp(cmdline, "reset") == 0) {
1878 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1879 } else {
1880 ptr = cmdline;
1881 i = atoi(ptr);
1882 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1883 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1885 ptr = nextArg(ptr);
1886 if (ptr) {
1887 rangeMin = atoi(ptr);
1888 validArgumentCount++;
1891 ptr = nextArg(ptr);
1892 if (ptr) {
1893 rangeMax = atoi(ptr);
1894 validArgumentCount++;
1897 if (validArgumentCount != 2) {
1898 cliShowInvalidArgumentCountError(cmdName);
1899 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1900 cliShowArgumentRangeError(cmdName, "range min/max", PWM_PULSE_MIN, PWM_PULSE_MAX);
1901 } else {
1902 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1903 channelRangeConfig->min = rangeMin;
1904 channelRangeConfig->max = rangeMax;
1905 cliDumpPrintLinef(0, false, format,
1907 channelRangeConfig->min,
1908 channelRangeConfig->max
1912 } else {
1913 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1918 #ifdef USE_LED_STRIP_STATUS_MODE
1919 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1921 const char *format = "led %u %s";
1922 char ledConfigBuffer[20];
1923 char ledConfigDefaultBuffer[20];
1924 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1925 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1926 ledConfig_t ledConfig = ledConfigs[i];
1927 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1928 bool equalsDefault = false;
1929 if (defaultLedConfigs) {
1930 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1931 equalsDefault = ledConfig == ledConfigDefault;
1932 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1933 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1934 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1936 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1940 static void cliLed(const char *cmdName, char *cmdline)
1942 const char *format = "led %u %s";
1943 char ledConfigBuffer[20];
1944 int i;
1945 const char *ptr;
1947 if (isEmpty(cmdline)) {
1948 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
1949 } else {
1950 ptr = cmdline;
1951 i = atoi(ptr);
1952 if (i >= 0 && i < LED_MAX_STRIP_LENGTH) {
1953 ptr = nextArg(cmdline);
1954 if (parseLedStripConfig(i, ptr)) {
1955 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1956 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1957 } else {
1958 cliShowParseError(cmdName);
1960 } else {
1961 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_MAX_STRIP_LENGTH - 1);
1966 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
1968 const char *format = "color %u %d,%u,%u";
1969 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1970 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1971 const hsvColor_t *color = &colors[i];
1972 bool equalsDefault = false;
1973 if (defaultColors) {
1974 const hsvColor_t *colorDefault = &defaultColors[i];
1975 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1976 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1977 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1979 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1983 static void cliColor(const char *cmdName, char *cmdline)
1985 const char *format = "color %u %d,%u,%u";
1986 if (isEmpty(cmdline)) {
1987 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
1988 } else {
1989 const char *ptr = cmdline;
1990 const int i = atoi(ptr);
1991 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1992 ptr = nextArg(cmdline);
1993 if (parseColor(i, ptr)) {
1994 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
1995 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1996 } else {
1997 cliShowParseError(cmdName);
1999 } else {
2000 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
2005 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
2007 const char *format = "mode_color %u %u %u";
2008 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2009 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
2010 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
2011 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
2012 bool equalsDefault = false;
2013 if (defaultLedStripConfig) {
2014 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
2015 equalsDefault = colorIndex == colorIndexDefault;
2016 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2017 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
2019 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
2023 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
2024 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
2025 bool equalsDefault = false;
2026 if (defaultLedStripConfig) {
2027 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
2028 equalsDefault = colorIndex == colorIndexDefault;
2029 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2030 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
2032 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
2035 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
2036 bool equalsDefault = false;
2037 if (defaultLedStripConfig) {
2038 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
2039 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
2040 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2041 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
2043 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
2046 static void cliModeColor(const char *cmdName, char *cmdline)
2048 if (isEmpty(cmdline)) {
2049 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
2050 } else {
2051 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
2052 int args[ARGS_COUNT];
2053 int argNo = 0;
2054 char *saveptr;
2055 const char* ptr = strtok_r(cmdline, " ", &saveptr);
2056 while (ptr && argNo < ARGS_COUNT) {
2057 args[argNo++] = atoi(ptr);
2058 ptr = strtok_r(NULL, " ", &saveptr);
2061 if (ptr != NULL || argNo != ARGS_COUNT) {
2062 cliShowInvalidArgumentCountError(cmdName);
2063 return;
2066 int modeIdx = args[MODE];
2067 int funIdx = args[FUNCTION];
2068 int color = args[COLOR];
2069 if (!setModeColor(modeIdx, funIdx, color)) {
2070 cliShowParseError(cmdName);
2071 return;
2073 // values are validated
2074 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
2077 #endif
2079 #ifdef USE_SERVOS
2080 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
2082 // print out servo settings
2083 const char *format = "servo %u %d %d %d %d %d";
2084 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2085 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2086 const servoParam_t *servoConf = &servoParams[i];
2087 bool equalsDefault = false;
2088 if (defaultServoParams) {
2089 const servoParam_t *defaultServoConf = &defaultServoParams[i];
2090 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
2091 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2092 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2094 defaultServoConf->min,
2095 defaultServoConf->max,
2096 defaultServoConf->middle,
2097 defaultServoConf->rate,
2098 defaultServoConf->forwardFromChannel
2101 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2103 servoConf->min,
2104 servoConf->max,
2105 servoConf->middle,
2106 servoConf->rate,
2107 servoConf->forwardFromChannel
2110 // print servo directions
2111 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2112 const char *format = "smix reverse %d %d r";
2113 const servoParam_t *servoConf = &servoParams[i];
2114 const servoParam_t *servoConfDefault = &defaultServoParams[i];
2115 if (defaultServoParams) {
2116 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
2117 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2118 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
2119 if (servoConfDefault->reversedSources & (1 << channel)) {
2120 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
2122 if (servoConf->reversedSources & (1 << channel)) {
2123 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
2126 } else {
2127 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2128 if (servoConf->reversedSources & (1 << channel)) {
2129 cliDumpPrintLinef(dumpMask, true, format, i , channel);
2136 static void cliServo(const char *cmdName, char *cmdline)
2138 const char *format = "servo %u %d %d %d %d %d";
2139 enum { SERVO_ARGUMENT_COUNT = 6 };
2140 int16_t arguments[SERVO_ARGUMENT_COUNT];
2142 servoParam_t *servo;
2144 int i;
2145 char *ptr;
2147 if (isEmpty(cmdline)) {
2148 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2149 } else {
2150 int validArgumentCount = 0;
2152 ptr = cmdline;
2154 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2156 // If command line doesn't fit the format, don't modify the config
2157 while (*ptr) {
2158 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2159 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2160 cliShowInvalidArgumentCountError(cmdName);
2161 return;
2164 arguments[validArgumentCount++] = atoi(ptr);
2166 do {
2167 ptr++;
2168 } while (*ptr >= '0' && *ptr <= '9');
2169 } else if (*ptr == ' ') {
2170 ptr++;
2171 } else {
2172 cliShowParseError(cmdName);
2173 return;
2177 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2179 i = arguments[INDEX];
2181 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2182 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2183 cliShowInvalidArgumentCountError(cmdName);
2184 return;
2187 servo = servoParamsMutable(i);
2189 if (
2190 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
2191 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
2192 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2193 arguments[MIN] > arguments[MAX] ||
2194 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2195 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2197 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2198 return;
2201 servo->min = arguments[MIN];
2202 servo->max = arguments[MAX];
2203 servo->middle = arguments[MIDDLE];
2204 servo->rate = arguments[RATE];
2205 servo->forwardFromChannel = arguments[FORWARD];
2207 cliDumpPrintLinef(0, false, format,
2209 servo->min,
2210 servo->max,
2211 servo->middle,
2212 servo->rate,
2213 servo->forwardFromChannel
2218 #endif
2220 #ifdef USE_SERVOS
2221 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2223 const char *format = "smix %d %d %d %d %d %d %d %d";
2224 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2225 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2226 const servoMixer_t customServoMixer = customServoMixers[i];
2227 if (customServoMixer.rate == 0) {
2228 break;
2231 bool equalsDefault = false;
2232 if (defaultCustomServoMixers) {
2233 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2234 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2236 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2237 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2239 customServoMixerDefault.targetChannel,
2240 customServoMixerDefault.inputSource,
2241 customServoMixerDefault.rate,
2242 customServoMixerDefault.speed,
2243 customServoMixerDefault.min,
2244 customServoMixerDefault.max,
2245 customServoMixerDefault.box
2248 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2250 customServoMixer.targetChannel,
2251 customServoMixer.inputSource,
2252 customServoMixer.rate,
2253 customServoMixer.speed,
2254 customServoMixer.min,
2255 customServoMixer.max,
2256 customServoMixer.box
2261 static void cliServoMix(const char *cmdName, char *cmdline)
2263 int args[8], check = 0;
2264 int len = strlen(cmdline);
2266 if (len == 0) {
2267 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2268 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2269 // erase custom mixer
2270 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2271 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2272 servoParamsMutable(i)->reversedSources = 0;
2274 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2275 const char *ptr = nextArg(cmdline);
2276 if (ptr) {
2277 len = strlen(ptr);
2278 for (uint32_t i = 0; ; i++) {
2279 if (mixerNames[i] == NULL) {
2280 cliPrintErrorLinef(cmdName, "INVALID NAME");
2281 break;
2283 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2284 servoMixerLoadMix(i);
2285 cliPrintLinef("Loaded %s", mixerNames[i]);
2286 cliServoMix(cmdName, "");
2287 break;
2291 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2292 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2293 char *ptr = strchr(cmdline, ' ');
2295 if (ptr == NULL) {
2296 cliPrintf("s");
2297 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2298 cliPrintf("\ti%d", inputSource);
2299 cliPrintLinefeed();
2301 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2302 cliPrintf("%d", servoIndex);
2303 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2304 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2306 cliPrintLinefeed();
2308 return;
2311 char *saveptr;
2312 ptr = strtok_r(ptr, " ", &saveptr);
2313 while (ptr != NULL && check < ARGS_COUNT - 1) {
2314 args[check++] = atoi(ptr);
2315 ptr = strtok_r(NULL, " ", &saveptr);
2318 if (ptr == NULL || check != ARGS_COUNT - 1) {
2319 cliShowInvalidArgumentCountError(cmdName);
2320 return;
2323 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2324 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2325 && (*ptr == 'r' || *ptr == 'n')) {
2326 if (*ptr == 'r') {
2327 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2328 } else {
2329 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2331 } else {
2332 cliShowArgumentRangeError(cmdName, "servo", 0, MAX_SUPPORTED_SERVOS);
2333 return;
2336 cliServoMix(cmdName, "reverse");
2337 } else {
2338 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2339 char *saveptr;
2340 char *ptr = strtok_r(cmdline, " ", &saveptr);
2341 while (ptr != NULL && check < ARGS_COUNT) {
2342 args[check++] = atoi(ptr);
2343 ptr = strtok_r(NULL, " ", &saveptr);
2346 if (ptr != NULL || check != ARGS_COUNT) {
2347 cliShowInvalidArgumentCountError(cmdName);
2348 return;
2351 int32_t i = args[RULE];
2352 if (i >= 0 && i < MAX_SERVO_RULES &&
2353 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2354 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2355 args[RATE] >= -100 && args[RATE] <= 100 &&
2356 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2357 args[MIN] >= 0 && args[MIN] <= 100 &&
2358 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2359 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2360 customServoMixersMutable(i)->targetChannel = args[TARGET];
2361 customServoMixersMutable(i)->inputSource = args[INPUT];
2362 customServoMixersMutable(i)->rate = args[RATE];
2363 customServoMixersMutable(i)->speed = args[SPEED];
2364 customServoMixersMutable(i)->min = args[MIN];
2365 customServoMixersMutable(i)->max = args[MAX];
2366 customServoMixersMutable(i)->box = args[BOX];
2367 cliServoMix(cmdName, "");
2368 } else {
2369 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2373 #endif
2375 #ifdef USE_SDCARD
2377 static void cliWriteBytes(const uint8_t *buffer, int count)
2379 while (count > 0) {
2380 cliWrite(*buffer);
2381 buffer++;
2382 count--;
2386 static void cliSdInfo(const char *cmdName, char *cmdline)
2388 UNUSED(cmdName);
2389 UNUSED(cmdline);
2391 cliPrint("SD card: ");
2393 if (sdcardConfig()->mode == SDCARD_MODE_NONE) {
2394 cliPrintLine("Not configured");
2396 return;
2399 if (!sdcard_isInserted()) {
2400 cliPrintLine("None inserted");
2401 return;
2404 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2405 cliPrintLine("Startup failed");
2406 return;
2409 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2411 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2412 metadata->manufacturerID,
2413 metadata->numBlocks / 2, /* One block is half a kB */
2414 metadata->productionMonth,
2415 metadata->productionYear,
2416 metadata->productRevisionMajor,
2417 metadata->productRevisionMinor
2420 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2422 cliPrint("'\r\n" "Filesystem: ");
2424 switch (afatfs_getFilesystemState()) {
2425 case AFATFS_FILESYSTEM_STATE_READY:
2426 cliPrint("Ready");
2427 break;
2428 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2429 cliPrint("Initializing");
2430 break;
2431 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2432 case AFATFS_FILESYSTEM_STATE_FATAL:
2433 cliPrint("Fatal");
2435 switch (afatfs_getLastError()) {
2436 case AFATFS_ERROR_BAD_MBR:
2437 cliPrint(" - no FAT MBR partitions");
2438 break;
2439 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2440 cliPrint(" - bad FAT header");
2441 break;
2442 case AFATFS_ERROR_GENERIC:
2443 case AFATFS_ERROR_NONE:
2444 ; // Nothing more detailed to print
2445 break;
2447 break;
2449 cliPrintLinefeed();
2452 #endif
2454 #ifdef USE_FLASH_CHIP
2456 static void cliFlashInfo(const char *cmdName, char *cmdline)
2458 UNUSED(cmdName);
2459 UNUSED(cmdline);
2461 const flashGeometry_t *layout = flashGetGeometry();
2463 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2464 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2466 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2467 const flashPartition_t *partition;
2468 if (index == 0) {
2469 cliPrintLine("Paritions:");
2471 partition = flashPartitionFindByIndex(index);
2472 if (!partition) {
2473 break;
2475 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2477 #ifdef USE_FLASHFS
2478 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2480 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2481 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2482 flashfsGetOffset()
2484 #endif
2487 #ifdef USE_FLASH_TOOLS
2488 static void cliFlashErase(const char *cmdName, char *cmdline)
2490 UNUSED(cmdName);
2491 UNUSED(cmdline);
2493 if (!flashfsIsSupported()) {
2494 return;
2497 #ifndef MINIMAL_CLI
2498 uint32_t i = 0;
2499 cliPrintLine("Erasing, please wait ... ");
2500 #else
2501 cliPrintLine("Erasing,");
2502 #endif
2504 cliWriterFlush();
2505 flashfsEraseCompletely();
2507 while (!flashfsIsReady()) {
2508 #ifndef MINIMAL_CLI
2509 cliPrintf(".");
2510 if (i++ > 120) {
2511 i=0;
2512 cliPrintLinefeed();
2515 cliWriterFlush();
2516 #endif
2517 delay(100);
2519 beeper(BEEPER_BLACKBOX_ERASE);
2520 cliPrintLinefeed();
2521 cliPrintLine("Done.");
2524 static void cliFlashVerify(const char *cmdName, char *cmdline)
2526 UNUSED(cmdline);
2528 cliPrintLine("Verifying");
2529 if (flashfsVerifyEntireFlash()) {
2530 cliPrintLine("Success");
2531 } else {
2532 cliPrintErrorLinef(cmdName, "Failed");
2536 static void cliFlashWrite(const char *cmdName, char *cmdline)
2538 const uint32_t address = atoi(cmdline);
2539 const char *text = strchr(cmdline, ' ');
2541 if (!text) {
2542 cliShowInvalidArgumentCountError(cmdName);
2543 } else {
2544 flashfsSeekAbs(address);
2545 flashfsWrite((uint8_t*)text, strlen(text), true);
2546 flashfsFlushSync();
2548 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2552 static void cliFlashRead(const char *cmdName, char *cmdline)
2554 uint32_t address = atoi(cmdline);
2556 const char *nextArg = strchr(cmdline, ' ');
2558 if (!nextArg) {
2559 cliShowInvalidArgumentCountError(cmdName);
2560 } else {
2561 uint32_t length = atoi(nextArg);
2563 cliPrintLinef("Reading %u bytes at %u:", length, address);
2565 uint8_t buffer[32];
2566 while (length > 0) {
2567 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2569 for (int i = 0; i < bytesRead; i++) {
2570 cliWrite(buffer[i]);
2573 length -= bytesRead;
2574 address += bytesRead;
2576 if (bytesRead == 0) {
2577 //Assume we reached the end of the volume or something fatal happened
2578 break;
2581 cliPrintLinefeed();
2584 #endif
2585 #endif
2587 #ifdef USE_VTX_CONTROL
2588 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2590 // print out vtx channel settings
2591 const char *format = "vtx %u %u %u %u %u %u %u";
2592 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2593 bool equalsDefault = false;
2594 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2595 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2596 if (vtxConfigDefault) {
2597 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2598 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2599 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2600 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2602 cacDefault->auxChannelIndex,
2603 cacDefault->band,
2604 cacDefault->channel,
2605 cacDefault->power,
2606 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2607 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2610 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2612 cac->auxChannelIndex,
2613 cac->band,
2614 cac->channel,
2615 cac->power,
2616 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2617 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2622 static void cliVtx(const char *cmdName, char *cmdline)
2624 const char *format = "vtx %u %u %u %u %u %u %u";
2625 int i, val = 0;
2626 const char *ptr;
2628 if (isEmpty(cmdline)) {
2629 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2630 } else {
2631 #ifdef USE_VTX_TABLE
2632 const uint8_t maxBandIndex = vtxTableConfig()->bands;
2633 const uint8_t maxChannelIndex = vtxTableConfig()->channels;
2634 const uint8_t maxPowerIndex = vtxTableConfig()->powerLevels;
2635 #else
2636 const uint8_t maxBandIndex = VTX_TABLE_MAX_BANDS;
2637 const uint8_t maxChannelIndex = VTX_TABLE_MAX_CHANNELS;
2638 const uint8_t maxPowerIndex = VTX_TABLE_MAX_POWER_LEVELS;
2639 #endif
2640 ptr = cmdline;
2641 i = atoi(ptr++);
2642 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2643 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2644 uint8_t validArgumentCount = 0;
2645 ptr = nextArg(ptr);
2646 if (ptr) {
2647 val = atoi(ptr);
2648 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2649 cac->auxChannelIndex = val;
2650 validArgumentCount++;
2653 ptr = nextArg(ptr);
2654 if (ptr) {
2655 val = atoi(ptr);
2656 if (val >= 0 && val <= maxBandIndex) {
2657 cac->band = val;
2658 validArgumentCount++;
2661 ptr = nextArg(ptr);
2662 if (ptr) {
2663 val = atoi(ptr);
2664 if (val >= 0 && val <= maxChannelIndex) {
2665 cac->channel = val;
2666 validArgumentCount++;
2669 ptr = nextArg(ptr);
2670 if (ptr) {
2671 val = atoi(ptr);
2672 if (val >= 0 && val <= maxPowerIndex) {
2673 cac->power= val;
2674 validArgumentCount++;
2677 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2679 if (validArgumentCount != 6) {
2680 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2681 cliShowInvalidArgumentCountError(cmdName);
2682 } else {
2683 cliDumpPrintLinef(0, false, format,
2685 cac->auxChannelIndex,
2686 cac->band,
2687 cac->channel,
2688 cac->power,
2689 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2690 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2693 } else {
2694 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2699 #endif // VTX_CONTROL
2701 #ifdef USE_VTX_TABLE
2703 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2705 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2706 char freqtmp[5 + 1];
2707 freqbuf[0] = 0;
2708 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2709 for (int channel = 0; channel < channels; channel++) {
2710 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2711 strcat(freqbuf, freqtmp);
2713 return freqbuf;
2716 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2718 char *fmt = "vtxtable band %d %s %c%s";
2719 bool equalsDefault = false;
2721 if (defaultConfig) {
2722 equalsDefault = true;
2723 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2724 equalsDefault = false;
2726 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2727 equalsDefault = false;
2729 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2730 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2731 equalsDefault = false;
2734 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2735 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2736 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2739 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2740 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2741 return headingStr;
2744 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2746 // (max 4 digit + 1 space) per level
2747 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2748 char pwrtmp[5 + 1];
2749 pwrbuf[0] = 0;
2750 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2751 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2752 strcat(pwrbuf, pwrtmp);
2754 return pwrbuf;
2757 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2759 char *fmt = "vtxtable powervalues%s";
2760 bool equalsDefault = false;
2761 if (defaultConfig) {
2762 equalsDefault = true;
2763 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2764 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2765 equalsDefault = false;
2768 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2769 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2770 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2773 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2774 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2775 return headingStr;
2778 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2780 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2781 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2782 pwrbuf[0] = 0;
2783 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2784 strcat(pwrbuf, " ");
2785 strcpy(pwrtmp, labels[pwrindex]);
2786 // trim trailing space
2787 char *sp;
2788 while ((sp = strchr(pwrtmp, ' '))) {
2789 *sp = 0;
2791 strcat(pwrbuf, pwrtmp);
2793 return pwrbuf;
2796 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2798 char *fmt = "vtxtable powerlabels%s";
2799 bool equalsDefault = false;
2800 if (defaultConfig) {
2801 equalsDefault = true;
2802 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2803 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2804 equalsDefault = false;
2807 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2808 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2809 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2812 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2813 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2814 return headingStr;
2817 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2819 bool equalsDefault;
2820 char *fmt;
2822 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2824 // bands
2825 equalsDefault = false;
2826 fmt = "vtxtable bands %d";
2827 if (defaultConfig) {
2828 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2829 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2830 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2832 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2834 // channels
2835 equalsDefault = false;
2836 fmt = "vtxtable channels %d";
2837 if (defaultConfig) {
2838 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2839 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2840 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2842 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2844 // band
2846 for (int band = 0; band < currentConfig->bands; band++) {
2847 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2850 // powerlevels
2852 equalsDefault = false;
2853 fmt = "vtxtable powerlevels %d";
2854 if (defaultConfig) {
2855 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2856 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2857 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2859 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2861 // powervalues
2863 // powerlabels
2864 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2865 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2868 static void cliVtxTable(const char *cmdName, char *cmdline)
2870 char *tok;
2871 char *saveptr;
2873 // Band number or nothing
2874 tok = strtok_r(cmdline, " ", &saveptr);
2876 if (!tok) {
2877 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2878 return;
2881 if (strcasecmp(tok, "bands") == 0) {
2882 tok = strtok_r(NULL, " ", &saveptr);
2883 int bands = atoi(tok);
2884 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2885 cliShowArgumentRangeError(cmdName, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS);
2886 return;
2888 if (bands < vtxTableConfigMutable()->bands) {
2889 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2890 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2893 vtxTableConfigMutable()->bands = bands;
2895 } else if (strcasecmp(tok, "channels") == 0) {
2896 tok = strtok_r(NULL, " ", &saveptr);
2898 int channels = atoi(tok);
2899 if (channels < 0 || channels > VTX_TABLE_MAX_CHANNELS) {
2900 cliShowArgumentRangeError(cmdName, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS);
2901 return;
2903 if (channels < vtxTableConfigMutable()->channels) {
2904 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2905 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2908 vtxTableConfigMutable()->channels = channels;
2910 } else if (strcasecmp(tok, "powerlevels") == 0) {
2911 // Number of power levels
2912 tok = strtok_r(NULL, " ", &saveptr);
2913 if (tok) {
2914 int levels = atoi(tok);
2915 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2916 cliShowArgumentRangeError(cmdName, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS);
2917 } else {
2918 if (levels < vtxTableConfigMutable()->powerLevels) {
2919 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2920 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2922 vtxTableConfigMutable()->powerLevels = levels;
2924 } else {
2925 // XXX Show current level count?
2927 return;
2929 } else if (strcasecmp(tok, "powervalues") == 0) {
2930 // Power values
2931 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2932 int count;
2933 int levels = vtxTableConfigMutable()->powerLevels;
2935 memset(power, 0, sizeof(power));
2937 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2938 int value = atoi(tok);
2939 power[count] = value;
2942 // Check remaining tokens
2944 if (count < levels) {
2945 cliPrintErrorLinef(cmdName, "NOT ENOUGH VALUES (EXPECTED %d)", levels);
2946 return;
2947 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2948 cliPrintErrorLinef(cmdName, "TOO MANY VALUES (EXPECTED %d)", levels);
2949 return;
2952 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
2953 vtxTableConfigMutable()->powerValues[i] = power[i];
2956 } else if (strcasecmp(tok, "powerlabels") == 0) {
2957 // Power labels
2958 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
2959 int levels = vtxTableConfigMutable()->powerLevels;
2960 int count;
2961 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2962 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
2963 for (unsigned i = 0; i < strlen(label[count]); i++) {
2964 label[count][i] = toupper(label[count][i]);
2968 // Check remaining tokens
2970 if (count < levels) {
2971 cliPrintErrorLinef(cmdName, "NOT ENOUGH LABELS (EXPECTED %d)", levels);
2972 return;
2973 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2974 cliPrintErrorLinef(cmdName, "TOO MANY LABELS (EXPECTED %d)", levels);
2975 return;
2978 for (int i = 0; i < count; i++) {
2979 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
2981 } else if (strcasecmp(tok, "band") == 0) {
2983 int bands = vtxTableConfigMutable()->bands;
2985 tok = strtok_r(NULL, " ", &saveptr);
2986 if (!tok) {
2987 return;
2990 int band = atoi(tok);
2991 --band;
2993 if (band < 0 || band >= bands) {
2994 cliShowArgumentRangeError(cmdName, "BAND NUMBER", 1, bands);
2995 return;
2998 // Band name
2999 tok = strtok_r(NULL, " ", &saveptr);
3001 if (!tok) {
3002 return;
3005 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
3006 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
3007 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
3008 for (unsigned i = 0; i < strlen(bandname); i++) {
3009 bandname[i] = toupper(bandname[i]);
3012 // Band letter
3013 tok = strtok_r(NULL, " ", &saveptr);
3015 if (!tok) {
3016 return;
3019 char bandletter = toupper(tok[0]);
3021 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
3022 int channel = 0;
3023 int channels = vtxTableConfigMutable()->channels;
3024 bool isFactory = false;
3026 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
3027 if (channel == 0 && !isdigit(tok[0])) {
3028 channel -= 1;
3029 if (strcasecmp(tok, "FACTORY") == 0) {
3030 isFactory = true;
3031 } else if (strcasecmp(tok, "CUSTOM") == 0) {
3032 isFactory = false;
3033 } else {
3034 cliPrintErrorLinef(cmdName, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
3035 return;
3038 int freq = atoi(tok);
3039 if (freq < 0) {
3040 cliPrintErrorLinef(cmdName, "INVALID FREQUENCY %s", tok);
3041 return;
3043 bandfreq[channel] = freq;
3046 if (channel < channels) {
3047 cliPrintErrorLinef(cmdName, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
3048 return;
3049 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3050 cliPrintErrorLinef(cmdName, "TOO MANY FREQUENCIES (EXPECTED %d)", channels);
3051 return;
3054 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
3055 vtxTableConfigMutable()->bandLetters[band] = bandletter;
3057 for (int i = 0; i < channel; i++) {
3058 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
3060 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
3061 } else {
3062 // Bad subcommand
3063 cliPrintErrorLinef(cmdName, "INVALID SUBCOMMAND %s", tok);
3067 static void cliVtxInfo(const char *cmdName, char *cmdline)
3069 UNUSED(cmdline);
3071 // Display the available power levels
3072 uint16_t levels[VTX_TABLE_MAX_POWER_LEVELS];
3073 uint16_t powers[VTX_TABLE_MAX_POWER_LEVELS];
3074 vtxDevice_t *vtxDevice = vtxCommonDevice();
3075 if (vtxDevice) {
3076 uint8_t level_count = vtxCommonGetVTXPowerLevels(vtxDevice, levels, powers);
3078 if (level_count) {
3079 for (int i = 0; i < level_count; i++) {
3080 cliPrintLinef("level %d dBm, power %d mW", levels[i], powers[i]);
3082 } else {
3083 cliPrintErrorLinef(cmdName, "NO POWER VALUES DEFINED");
3085 } else {
3086 cliPrintErrorLinef(cmdName, "NO VTX");
3089 #endif // USE_VTX_TABLE
3091 #if defined(USE_SIMPLIFIED_TUNING)
3092 static void applySimplifiedTuningAllProfiles(void)
3094 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3095 applySimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3099 static void cliSimplifiedTuning(const char *cmdName, char *cmdline)
3101 if (strcasecmp(cmdline, "apply") == 0) {
3102 applySimplifiedTuningAllProfiles();
3104 cliPrintLine("Applied simplified tuning.");
3105 } else if (strcasecmp(cmdline, "disable") == 0) {
3106 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3107 disableSimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3110 cliPrintLine("Disabled simplified tuning.");
3111 } else {
3112 cliShowParseError(cmdName);
3115 #endif
3117 static void printCraftName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
3119 const bool equalsDefault = strlen(pilotConfig->craftName) == 0;
3120 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->craftName);
3123 #if defined(USE_BOARD_INFO)
3125 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
3127 static void printBoardName(dumpFlags_t dumpMask)
3129 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
3130 cliPrintLinef("board_name %s", getBoardName());
3134 static void cliBoardName(const char *cmdName, char *cmdline)
3136 const unsigned int len = strlen(cmdline);
3137 const char *boardName = getBoardName();
3138 if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
3139 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "BOARD_NAME", boardName);
3140 } else {
3141 if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
3142 boardInformationUpdated = true;
3144 cliPrintHashLine("Set board_name.");
3146 printBoardName(DUMP_ALL);
3150 static void printManufacturerId(dumpFlags_t dumpMask)
3152 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
3153 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3157 static void cliManufacturerId(const char *cmdName, char *cmdline)
3159 const unsigned int len = strlen(cmdline);
3160 const char *manufacturerId = getManufacturerId();
3161 if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
3162 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
3163 } else {
3164 if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
3165 boardInformationUpdated = true;
3167 cliPrintHashLine("Set manufacturer_id.");
3169 printManufacturerId(DUMP_ALL);
3173 #if defined(USE_SIGNATURE)
3174 static void writeSignature(char *signatureStr, uint8_t *signature)
3176 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3177 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
3181 static void cliSignature(const char *cmdName, char *cmdline)
3183 const int len = strlen(cmdline);
3185 uint8_t signature[SIGNATURE_LENGTH] = {0};
3186 if (len > 0) {
3187 if (len != 2 * SIGNATURE_LENGTH) {
3188 cliPrintErrorLinef(cmdName, "INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
3190 return;
3193 #define BLOCK_SIZE 2
3194 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3195 char temp[BLOCK_SIZE + 1];
3196 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3197 temp[BLOCK_SIZE] = '\0';
3198 char *end;
3199 unsigned result = strtoul(temp, &end, 16);
3200 if (end == &temp[BLOCK_SIZE]) {
3201 signature[i] = result;
3202 } else {
3203 cliPrintErrorLinef(cmdName, "INVALID CHARACTER FOUND: %c", end[0]);
3205 return;
3208 #undef BLOCK_SIZE
3211 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3212 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3213 writeSignature(signatureStr, getSignature());
3214 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "SIGNATURE", signatureStr);
3215 } else {
3216 if (len > 0 && !configIsInCopy && setSignature(signature)) {
3217 signatureUpdated = true;
3219 writeSignature(signatureStr, getSignature());
3221 cliPrintHashLine("Set signature.");
3222 } else if (signatureUpdated || signatureIsSet()) {
3223 writeSignature(signatureStr, getSignature());
3226 cliPrintLinef("signature %s", signatureStr);
3229 #endif
3231 #undef ERROR_MESSAGE
3233 #endif // USE_BOARD_INFO
3235 static void cliMcuId(const char *cmdName, char *cmdline)
3237 UNUSED(cmdName);
3238 UNUSED(cmdline);
3240 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3243 static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
3245 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3246 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
3247 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3248 const char *format = "feature -%s";
3249 const bool equalsDefault = (~defaultMask | mask) & (1 << i);
3250 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3251 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
3252 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3255 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
3256 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3257 const char *format = "feature %s";
3258 if (defaultMask & (1 << i)) {
3259 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
3261 if (mask & (1 << i)) {
3262 const bool equalsDefault = (defaultMask | ~mask) & (1 << i);
3263 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3264 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3270 static void cliFeature(const char *cmdName, char *cmdline)
3272 uint32_t len = strlen(cmdline);
3273 const uint32_t mask = featureConfig()->enabledFeatures;
3274 if (len == 0) {
3275 cliPrint("Enabled: ");
3276 for (uint32_t i = 0; ; i++) {
3277 if (featureNames[i] == NULL) {
3278 break;
3280 if (mask & (1 << i)) {
3281 cliPrintf("%s ", featureNames[i]);
3284 cliPrintLinefeed();
3285 } else if (strncasecmp(cmdline, "list", len) == 0) {
3286 cliPrint("Available:");
3287 for (uint32_t i = 0; ; i++) {
3288 if (featureNames[i] == NULL)
3289 break;
3290 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
3291 cliPrintf(" %s", featureNames[i]);
3293 cliPrintLinefeed();
3294 return;
3295 } else {
3296 uint32_t feature;
3298 bool remove = false;
3299 if (cmdline[0] == '-') {
3300 // remove feature
3301 remove = true;
3302 cmdline++; // skip over -
3303 len--;
3306 for (uint32_t i = 0; ; i++) {
3307 if (featureNames[i] == NULL) {
3308 cliPrintErrorLinef(cmdName, "INVALID NAME");
3309 break;
3312 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
3313 feature = 1 << i;
3314 #ifndef USE_GPS
3315 if (feature & FEATURE_GPS) {
3316 cliPrintLine("unavailable");
3317 break;
3319 #endif
3320 #ifndef USE_RANGEFINDER
3321 if (feature & FEATURE_RANGEFINDER) {
3322 cliPrintLine("unavailable");
3323 break;
3325 #endif
3326 if (remove) {
3327 featureConfigClear(feature);
3328 cliPrint("Disabled");
3329 } else {
3330 featureConfigSet(feature);
3331 cliPrint("Enabled");
3333 cliPrintLinef(" %s", featureNames[i]);
3334 break;
3340 #if defined(USE_BEEPER)
3341 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3343 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3344 const uint8_t beeperCount = beeperTableEntryCount();
3345 for (int32_t i = 0; i < beeperCount - 1; i++) {
3346 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3347 const char *formatOff = "%s -%s";
3348 const char *formatOn = "%s %s";
3349 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3350 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3351 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3352 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3353 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3358 static void processBeeperCommand(const char *cmdName, char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3360 uint32_t len = strlen(cmdline);
3361 uint8_t beeperCount = beeperTableEntryCount();
3363 if (len == 0) {
3364 cliPrintf("Disabled:");
3365 for (int32_t i = 0; ; i++) {
3366 if (i == beeperCount - 1) {
3367 if (*offFlags == 0)
3368 cliPrint(" none");
3369 break;
3372 if (beeperModeMaskForTableIndex(i) & *offFlags)
3373 cliPrintf(" %s", beeperNameForTableIndex(i));
3375 cliPrintLinefeed();
3376 } else if (strncasecmp(cmdline, "list", len) == 0) {
3377 cliPrint("Available:");
3378 for (uint32_t i = 0; i < beeperCount; i++) {
3379 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3380 cliPrintf(" %s", beeperNameForTableIndex(i));
3383 cliPrintLinefeed();
3384 } else {
3385 bool remove = false;
3386 if (cmdline[0] == '-') {
3387 remove = true; // this is for beeper OFF condition
3388 cmdline++;
3389 len--;
3392 for (uint32_t i = 0; ; i++) {
3393 if (i == beeperCount) {
3394 cliPrintErrorLinef(cmdName, "INVALID NAME");
3395 break;
3397 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3398 if (remove) { // beeper off
3399 if (i == BEEPER_ALL - 1) {
3400 *offFlags = allowedFlags;
3401 } else {
3402 *offFlags |= beeperModeMaskForTableIndex(i);
3404 cliPrint("Disabled");
3406 else { // beeper on
3407 if (i == BEEPER_ALL - 1) {
3408 *offFlags = 0;
3409 } else {
3410 *offFlags &= ~beeperModeMaskForTableIndex(i);
3412 cliPrint("Enabled");
3414 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3415 break;
3421 #if defined(USE_DSHOT)
3422 static void cliBeacon(const char *cmdName, char *cmdline)
3424 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3426 #endif
3428 static void cliBeeper(const char *cmdName, char *cmdline)
3430 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3432 #endif
3434 #if defined(USE_RX_BIND)
3435 static void cliRxBind(const char *cmdName, char *cmdline)
3437 UNUSED(cmdline);
3438 if (!startRxBind()) {
3439 cliPrintErrorLinef(cmdName, "Not supported.");
3440 } else {
3441 cliPrintLinef("Binding...");
3444 #endif
3446 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3448 bool equalsDefault = true;
3449 char buf[16];
3450 char bufDefault[16];
3451 uint32_t i;
3453 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3454 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3455 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3456 if (defaultRxConfig) {
3457 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3458 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3461 buf[i] = '\0';
3463 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3464 const char *formatMap = "map %s";
3465 if (defaultRxConfig) {
3466 bufDefault[i] = '\0';
3467 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3469 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3473 static void cliMap(const char *cmdName, char *cmdline)
3475 uint32_t i;
3476 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3478 uint32_t len = strlen(cmdline);
3479 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3481 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3482 buf[i] = toupper((unsigned char)cmdline[i]);
3484 buf[i] = '\0';
3486 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3487 buf[i] = toupper((unsigned char)cmdline[i]);
3489 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3490 continue;
3492 cliShowParseError(cmdName);
3493 return;
3495 parseRcChannels(buf, rxConfigMutable());
3496 } else if (len > 0) {
3497 cliShowInvalidArgumentCountError(cmdName);
3498 return;
3501 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3502 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3505 buf[i] = '\0';
3506 cliPrintLinef("map %s", buf);
3509 static char *skipSpace(char *buffer)
3511 while (*(buffer) == ' ') {
3512 buffer++;
3515 return buffer;
3518 static char *checkCommand(char *cmdline, const char *command)
3520 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3521 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3522 return skipSpace(cmdline + strlen(command) + 1);
3523 } else {
3524 return 0;
3528 static void cliRebootEx(rebootTarget_e rebootTarget)
3530 cliPrint("\r\nRebooting");
3531 cliWriterFlush();
3532 waitForSerialPortToFinishTransmitting(cliPort);
3533 motorShutdown();
3535 switch (rebootTarget) {
3536 case REBOOT_TARGET_BOOTLOADER_ROM:
3537 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3539 break;
3540 #if defined(USE_FLASH_BOOT_LOADER)
3541 case REBOOT_TARGET_BOOTLOADER_FLASH:
3542 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3544 break;
3545 #endif
3546 case REBOOT_TARGET_FIRMWARE:
3547 default:
3548 systemReset();
3550 break;
3554 static void cliReboot(void)
3556 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3559 static void cliBootloader(const char *cmdName, char *cmdline)
3561 rebootTarget_e rebootTarget;
3562 if (
3563 #if !defined(USE_FLASH_BOOT_LOADER)
3564 isEmpty(cmdline) ||
3565 #endif
3566 strncasecmp(cmdline, "rom", 3) == 0) {
3567 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3569 cliPrintHashLine("restarting in ROM bootloader mode");
3570 #if defined(USE_FLASH_BOOT_LOADER)
3571 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3572 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3574 cliPrintHashLine("restarting in flash bootloader mode");
3575 #endif
3576 } else {
3577 cliPrintErrorLinef(cmdName, "Invalid option");
3579 return;
3582 cliRebootEx(rebootTarget);
3585 static void cliExit(const char *cmdName, char *cmdline)
3587 UNUSED(cmdName);
3588 UNUSED(cmdline);
3590 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3591 cliWriterFlush();
3593 *cliBuffer = '\0';
3594 bufferIndex = 0;
3595 cliMode = false;
3596 // incase a motor was left running during motortest, clear it here
3597 mixerResetDisarmedMotors();
3598 cliReboot();
3601 #ifdef USE_GPS
3602 static void cliGpsPassthrough(const char *cmdName, char *cmdline)
3604 UNUSED(cmdName);
3605 UNUSED(cmdline);
3607 gpsEnablePassthrough(cliPort);
3609 #endif
3611 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3612 static void cliPrintGyroRegisters(uint8_t whichSensor)
3614 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3615 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3616 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3619 static void cliDumpGyroRegisters(const char *cmdName, char *cmdline)
3621 UNUSED(cmdName);
3622 UNUSED(cmdline);
3624 #ifdef USE_MULTI_GYRO
3625 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3626 cliPrintLinef("\r\n# Gyro 1");
3627 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3629 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3630 cliPrintLinef("\r\n# Gyro 2");
3631 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3633 #else
3634 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3635 #endif
3637 #endif
3640 static int parseOutputIndex(const char *cmdName, char *pch, bool allowAllEscs)
3642 int outputIndex = atoi(pch);
3643 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3644 cliPrintLinef("Using output %d.", outputIndex);
3645 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3646 cliPrintLinef("Using all outputs.");
3647 } else {
3648 cliPrintErrorLinef(cmdName, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3650 return -1;
3653 return outputIndex;
3656 #if defined(USE_DSHOT)
3657 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3659 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3660 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3661 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3663 enum {
3664 ESC_INFO_KISS_V1,
3665 ESC_INFO_KISS_V2,
3666 ESC_INFO_BLHELI32
3669 #define ESC_INFO_VERSION_POSITION 12
3671 static void printEscInfo(const char *cmdName, const uint8_t *escInfoBuffer, uint8_t bytesRead)
3673 bool escInfoReceived = false;
3674 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3675 uint8_t escInfoVersion;
3676 uint8_t frameLength;
3677 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3678 escInfoVersion = ESC_INFO_BLHELI32;
3679 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3680 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3681 escInfoVersion = ESC_INFO_KISS_V2;
3682 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3683 } else {
3684 escInfoVersion = ESC_INFO_KISS_V1;
3685 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3688 if (bytesRead == frameLength) {
3689 escInfoReceived = true;
3691 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3692 uint8_t firmwareVersion = 0;
3693 uint8_t firmwareSubVersion = 0;
3694 uint8_t escType = 0;
3695 switch (escInfoVersion) {
3696 case ESC_INFO_KISS_V1:
3697 firmwareVersion = escInfoBuffer[12];
3698 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3699 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3701 break;
3702 case ESC_INFO_KISS_V2:
3703 firmwareVersion = escInfoBuffer[13];
3704 firmwareSubVersion = escInfoBuffer[14];
3705 escType = escInfoBuffer[15];
3707 break;
3708 case ESC_INFO_BLHELI32:
3709 firmwareVersion = escInfoBuffer[13];
3710 firmwareSubVersion = escInfoBuffer[14];
3711 escType = escInfoBuffer[15];
3713 break;
3716 cliPrint("ESC Type: ");
3717 switch (escInfoVersion) {
3718 case ESC_INFO_KISS_V1:
3719 case ESC_INFO_KISS_V2:
3720 switch (escType) {
3721 case 1:
3722 cliPrintLine("KISS8A");
3724 break;
3725 case 2:
3726 cliPrintLine("KISS16A");
3728 break;
3729 case 3:
3730 cliPrintLine("KISS24A");
3732 break;
3733 case 5:
3734 cliPrintLine("KISS Ultralite");
3736 break;
3737 default:
3738 cliPrintLine("unknown");
3740 break;
3743 break;
3744 case ESC_INFO_BLHELI32:
3746 char *escType = (char *)(escInfoBuffer + 31);
3747 escType[32] = 0;
3748 cliPrintLine(escType);
3751 break;
3754 cliPrint("MCU Serial No: 0x");
3755 for (int i = 0; i < 12; i++) {
3756 if (i && (i % 3 == 0)) {
3757 cliPrint("-");
3759 cliPrintf("%02x", escInfoBuffer[i]);
3761 cliPrintLinefeed();
3763 switch (escInfoVersion) {
3764 case ESC_INFO_KISS_V1:
3765 case ESC_INFO_KISS_V2:
3766 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3768 break;
3769 case ESC_INFO_BLHELI32:
3770 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3772 break;
3774 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3775 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3776 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3777 if (escInfoVersion == ESC_INFO_BLHELI32) {
3778 uint8_t setting = escInfoBuffer[18];
3779 cliPrint("Low voltage Limit: ");
3780 switch (setting) {
3781 case 0:
3782 cliPrintLine("off");
3784 break;
3785 case 255:
3786 cliPrintLine("unsupported");
3788 break;
3789 default:
3790 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3792 break;
3795 setting = escInfoBuffer[19];
3796 cliPrint("Current Limit: ");
3797 switch (setting) {
3798 case 0:
3799 cliPrintLine("off");
3801 break;
3802 case 255:
3803 cliPrintLine("unsupported");
3805 break;
3806 default:
3807 cliPrintLinef("%d", setting);
3809 break;
3812 for (int i = 0; i < 4; i++) {
3813 setting = escInfoBuffer[i + 20];
3814 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3818 } else {
3819 cliPrintErrorLinef(cmdName, "CHECKSUM ERROR.");
3824 if (!escInfoReceived) {
3825 cliPrintLine("No Info.");
3829 static void executeEscInfoCommand(const char *cmdName, uint8_t escIndex)
3831 cliPrintLinef("Info for ESC %d:", escIndex);
3833 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3835 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3837 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, DSHOT_CMD_TYPE_BLOCKING);
3839 delay(10);
3841 printEscInfo(cmdName, escInfoBuffer, getNumberEscBytesRead());
3843 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3845 static void cliDshotProg(const char *cmdName, char *cmdline)
3847 if (isEmpty(cmdline) || !isMotorProtocolDshot()) {
3848 cliShowParseError(cmdName);
3850 return;
3853 char *saveptr;
3854 char *pch = strtok_r(cmdline, " ", &saveptr);
3855 int pos = 0;
3856 int escIndex = 0;
3857 bool firstCommand = true;
3858 while (pch != NULL) {
3859 switch (pos) {
3860 case 0:
3861 escIndex = parseOutputIndex(cmdName, pch, true);
3862 if (escIndex == -1) {
3863 return;
3866 break;
3867 default:
3869 int command = atoi(pch);
3870 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3871 if (firstCommand) {
3872 // pwmDisableMotors();
3873 motorDisable();
3875 firstCommand = false;
3878 if (command != DSHOT_CMD_ESC_INFO) {
3879 dshotCommandWrite(escIndex, getMotorCount(), command, DSHOT_CMD_TYPE_BLOCKING);
3880 } else {
3881 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3882 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3883 if (escIndex != ALL_MOTORS) {
3884 executeEscInfoCommand(cmdName, escIndex);
3885 } else {
3886 for (uint8_t i = 0; i < getMotorCount(); i++) {
3887 executeEscInfoCommand(cmdName, i);
3890 } else
3891 #endif
3893 cliPrintLine("Not supported.");
3897 cliPrintLinef("Command Sent: %d", command);
3899 } else {
3900 cliPrintErrorLinef(cmdName, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3904 break;
3907 pos++;
3908 pch = strtok_r(NULL, " ", &saveptr);
3911 motorEnable();
3913 #endif // USE_DSHOT
3915 #ifdef USE_ESCSERIAL
3916 static void cliEscPassthrough(const char *cmdName, char *cmdline)
3918 if (isEmpty(cmdline)) {
3919 cliShowInvalidArgumentCountError(cmdName);
3921 return;
3924 char *saveptr;
3925 char *pch = strtok_r(cmdline, " ", &saveptr);
3926 int pos = 0;
3927 uint8_t mode = 0;
3928 int escIndex = 0;
3929 while (pch != NULL) {
3930 switch (pos) {
3931 case 0:
3932 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3933 mode = PROTOCOL_SIMONK;
3934 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3935 mode = PROTOCOL_BLHELI;
3936 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3937 mode = PROTOCOL_KISS;
3938 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3939 mode = PROTOCOL_KISSALL;
3940 } else {
3941 cliShowParseError(cmdName);
3943 return;
3945 break;
3946 case 1:
3947 escIndex = parseOutputIndex(cmdName, pch, mode == PROTOCOL_KISS);
3948 if (escIndex == -1) {
3949 return;
3952 break;
3953 default:
3954 cliShowInvalidArgumentCountError(cmdName);
3956 return;
3958 break;
3961 pos++;
3962 pch = strtok_r(NULL, " ", &saveptr);
3965 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
3966 cliPrintErrorLinef(cmdName, "Error starting ESC connection");
3969 #endif
3971 #ifndef USE_QUAD_MIXER_ONLY
3972 static void cliMixer(const char *cmdName, char *cmdline)
3974 int len;
3976 len = strlen(cmdline);
3978 if (len == 0) {
3979 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3980 return;
3981 } else if (strncasecmp(cmdline, "list", len) == 0) {
3982 cliPrint("Available:");
3983 for (uint32_t i = 0; ; i++) {
3984 if (mixerNames[i] == NULL)
3985 break;
3986 cliPrintf(" %s", mixerNames[i]);
3988 cliPrintLinefeed();
3989 return;
3992 for (uint32_t i = 0; ; i++) {
3993 if (mixerNames[i] == NULL) {
3994 cliPrintErrorLinef(cmdName, "INVALID NAME");
3995 return;
3997 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
3998 mixerConfigMutable()->mixerMode = i + 1;
3999 break;
4003 cliMixer(cmdName, "");
4005 #endif
4007 static void cliMotor(const char *cmdName, char *cmdline)
4009 if (isEmpty(cmdline)) {
4010 cliShowInvalidArgumentCountError(cmdName);
4012 return;
4015 int motorIndex = 0;
4016 int motorValue = 0;
4018 char *saveptr;
4019 char *pch = strtok_r(cmdline, " ", &saveptr);
4020 int index = 0;
4021 while (pch != NULL) {
4022 switch (index) {
4023 case 0:
4024 motorIndex = parseOutputIndex(cmdName, pch, true);
4025 if (motorIndex == -1) {
4026 return;
4029 break;
4030 case 1:
4031 motorValue = atoi(pch);
4033 break;
4035 index++;
4036 pch = strtok_r(NULL, " ", &saveptr);
4039 if (index == 2) {
4040 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
4041 cliShowArgumentRangeError(cmdName, "VALUE", 1000, 2000);
4042 } else {
4043 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
4045 if (motorIndex != ALL_MOTORS) {
4046 motor_disarmed[motorIndex] = motorOutputValue;
4048 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
4049 } else {
4050 for (int i = 0; i < getMotorCount(); i++) {
4051 motor_disarmed[i] = motorOutputValue;
4054 cliPrintLinef("all motors: %d", motorOutputValue);
4057 } else {
4058 cliShowInvalidArgumentCountError(cmdName);
4062 #ifndef MINIMAL_CLI
4063 static void cliPlaySound(const char *cmdName, char *cmdline)
4065 int i;
4066 const char *name;
4067 static int lastSoundIdx = -1;
4069 if (isEmpty(cmdline)) {
4070 i = lastSoundIdx + 1; //next sound index
4071 if ((name=beeperNameForTableIndex(i)) == NULL) {
4072 while (true) { //no name for index; try next one
4073 if (++i >= beeperTableEntryCount())
4074 i = 0; //if end then wrap around to first entry
4075 if ((name=beeperNameForTableIndex(i)) != NULL)
4076 break; //if name OK then play sound below
4077 if (i == lastSoundIdx + 1) { //prevent infinite loop
4078 cliPrintErrorLinef(cmdName, "ERROR PLAYING SOUND");
4079 return;
4083 } else { //index value was given
4084 i = atoi(cmdline);
4085 if ((name=beeperNameForTableIndex(i)) == NULL) {
4086 cliPrintLinef("No sound for index %d", i);
4087 return;
4090 lastSoundIdx = i;
4091 beeperSilence();
4092 cliPrintLinef("Playing sound %d: %s", i, name);
4093 beeper(beeperModeForTableIndex(i));
4095 #endif
4097 static void cliProfile(const char *cmdName, char *cmdline)
4099 if (isEmpty(cmdline)) {
4100 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4101 return;
4102 } else {
4103 const int i = atoi(cmdline);
4104 if (i >= 0 && i < PID_PROFILE_COUNT) {
4105 changePidProfile(i);
4106 cliProfile(cmdName, "");
4107 } else {
4108 cliPrintErrorLinef(cmdName, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
4113 static void cliRateProfile(const char *cmdName, char *cmdline)
4115 if (isEmpty(cmdline)) {
4116 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4117 return;
4118 } else {
4119 const int i = atoi(cmdline);
4120 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
4121 changeControlRateProfile(i);
4122 cliRateProfile(cmdName, "");
4123 } else {
4124 cliPrintErrorLinef(cmdName, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
4129 static void cliDumpPidProfile(const char *cmdName, uint8_t pidProfileIndex, dumpFlags_t dumpMask)
4131 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4132 // Faulty values
4133 return;
4136 pidProfileIndexToUse = pidProfileIndex;
4138 cliPrintLinefeed();
4139 cliProfile(cmdName, "");
4141 char profileStr[10];
4142 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4143 dumpAllValues(cmdName, PROFILE_VALUE, dumpMask, profileStr);
4145 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4148 static void cliDumpRateProfile(const char *cmdName, uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4150 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4151 // Faulty values
4152 return;
4155 rateProfileIndexToUse = rateProfileIndex;
4157 cliPrintLinefeed();
4158 cliRateProfile(cmdName, "");
4160 char rateProfileStr[14];
4161 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4162 dumpAllValues(cmdName, PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4164 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4167 #ifdef USE_CLI_BATCH
4168 static void cliPrintCommandBatchWarning(const char *cmdName, const char *warning)
4170 cliPrintErrorLinef(cmdName, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4171 if (warning) {
4172 cliPrintErrorLinef(cmdName, warning);
4176 static void resetCommandBatch(void)
4178 commandBatchActive = false;
4179 commandBatchError = false;
4182 static void cliBatch(const char *cmdName, char *cmdline)
4184 if (strncasecmp(cmdline, "start", 5) == 0) {
4185 if (!commandBatchActive) {
4186 commandBatchActive = true;
4187 commandBatchError = false;
4189 cliPrintLine("Command batch started");
4190 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4191 if (commandBatchActive && commandBatchError) {
4192 cliPrintCommandBatchWarning(cmdName, NULL);
4193 } else {
4194 cliPrintLine("Command batch ended");
4196 resetCommandBatch();
4197 } else {
4198 cliPrintErrorLinef(cmdName, "Invalid option");
4201 #endif
4203 static bool prepareSave(void)
4205 #if defined(USE_CUSTOM_DEFAULTS)
4206 if (processingCustomDefaults) {
4207 return true;
4209 #endif
4211 #ifdef USE_CLI_BATCH
4212 if (commandBatchActive && commandBatchError) {
4213 return false;
4215 #endif
4217 #if defined(USE_BOARD_INFO)
4218 if (boardInformationUpdated) {
4219 persistBoardInformation();
4221 #if defined(USE_SIGNATURE)
4222 if (signatureUpdated) {
4223 persistSignature();
4225 #endif
4226 #endif // USE_BOARD_INFO
4228 return true;
4231 bool tryPrepareSave(const char *cmdName)
4233 bool success = prepareSave();
4234 #if defined(USE_CLI_BATCH)
4235 if (!success) {
4236 cliPrintCommandBatchWarning(cmdName, "PLEASE FIX ERRORS THEN 'SAVE'");
4237 resetCommandBatch();
4239 return false;
4241 #else
4242 UNUSED(cmdName);
4243 UNUSED(success);
4244 #endif
4246 return true;
4249 static void cliSave(const char *cmdName, char *cmdline)
4251 UNUSED(cmdline);
4253 if (tryPrepareSave(cmdName)) {
4254 writeEEPROM();
4255 cliPrintHashLine("saving");
4257 cliReboot();
4261 #if defined(USE_CUSTOM_DEFAULTS)
4262 bool resetConfigToCustomDefaults(void)
4264 resetConfig();
4266 #ifdef USE_CLI_BATCH
4267 commandBatchError = false;
4268 #endif
4270 cliProcessCustomDefaults(true);
4272 #if defined(USE_SIMPLIFIED_TUNING)
4273 applySimplifiedTuningAllProfiles();
4274 #endif
4276 return prepareSave();
4279 static bool customDefaultsHasNext(const char *customDefaultsPtr)
4281 return *customDefaultsPtr && *customDefaultsPtr != 0xFF && customDefaultsPtr < customDefaultsEnd;
4284 static const char *parseCustomDefaultsHeaderElement(char *dest, const char *customDefaultsPtr, const char *prefix, const char terminator, const unsigned maxLength)
4286 char *endPtr = NULL;
4287 unsigned len = strlen(prefix);
4288 if (customDefaultsPtr && customDefaultsHasNext(customDefaultsPtr) && strncmp(customDefaultsPtr, prefix, len) == 0) {
4289 customDefaultsPtr += len;
4290 endPtr = strchr(customDefaultsPtr, terminator);
4293 if (endPtr && customDefaultsHasNext(endPtr)) {
4294 len = endPtr - customDefaultsPtr;
4295 memcpy(dest, customDefaultsPtr, MIN(len, maxLength));
4297 customDefaultsPtr += len;
4299 return customDefaultsPtr;
4302 return NULL;
4305 static void parseCustomDefaultsHeader(void)
4307 const char *customDefaultsPtr = customDefaultsStart;
4308 if (strncmp(customDefaultsPtr, CUSTOM_DEFAULTS_START_PREFIX, strlen(CUSTOM_DEFAULTS_START_PREFIX)) == 0) {
4309 customDefaultsFound = true;
4311 customDefaultsPtr = strchr(customDefaultsPtr, '\n');
4312 if (customDefaultsPtr && customDefaultsHasNext(customDefaultsPtr)) {
4313 customDefaultsPtr++;
4316 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsManufacturerId, customDefaultsPtr, CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX, CUSTOM_DEFAULTS_BOARD_NAME_PREFIX[0], MAX_MANUFACTURER_ID_LENGTH);
4318 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsBoardName, customDefaultsPtr, CUSTOM_DEFAULTS_BOARD_NAME_PREFIX, CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX[0], MAX_BOARD_NAME_LENGTH);
4320 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsChangesetId, customDefaultsPtr, CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX, CUSTOM_DEFAULTS_DATE_PREFIX[0], MAX_CHANGESET_ID_LENGTH);
4322 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsDate, customDefaultsPtr, CUSTOM_DEFAULTS_DATE_PREFIX, '\n', MAX_DATE_LENGTH);
4325 customDefaultsHeaderParsed = true;
4328 bool hasCustomDefaults(void)
4330 if (!customDefaultsHeaderParsed) {
4331 parseCustomDefaultsHeader();
4334 return customDefaultsFound;
4336 #endif
4338 static void cliDefaults(const char *cmdName, char *cmdline)
4340 bool saveConfigs = true;
4341 uint16_t parameterGroupId = 0;
4342 #if defined(USE_CUSTOM_DEFAULTS)
4343 bool useCustomDefaults = true;
4344 #elif defined(USE_CUSTOM_DEFAULTS_ADDRESS)
4345 // Required to keep the linker from eliminating these
4346 if (customDefaultsStart != customDefaultsEnd) {
4347 delay(0);
4349 #endif
4351 char *saveptr;
4352 char* tok = strtok_r(cmdline, " ", &saveptr);
4353 int index = 0;
4354 bool expectParameterGroupId = false;
4355 while (tok != NULL) {
4356 if (expectParameterGroupId) {
4357 parameterGroupId = atoi(tok);
4358 expectParameterGroupId = false;
4360 if (!parameterGroupId) {
4361 cliShowParseError(cmdName);
4363 return;
4365 } else if (strcasestr(tok, "group_id")) {
4366 expectParameterGroupId = true;
4367 } else if (strcasestr(tok, "nosave")) {
4368 saveConfigs = false;
4369 #if defined(USE_CUSTOM_DEFAULTS)
4370 } else if (strcasestr(tok, "bare")) {
4371 useCustomDefaults = false;
4372 } else if (strcasestr(tok, "show")) {
4373 if (index != 0) {
4374 cliShowParseError(cmdName);
4375 } else if (hasCustomDefaults()) {
4376 char *customDefaultsPtr = customDefaultsStart;
4377 while (customDefaultsHasNext(customDefaultsPtr)) {
4378 if (*customDefaultsPtr != '\n') {
4379 cliPrintf("%c", *customDefaultsPtr++);
4380 } else {
4381 cliPrintLinefeed();
4382 customDefaultsPtr++;
4385 } else {
4386 cliPrintError(cmdName, "NO CUSTOM DEFAULTS FOUND");
4389 return;
4390 #endif
4391 } else {
4392 cliShowParseError(cmdName);
4394 return;
4397 index++;
4398 tok = strtok_r(NULL, " ", &saveptr);
4401 if (expectParameterGroupId) {
4402 cliShowParseError(cmdName);
4404 return;
4407 if (parameterGroupId) {
4408 cliPrintLinef("\r\n# resetting group %d to defaults", parameterGroupId);
4409 backupConfigs();
4410 } else {
4411 cliPrintHashLine("resetting to defaults");
4414 resetConfig();
4416 #ifdef USE_CLI_BATCH
4417 // Reset only the error state and allow the batch active state to remain.
4418 // This way if a "defaults nosave" was issued after the "batch on" we'll
4419 // only reset the current error state but the batch will still be active
4420 // for subsequent commands.
4421 commandBatchError = false;
4422 #endif
4424 #if defined(USE_CUSTOM_DEFAULTS)
4425 if (useCustomDefaults) {
4426 cliProcessCustomDefaults(false);
4428 #endif
4430 #if defined(USE_SIMPLIFIED_TUNING)
4431 applySimplifiedTuningAllProfiles();
4432 #endif
4434 if (parameterGroupId) {
4435 restoreConfigs(parameterGroupId);
4438 if (saveConfigs && tryPrepareSave(cmdName)) {
4439 writeUnmodifiedConfigToEEPROM();
4441 cliReboot();
4445 static void cliPrintVarDefault(const char *cmdName, const clivalue_t *value)
4447 const pgRegistry_t *pg = pgFind(value->pgn);
4448 if (pg) {
4449 const char *defaultFormat = "Default value: ";
4450 const int valueOffset = getValueOffset(value);
4451 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
4452 if (!equalsDefault) {
4453 cliPrintf(defaultFormat, value->name);
4454 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
4455 cliPrintLinefeed();
4460 STATIC_UNIT_TESTED void cliGet(const char *cmdName, char *cmdline)
4462 const clivalue_t *val;
4463 int matchedCommands = 0;
4465 pidProfileIndexToUse = getCurrentPidProfileIndex();
4466 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4468 backupAndResetConfigs(true);
4470 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4471 if (strcasestr(valueTable[i].name, cmdline)) {
4472 val = &valueTable[i];
4473 if (matchedCommands > 0) {
4474 cliPrintLinefeed();
4476 cliPrintf("%s = ", valueTable[i].name);
4477 cliPrintVar(cmdName, val, 0);
4478 cliPrintLinefeed();
4479 switch (val->type & VALUE_SECTION_MASK) {
4480 case PROFILE_VALUE:
4481 cliProfile(cmdName, "");
4483 break;
4484 case PROFILE_RATE_VALUE:
4485 cliRateProfile(cmdName, "");
4487 break;
4488 default:
4490 break;
4492 cliPrintVarRange(val);
4493 cliPrintVarDefault(cmdName, val);
4495 matchedCommands++;
4499 restoreConfigs(0);
4501 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4502 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4504 if (!matchedCommands) {
4505 cliPrintErrorLinef(cmdName, "INVALID NAME");
4509 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
4511 while (*(bufEnd - 1) == ' ') {
4512 bufEnd--;
4515 return bufEnd - bufBegin;
4518 uint16_t cliGetSettingIndex(char *name, uint8_t length)
4520 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4521 const char *settingName = valueTable[i].name;
4523 // ensure exact match when setting to prevent setting variables with shorter names
4524 if (strncasecmp(name, settingName, strlen(settingName)) == 0 && length == strlen(settingName)) {
4525 return i;
4528 return valueTableEntryCount;
4531 STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
4533 const uint32_t len = strlen(cmdline);
4534 char *eqptr;
4536 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4537 cliPrintLine("Current settings: ");
4539 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4540 const clivalue_t *val = &valueTable[i];
4541 cliPrintf("%s = ", valueTable[i].name);
4542 cliPrintVar(cmdName, val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4543 cliPrintLinefeed();
4545 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4546 // has equals
4548 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4550 // skip the '=' and any ' ' characters
4551 eqptr++;
4552 eqptr = skipSpace(eqptr);
4554 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4555 if (index >= valueTableEntryCount) {
4556 cliPrintErrorLinef(cmdName, "INVALID NAME");
4557 return;
4559 const clivalue_t *val = &valueTable[index];
4561 bool valueChanged = false;
4562 int16_t value = 0;
4563 switch (val->type & VALUE_MODE_MASK) {
4564 case MODE_DIRECT: {
4565 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4566 uint32_t value = strtoul(eqptr, NULL, 10);
4568 if (value <= val->config.u32Max) {
4569 cliSetVar(val, value);
4570 valueChanged = true;
4572 } else {
4573 int value = atoi(eqptr);
4575 int min;
4576 int max;
4577 getMinMax(val, &min, &max);
4579 if (value >= min && value <= max) {
4580 cliSetVar(val, value);
4581 valueChanged = true;
4586 break;
4587 case MODE_LOOKUP:
4588 case MODE_BITSET: {
4589 int tableIndex;
4590 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4591 tableIndex = TABLE_OFF_ON;
4592 } else {
4593 tableIndex = val->config.lookup.tableIndex;
4595 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4596 bool matched = false;
4597 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4598 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4600 if (matched) {
4601 value = tableValueIndex;
4603 cliSetVar(val, value);
4604 valueChanged = true;
4609 break;
4611 case MODE_ARRAY: {
4612 const uint8_t arrayLength = val->config.array.length;
4613 char *valPtr = eqptr;
4615 int i = 0;
4616 while (i < arrayLength && valPtr != NULL) {
4617 // skip spaces
4618 valPtr = skipSpace(valPtr);
4620 // process substring starting at valPtr
4621 // note: no need to copy substrings for atoi()
4622 // it stops at the first character that cannot be converted...
4623 switch (val->type & VALUE_TYPE_MASK) {
4624 default:
4625 case VAR_UINT8:
4627 // fetch data pointer
4628 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4629 // store value
4630 *data = (uint8_t)atoi((const char*) valPtr);
4633 break;
4634 case VAR_INT8:
4636 // fetch data pointer
4637 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4638 // store value
4639 *data = (int8_t)atoi((const char*) valPtr);
4642 break;
4643 case VAR_UINT16:
4645 // fetch data pointer
4646 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4647 // store value
4648 *data = (uint16_t)atoi((const char*) valPtr);
4651 break;
4652 case VAR_INT16:
4654 // fetch data pointer
4655 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4656 // store value
4657 *data = (int16_t)atoi((const char*) valPtr);
4660 break;
4661 case VAR_UINT32:
4663 // fetch data pointer
4664 uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
4665 // store value
4666 *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
4669 break;
4672 // find next comma (or end of string)
4673 valPtr = strchr(valPtr, ',') + 1;
4675 i++;
4679 // mark as changed
4680 valueChanged = true;
4682 break;
4683 case MODE_STRING: {
4684 char *valPtr = eqptr;
4685 valPtr = skipSpace(valPtr);
4687 const unsigned int len = strlen(valPtr);
4688 const uint8_t min = val->config.string.minlength;
4689 const uint8_t max = val->config.string.maxlength;
4690 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4691 strlen((char *)cliGetValuePointer(val)) == 0 ||
4692 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4694 if (updatable && len > 0 && len <= max) {
4695 memset((char *)cliGetValuePointer(val), 0, max);
4696 if (len >= min && strncmp(valPtr, emptyName, len)) {
4697 strncpy((char *)cliGetValuePointer(val), valPtr, len);
4699 valueChanged = true;
4700 } else {
4701 cliPrintErrorLinef(cmdName, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4704 break;
4707 if (valueChanged) {
4708 cliPrintf("%s set to ", val->name);
4709 cliPrintVar(cmdName, val, 0);
4710 } else {
4711 cliPrintErrorLinef(cmdName, "INVALID VALUE");
4712 cliPrintVarRange(val);
4715 return;
4716 } else {
4717 // no equals, check for matching variables.
4718 cliGet(cmdName, cmdline);
4722 static const char *getMcuTypeById(mcuTypeId_e id)
4724 if (id < ARRAYLEN(mcuTypeNames)) {
4725 return mcuTypeNames[id];
4726 } else {
4727 return "UNKNOWN";
4731 static void cliStatus(const char *cmdName, char *cmdline)
4733 UNUSED(cmdName);
4734 UNUSED(cmdline);
4736 // MCU type, clock, vrefint, core temperature
4738 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock / 1000000));
4740 #if defined(STM32F4) || defined(STM32G4)
4741 // Only F4 and G4 is capable of switching between HSE/HSI (for now)
4742 int sysclkSource = SystemSYSCLKSource();
4744 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4745 const char *PLLSource[] = { "-HSI", "-HSE" };
4747 int pllSource;
4749 if (sysclkSource >= 2) {
4750 pllSource = SystemPLLSource();
4753 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4754 #endif
4756 #ifdef USE_ADC_INTERNAL
4757 uint16_t vrefintMv = getVrefMv();
4758 int16_t coretemp = getCoreTemperatureCelsius();
4759 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4760 #else
4761 cliPrintLinefeed();
4762 #endif
4764 // Stack and config sizes and usages
4766 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4767 #ifdef USE_STACK_CHECK
4768 cliPrintf(", Stack used: %d", stackUsedSize());
4769 #endif
4770 cliPrintLinefeed();
4772 cliPrintLinef("Configuration: %s, size: %d, max available: %d", configurationStates[systemConfigMutable()->configurationState], getEEPROMConfigSize(), getEEPROMStorageSize());
4774 // Devices
4775 #if defined(USE_SPI) || defined(USE_I2C)
4776 cliPrint("Devices detected:");
4777 #if defined(USE_SPI)
4778 cliPrintf(" SPI:%d", spiGetRegisteredDeviceCount());
4779 #if defined(USE_I2C)
4780 cliPrint(",");
4781 #endif
4782 #endif
4783 #if defined(USE_I2C)
4784 cliPrintf(" I2C:%d", i2cGetRegisteredDeviceCount());
4785 #endif
4786 cliPrintLinefeed();
4787 #endif
4789 // Sensors
4790 cliPrint("Gyros detected:");
4791 bool found = false;
4792 for (unsigned pos = 0; pos < 7; pos++) {
4793 if (gyroConfig()->gyrosDetected & BIT(pos)) {
4794 if (found) {
4795 cliPrint(",");
4796 } else {
4797 found = true;
4799 cliPrintf(" gyro %d", pos + 1);
4802 #ifdef USE_SPI
4803 if (gyroActiveDev()->gyroModeSPI != GYRO_EXTI_NO_INT) {
4804 cliPrintf(" locked");
4806 if (gyroActiveDev()->gyroModeSPI == GYRO_EXTI_INT_DMA) {
4807 cliPrintf(" dma");
4809 if (spiGetExtDeviceCount(&gyroActiveDev()->dev) > 1) {
4810 cliPrintf(" shared");
4812 #endif
4813 cliPrintLinefeed();
4815 #if defined(USE_SENSOR_NAMES)
4816 const uint32_t detectedSensorsMask = sensorsMask();
4817 for (uint32_t i = 0; ; i++) {
4818 if (sensorTypeNames[i] == NULL) {
4819 break;
4821 const uint32_t mask = (1 << i);
4822 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4823 const uint8_t sensorHardwareIndex = detectedSensors[i];
4824 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4825 if (i) {
4826 cliPrint(", ");
4828 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4829 #if defined(USE_ACC)
4830 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4831 cliPrintf(".%c", acc.dev.revisionCode);
4833 #endif
4836 cliPrintLinefeed();
4837 #endif /* USE_SENSOR_NAMES */
4839 #if defined(USE_OSD)
4840 osdDisplayPortDevice_e displayPortDeviceType;
4841 osdGetDisplayPort(&displayPortDeviceType);
4843 cliPrintLinef("OSD: %s", lookupTableOsdDisplayPortDevice[displayPortDeviceType]);
4844 #endif
4846 // Uptime and wall clock
4848 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4850 #ifdef USE_RTC_TIME
4851 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4852 dateTime_t dt;
4853 if (rtcGetDateTime(&dt)) {
4854 dateTimeFormatLocal(buf, &dt);
4855 cliPrintf(", Current Time: %s", buf);
4857 #endif
4858 cliPrintLinefeed();
4860 // Run status
4862 const int gyroRate = getTaskDeltaTimeUs(TASK_GYRO) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_GYRO)));
4863 int rxRate = getCurrentRxRefreshRate();
4864 if (rxRate != 0) {
4865 rxRate = (int)(1000000.0f / ((float)rxRate));
4867 const int systemRate = getTaskDeltaTimeUs(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_SYSTEM)));
4868 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4869 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE), getTaskDeltaTimeUs(TASK_GYRO), gyroRate, rxRate, systemRate);
4871 // Battery meter
4873 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4875 // Other devices and status
4877 #ifdef USE_I2C
4878 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4879 #else
4880 const uint16_t i2cErrorCounter = 0;
4881 #endif
4882 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4884 #ifdef USE_SDCARD
4885 cliSdInfo(cmdName, "");
4886 #endif
4888 cliPrint("Arming disable flags:");
4889 armingDisableFlags_e flags = getArmingDisableFlags();
4890 while (flags) {
4891 const int bitpos = ffs(flags) - 1;
4892 flags &= ~(1 << bitpos);
4893 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4895 cliPrintLinefeed();
4898 static void cliTasks(const char *cmdName, char *cmdline)
4900 UNUSED(cmdName);
4901 UNUSED(cmdline);
4902 int averageLoadSum = 0;
4904 #ifndef MINIMAL_CLI
4905 if (systemConfig()->task_statistics) {
4906 #if defined(USE_LATE_TASK_STATISTICS)
4907 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms late run reqd/us");
4908 #else
4909 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4910 #endif
4911 } else {
4912 cliPrintLine("Task list");
4914 #endif
4915 for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4916 taskInfo_t taskInfo;
4917 getTaskInfo(taskId, &taskInfo);
4918 if (taskInfo.isEnabled) {
4919 int taskFrequency = taskInfo.averageDeltaTime10thUs == 0 ? 0 : lrintf(1e7f / taskInfo.averageDeltaTime10thUs);
4920 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4921 const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 : (taskInfo.maxExecutionTimeUs * taskFrequency) / 1000;
4922 const int averageLoad = taskInfo.averageExecutionTime10thUs == 0 ? 0 : (taskInfo.averageExecutionTime10thUs * taskFrequency) / 10000;
4923 if (taskId != TASK_SERIAL) {
4924 averageLoadSum += averageLoad;
4926 if (systemConfig()->task_statistics) {
4927 #if defined(USE_LATE_TASK_STATISTICS)
4928 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d %6d %6d %7d",
4929 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
4930 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4931 taskInfo.totalExecutionTimeUs / 1000,
4932 taskInfo.lateCount, taskInfo.runCount, taskInfo.execTime);
4933 #else
4934 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4935 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
4936 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4937 taskInfo.totalExecutionTimeUs / 1000);
4938 #endif
4939 } else {
4940 cliPrintLinef("%6d", taskFrequency);
4943 schedulerResetTaskMaxExecutionTime(taskId);
4946 if (systemConfig()->task_statistics) {
4947 cfCheckFuncInfo_t checkFuncInfo;
4948 getCheckFuncInfo(&checkFuncInfo);
4949 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTimeUs, checkFuncInfo.averageExecutionTimeUs, checkFuncInfo.totalExecutionTimeUs / 1000);
4950 cliPrintLinef("Total (excluding SERIAL) %33d.%1d%%", averageLoadSum/10, averageLoadSum%10);
4951 if (debugMode == DEBUG_SCHEDULER_DETERMINISM) {
4952 extern int32_t schedLoopStartCycles, taskGuardCycles;
4954 cliPrintLinef("Scheduler start cycles %d guard cycles %d", schedLoopStartCycles, taskGuardCycles);
4956 schedulerResetCheckFunctionMaxExecutionTime();
4960 static void printVersion(const char *cmdName, bool printBoardInfo)
4962 #if !(defined(USE_CUSTOM_DEFAULTS))
4963 UNUSED(cmdName);
4964 UNUSED(printBoardInfo);
4965 #endif
4967 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4968 FC_FIRMWARE_NAME,
4969 targetName,
4970 systemConfig()->boardIdentifier,
4971 FC_VERSION_STRING,
4972 buildDate,
4973 buildTime,
4974 shortGitRevision,
4975 MSP_API_VERSION_STRING
4978 #ifdef FEATURE_CUT_LEVEL
4979 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL);
4980 #else
4981 cliPrintLinefeed();
4982 #endif
4984 #if defined(USE_CUSTOM_DEFAULTS)
4985 if (hasCustomDefaults()) {
4986 if (strlen(customDefaultsManufacturerId) || strlen(customDefaultsBoardName) || strlen(customDefaultsChangesetId) || strlen(customDefaultsDate)) {
4987 cliPrintLinef("%s%s%s%s%s%s%s%s",
4988 CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX, customDefaultsManufacturerId,
4989 CUSTOM_DEFAULTS_BOARD_NAME_PREFIX, customDefaultsBoardName,
4990 CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX, customDefaultsChangesetId,
4991 CUSTOM_DEFAULTS_DATE_PREFIX, customDefaultsDate
4993 } else {
4994 cliPrintHashLine("config: YES");
4996 } else {
4997 cliPrintError(cmdName, "NO CONFIG FOUND");
4999 #endif // USE_CUSTOM_DEFAULTS
5001 #if defined(USE_BOARD_INFO)
5002 if (printBoardInfo && strlen(getManufacturerId()) && strlen(getBoardName())) {
5003 cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
5005 #endif
5008 static void cliVersion(const char *cmdName, char *cmdline)
5010 UNUSED(cmdline);
5012 printVersion(cmdName, true);
5015 #ifdef USE_RC_SMOOTHING_FILTER
5016 static void cliRcSmoothing(const char *cmdName, char *cmdline)
5018 UNUSED(cmdName);
5019 UNUSED(cmdline);
5020 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
5021 cliPrint("# RC Smoothing Type: ");
5022 if (rxConfig()->rc_smoothing_mode) {
5023 cliPrintLine("FILTER");
5024 if (rcSmoothingAutoCalculate()) {
5025 const uint16_t avgRxFrameUs = rcSmoothingData->averageFrameTimeUs;
5026 cliPrint("# Detected RX frame rate: ");
5027 if (avgRxFrameUs == 0) {
5028 cliPrintLine("NO SIGNAL");
5029 } else {
5030 cliPrintLinef("%d.%03dms", avgRxFrameUs / 1000, avgRxFrameUs % 1000);
5033 cliPrintf("# Active setpoint cutoff: %dhz ", rcSmoothingData->setpointCutoffFrequency);
5034 if (rcSmoothingData->setpointCutoffSetting) {
5035 cliPrintLine("(manual)");
5036 } else {
5037 cliPrintLine("(auto)");
5039 cliPrintf("# Active FF cutoff: %dhz ", rcSmoothingData->feedforwardCutoffFrequency);
5040 if (rcSmoothingData->ffCutoffSetting) {
5041 cliPrintLine("(manual)");
5042 } else {
5043 cliPrintLine("(auto)");
5045 cliPrintf("# Active throttle cutoff: %dhz ", rcSmoothingData->throttleCutoffFrequency);
5046 if (rcSmoothingData->throttleCutoffSetting) {
5047 cliPrintLine("(manual)");
5048 } else {
5049 cliPrintLine("(auto)");
5051 } else {
5052 cliPrintLine("OFF");
5055 #endif // USE_RC_SMOOTHING_FILTER
5057 #if defined(USE_RESOURCE_MGMT)
5059 #define RESOURCE_VALUE_MAX_INDEX(x) ((x) == 0 ? 1 : (x))
5061 typedef struct {
5062 const uint8_t owner;
5063 pgn_t pgn;
5064 uint8_t stride;
5065 uint8_t offset;
5066 const uint8_t maxIndex;
5067 } cliResourceValue_t;
5069 // Handy macros for keeping the table tidy.
5070 // DEFS : Single entry
5071 // DEFA : Array of uint8_t (stride = 1)
5072 // DEFW : Wider stride case; array of structs.
5074 #define DEFS(owner, pgn, type, member) \
5075 { owner, pgn, 0, offsetof(type, member), 0 }
5077 #define DEFA(owner, pgn, type, member, max) \
5078 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
5080 #define DEFW(owner, pgn, type, member, max) \
5081 { owner, pgn, sizeof(type), offsetof(type, member), max }
5083 const cliResourceValue_t resourceTable[] = {
5084 #ifdef USE_BEEPER
5085 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
5086 #endif
5087 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
5088 #ifdef USE_SERVOS
5089 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
5090 #endif
5091 #if defined(USE_PPM)
5092 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
5093 #endif
5094 #if defined(USE_PWM)
5095 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
5096 #endif
5097 #ifdef USE_RANGEFINDER_HCSR04
5098 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
5099 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
5100 #endif
5101 #ifdef USE_LED_STRIP
5102 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
5103 #endif
5104 #ifdef USE_UART
5105 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
5106 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
5107 #endif
5108 #ifdef USE_INVERTER
5109 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
5110 #endif
5111 #ifdef USE_I2C
5112 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
5113 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
5114 #endif
5115 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
5116 #ifdef USE_SPEKTRUM_BIND
5117 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
5118 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
5119 #endif
5120 #ifdef USE_TRANSPONDER
5121 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
5122 #endif
5123 #ifdef USE_SPI
5124 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
5125 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
5126 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
5127 #endif
5128 #ifdef USE_ESCSERIAL
5129 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
5130 #endif
5131 #ifdef USE_CAMERA_CONTROL
5132 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
5133 #endif
5134 #ifdef USE_ADC
5135 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
5136 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
5137 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
5138 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
5139 #endif
5140 #ifdef USE_BARO
5141 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
5142 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
5143 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
5144 #endif
5145 #ifdef USE_MAG
5146 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
5147 #ifdef USE_MAG_DATA_READY_SIGNAL
5148 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
5149 #endif
5150 #endif
5151 #ifdef USE_SDCARD_SPI
5152 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
5153 #endif
5154 #ifdef USE_SDCARD
5155 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
5156 #endif
5157 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
5158 DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
5159 DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
5160 DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
5161 DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
5162 DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
5163 DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
5164 #endif
5165 #ifdef USE_PINIO
5166 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
5167 #endif
5168 #if defined(USE_USB_MSC)
5169 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
5170 #endif
5171 #ifdef USE_FLASH_CHIP
5172 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
5173 #endif
5174 #ifdef USE_MAX7456
5175 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
5176 #endif
5177 #ifdef USE_RX_SPI
5178 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
5179 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
5180 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
5181 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
5182 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
5183 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
5184 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
5185 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
5186 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
5187 #endif
5188 #endif
5189 #if defined(USE_RX_EXPRESSLRS)
5190 DEFS( OWNER_RX_SPI_EXPRESSLRS_RESET, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, resetIoTag ),
5191 DEFS( OWNER_RX_SPI_EXPRESSLRS_BUSY, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, busyIoTag ),
5192 #endif
5193 #endif
5194 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
5195 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
5196 #ifdef USE_USB_DETECT
5197 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
5198 #endif
5199 #ifdef USE_VTX_RTC6705
5200 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
5201 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
5202 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
5203 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
5204 #endif
5205 #ifdef USE_PIN_PULL_UP_DOWN
5206 DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5207 DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5208 #endif
5211 #undef DEFS
5212 #undef DEFA
5213 #undef DEFW
5215 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
5217 const pgRegistry_t* rec = pgFind(value.pgn);
5218 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
5221 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
5223 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5224 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
5225 const char* owner = ownerNames[resourceTable[i].owner];
5226 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
5227 const void *currentConfig;
5228 const void *defaultConfig;
5229 if (isReadingConfigFromCopy()) {
5230 currentConfig = pg->copy;
5231 defaultConfig = pg->address;
5232 } else {
5233 currentConfig = pg->address;
5234 defaultConfig = NULL;
5237 for (int index = 0; index < RESOURCE_VALUE_MAX_INDEX(resourceTable[i].maxIndex); index++) {
5238 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5239 ioTag_t ioTagDefault = 0;
5240 if (defaultConfig) {
5241 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5244 const bool equalsDefault = ioTag == ioTagDefault;
5245 const char *format = "resource %s %d %c%02d";
5246 const char *formatUnassigned = "resource %s %d NONE";
5247 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5248 if (ioTagDefault) {
5249 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
5250 } else if (defaultConfig) {
5251 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5253 if (ioTag) {
5254 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
5255 } else if (!(dumpMask & HIDE_UNUSED)) {
5256 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5262 static void printResourceOwner(uint8_t owner, uint8_t index)
5264 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
5266 if (resourceTable[owner].maxIndex > 0) {
5267 cliPrintf(" %d", RESOURCE_INDEX(index));
5271 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
5273 if (!newTag) {
5274 return;
5277 const char * format = "\r\nNOTE: %c%02d already assigned to ";
5278 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
5279 for (int i = 0; i < RESOURCE_VALUE_MAX_INDEX(resourceTable[r].maxIndex); i++) {
5280 ioTag_t *tag = getIoTag(resourceTable[r], i);
5281 if (*tag == newTag) {
5282 bool cleared = false;
5283 if (r == resourceIndex) {
5284 if (i == index) {
5285 continue;
5287 *tag = IO_TAG_NONE;
5288 cleared = true;
5291 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
5293 printResourceOwner(r, i);
5295 if (cleared) {
5296 cliPrintf(". ");
5297 printResourceOwner(r, i);
5298 cliPrintf(" disabled");
5301 cliPrintLine(".");
5307 static bool strToPin(char *ptr, ioTag_t *tag)
5309 if (strcasecmp(ptr, "NONE") == 0) {
5310 *tag = IO_TAG_NONE;
5312 return true;
5313 } else {
5314 const unsigned port = (*ptr >= 'a') ? *ptr - 'a' : *ptr - 'A';
5315 if (port < 8) {
5316 ptr++;
5318 char *end;
5319 const long pin = strtol(ptr, &end, 10);
5320 if (end != ptr && pin >= 0 && pin < 16) {
5321 *tag = DEFIO_TAG_MAKE(port, pin);
5323 return true;
5328 return false;
5331 #ifdef USE_DMA
5332 static void showDma(void)
5334 cliPrintLinefeed();
5336 #ifdef MINIMAL_CLI
5337 cliPrintLine("DMA:");
5338 #else
5339 cliPrintLine("Currently active DMA:");
5340 cliRepeat('-', 20);
5341 #endif
5342 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
5343 const resourceOwner_t *owner = dmaGetOwner(i);
5345 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
5346 if (owner->resourceIndex > 0) {
5347 cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
5348 } else {
5349 cliPrintLinef(" %s", ownerNames[owner->owner]);
5353 #endif
5355 #ifdef USE_DMA_SPEC
5357 typedef struct dmaoptEntry_s {
5358 char *device;
5359 dmaPeripheral_e peripheral;
5360 pgn_t pgn;
5361 uint8_t stride;
5362 uint8_t offset;
5363 uint8_t maxIndex;
5364 uint32_t presenceMask;
5365 } dmaoptEntry_t;
5367 #define MASK_IGNORED (0)
5369 // Handy macros for keeping the table tidy.
5370 // DEFS : Single entry
5371 // DEFA : Array of uint8_t (stride = 1)
5372 // DEFW : Wider stride case; array of structs.
5374 #define DEFS(device, peripheral, pgn, type, member) \
5375 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5377 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5378 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5380 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5381 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
5383 dmaoptEntry_t dmaoptEntryTable[] = {
5384 DEFW("SPI_MOSI", DMA_PERIPH_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5385 DEFW("SPI_MISO", DMA_PERIPH_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5386 // SPI_TX/SPI_RX for backwards compatibility with unified configs defined for 4.2.x
5387 DEFW("SPI_TX", DMA_PERIPH_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5388 DEFW("SPI_RX", DMA_PERIPH_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5389 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
5390 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
5391 DEFW("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5392 DEFW("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5393 #if defined(STM32H7) || defined(STM32G4)
5394 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
5395 #endif
5398 #undef DEFS
5399 #undef DEFA
5400 #undef DEFW
5402 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5403 #define DMA_OPT_STRING_BUFSIZE 5
5405 #if defined(STM32H7) || defined(STM32G4)
5406 #define DMA_CHANREQ_STRING "Request"
5407 #else
5408 #define DMA_CHANREQ_STRING "Channel"
5409 #endif
5411 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
5412 #define DMA_STCH_STRING "Stream"
5413 #else
5414 #define DMA_STCH_STRING "Channel"
5415 #endif
5417 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5419 static void optToString(int optval, char *buf)
5421 if (optval == DMA_OPT_UNUSED) {
5422 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
5423 } else {
5424 tfp_sprintf(buf, "%d", optval);
5428 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5430 // We compute number to display for different peripherals in advance.
5431 // This is done to deal with TIMUP which numbered non-contiguously.
5432 // Note that using timerGetNumberByIndex is not a generic solution,
5433 // but we are lucky that TIMUP is the only peripheral with non-contiguous numbering.
5435 int uiIndex;
5437 if (entry->presenceMask) {
5438 uiIndex = timerGetNumberByIndex(index);
5439 } else {
5440 uiIndex = DMA_OPT_UI_INDEX(index);
5443 if (dmaopt != DMA_OPT_UNUSED) {
5444 printValue(dumpMask, equalsDefault,
5445 "dma %s %d %d",
5446 entry->device, uiIndex, dmaopt);
5448 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5449 dmaCode_t dmaCode = 0;
5450 if (dmaChannelSpec) {
5451 dmaCode = dmaChannelSpec->code;
5453 printValue(dumpMask, equalsDefault,
5454 "# %s %d: " DMASPEC_FORMAT_STRING,
5455 entry->device, uiIndex, DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5456 } else if (!(dumpMask & HIDE_UNUSED)) {
5457 printValue(dumpMask, equalsDefault,
5458 "dma %s %d NONE",
5459 entry->device, uiIndex);
5463 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5465 const pgRegistry_t* pg = pgFind(entry->pgn);
5466 const void *currentConfig;
5467 const void *defaultConfig;
5469 if (isReadingConfigFromCopy()) {
5470 currentConfig = pg->copy;
5471 defaultConfig = pg->address;
5472 } else {
5473 currentConfig = pg->address;
5474 defaultConfig = NULL;
5477 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5478 dmaoptValue_t defaultOpt;
5480 if (defaultConfig) {
5481 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5482 } else {
5483 defaultOpt = DMA_OPT_UNUSED;
5486 bool equalsDefault = currentOpt == defaultOpt;
5487 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5489 if (defaultConfig) {
5490 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5493 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5494 return headingStr;
5497 #if defined(USE_TIMER_MGMT)
5498 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5500 const char *format = "dma pin %c%02d %d";
5502 if (dmaopt != DMA_OPT_UNUSED) {
5503 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5504 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5505 dmaopt
5508 if (printDetails) {
5509 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5510 dmaCode_t dmaCode = 0;
5511 if (dmaChannelSpec) {
5512 dmaCode = dmaChannelSpec->code;
5513 printValue(dumpMask, false,
5514 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5515 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5516 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5520 } else if (!(dumpMask & HIDE_UNUSED)) {
5521 printValue(dumpMask, equalsDefault,
5522 "dma pin %c%02d NONE",
5523 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5528 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5530 const ioTag_t ioTag = currentConfig[index].ioTag;
5532 if (!ioTag) {
5533 return headingStr;
5536 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5537 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5539 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5540 bool equalsDefault = defaultDmaopt == dmaopt;
5541 if (defaultConfig) {
5542 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5543 if (defaultConfig[i].ioTag == ioTag) {
5544 defaultDmaopt = defaultConfig[i].dmaopt;
5546 // 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.
5547 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5549 tagsInUse[index] = true;
5551 break;
5556 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5558 if (defaultConfig) {
5559 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5562 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5563 return headingStr;
5565 #endif
5567 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5569 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5570 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5571 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5572 for (int index = 0; index < entry->maxIndex; index++) {
5573 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5577 #if defined(USE_TIMER_MGMT)
5578 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5579 const timerIOConfig_t *currentConfig;
5580 const timerIOConfig_t *defaultConfig;
5582 if (isReadingConfigFromCopy()) {
5583 currentConfig = (timerIOConfig_t *)pg->copy;
5584 defaultConfig = (timerIOConfig_t *)pg->address;
5585 } else {
5586 currentConfig = (timerIOConfig_t *)pg->address;
5587 defaultConfig = NULL;
5590 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5591 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5592 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5595 if (defaultConfig) {
5596 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5597 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5598 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5599 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5600 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5602 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5606 #endif
5609 static void cliDmaopt(const char *cmdName, char *cmdline)
5611 char *pch = NULL;
5612 char *saveptr;
5614 // Peripheral name or command option
5615 pch = strtok_r(cmdline, " ", &saveptr);
5616 if (!pch) {
5617 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5619 return;
5620 } else if (strcasecmp(pch, "list") == 0) {
5621 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5623 return;
5626 dmaoptEntry_t *entry = NULL;
5627 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5628 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5629 entry = &dmaoptEntryTable[i];
5633 if (!entry && strcasecmp(pch, "pin") != 0) {
5634 cliPrintErrorLinef(cmdName, "BAD DEVICE: %s", pch);
5635 return;
5638 // Index
5639 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5641 int index = 0;
5642 dmaoptValue_t *optaddr = NULL;
5644 ioTag_t ioTag = IO_TAG_NONE;
5645 #if defined(USE_TIMER_MGMT)
5646 timerIOConfig_t *timerIoConfig = NULL;
5647 #endif
5648 const timerHardware_t *timer = NULL;
5649 pch = strtok_r(NULL, " ", &saveptr);
5650 if (entry) {
5651 index = pch ? (atoi(pch) - 1) : -1;
5652 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5653 cliPrintErrorLinef(cmdName, "BAD INDEX: '%s'", pch ? pch : "");
5654 return;
5657 const pgRegistry_t* pg = pgFind(entry->pgn);
5658 const void *currentConfig;
5659 if (isWritingConfigToCopy()) {
5660 currentConfig = pg->copy;
5661 } else {
5662 currentConfig = pg->address;
5664 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5665 orgval = *optaddr;
5666 } else {
5667 // It's a pin
5668 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5669 cliPrintErrorLinef(cmdName, "INVALID PIN: '%s'", pch ? pch : "");
5671 return;
5674 orgval = dmaoptByTag(ioTag);
5675 #if defined(USE_TIMER_MGMT)
5676 timerIoConfig = timerIoConfigByTag(ioTag);
5677 #endif
5678 timer = timerGetConfiguredByTag(ioTag);
5681 // opt or list
5682 pch = strtok_r(NULL, " ", &saveptr);
5683 if (!pch) {
5684 if (entry) {
5685 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5687 #if defined(USE_TIMER_MGMT)
5688 else {
5689 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5691 #endif
5693 return;
5694 } else if (strcasecmp(pch, "list") == 0) {
5695 // Show possible opts
5696 const dmaChannelSpec_t *dmaChannelSpec;
5697 if (entry) {
5698 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5699 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5701 } else {
5702 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5703 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5707 return;
5708 } else if (pch) {
5709 int optval;
5710 if (strcasecmp(pch, "none") == 0) {
5711 optval = DMA_OPT_UNUSED;
5712 } else {
5713 optval = atoi(pch);
5715 if (entry) {
5716 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5717 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5719 return;
5721 } else {
5722 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5723 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5725 return;
5730 char optvalString[DMA_OPT_STRING_BUFSIZE];
5731 optToString(optval, optvalString);
5733 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5734 optToString(orgval, orgvalString);
5736 if (optval != orgval) {
5737 if (entry) {
5738 *optaddr = optval;
5740 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5741 } else {
5742 #if defined(USE_TIMER_MGMT)
5743 timerIoConfig->dmaopt = optval;
5744 #endif
5746 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5748 } else {
5749 if (entry) {
5750 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5751 } else {
5752 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5757 #endif // USE_DMA_SPEC
5759 #ifdef USE_DMA
5760 static void cliDma(const char *cmdName, char* cmdline)
5762 int len = strlen(cmdline);
5763 if (len && strncasecmp(cmdline, "show", len) == 0) {
5764 showDma();
5766 return;
5769 #if defined(USE_DMA_SPEC)
5770 cliDmaopt(cmdName, cmdline);
5771 #else
5772 cliShowParseError(cmdName);
5773 #endif
5775 #endif
5776 #endif // USE_RESOURCE_MGMT
5778 #ifdef USE_TIMER_MGMT
5779 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5781 const char *format = "timer %c%02d AF%d";
5782 const char *emptyFormat = "timer %c%02d NONE";
5784 if (timerIndex > 0) {
5785 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5786 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5787 IO_GPIOPortIdxByTag(ioTag) + 'A',
5788 IO_GPIOPinIdxByTag(ioTag),
5789 timer->alternateFunction
5791 if (printDetails) {
5792 printValue(dumpMask, false,
5793 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5794 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5795 timerGetTIMNumber(timer->tim),
5796 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5797 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5798 timer->alternateFunction
5801 } else {
5802 printValue(dumpMask, equalsDefault, emptyFormat,
5803 IO_GPIOPortIdxByTag(ioTag) + 'A',
5804 IO_GPIOPinIdxByTag(ioTag)
5809 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5811 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5812 const timerIOConfig_t *currentConfig;
5813 const timerIOConfig_t *defaultConfig;
5815 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5816 if (isReadingConfigFromCopy()) {
5817 currentConfig = (timerIOConfig_t *)pg->copy;
5818 defaultConfig = (timerIOConfig_t *)pg->address;
5819 } else {
5820 currentConfig = (timerIOConfig_t *)pg->address;
5821 defaultConfig = NULL;
5824 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5825 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5826 const ioTag_t ioTag = currentConfig[i].ioTag;
5828 if (!ioTag) {
5829 continue;
5832 const uint8_t timerIndex = currentConfig[i].index;
5834 uint8_t defaultTimerIndex = 0;
5835 if (defaultConfig) {
5836 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5837 if (defaultConfig[i].ioTag == ioTag) {
5838 defaultTimerIndex = defaultConfig[i].index;
5839 tagsInUse[i] = true;
5841 break;
5846 const bool equalsDefault = defaultTimerIndex == timerIndex;
5847 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5848 if (defaultConfig && defaultTimerIndex) {
5849 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5852 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5855 if (defaultConfig) {
5856 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5857 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5858 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5859 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5861 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5867 #define TIMER_INDEX_UNDEFINED -1
5868 #define TIMER_AF_STRING_BUFSIZE 5
5870 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5872 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5873 if (!timer) {
5874 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5875 } else {
5876 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5880 static void showTimers(void)
5882 cliPrintLinefeed();
5884 #ifdef MINIMAL_CLI
5885 cliPrintLine("Timers:");
5886 #else
5887 cliPrintLine("Currently active Timers:");
5888 cliRepeat('-', 23);
5889 #endif
5891 int8_t timerNumber;
5892 for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
5893 cliPrintf("TIM%d:", timerNumber);
5894 bool timerUsed = false;
5895 for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
5896 const timerHardware_t *timer = timerGetAllocatedByNumberAndChannel(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
5897 const resourceOwner_t *timerOwner = timerGetOwner(timer);
5898 if (timerOwner->owner) {
5899 if (!timerUsed) {
5900 timerUsed = true;
5902 cliPrintLinefeed();
5905 if (timerOwner->resourceIndex > 0) {
5906 cliPrintLinef(" CH%d%s: %s %d", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner], timerOwner->resourceIndex);
5907 } else {
5908 cliPrintLinef(" CH%d%s: %s", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner]);
5913 if (!timerUsed) {
5914 cliPrintLine(" FREE");
5919 static void cliTimer(const char *cmdName, char *cmdline)
5921 int len = strlen(cmdline);
5923 if (len == 0) {
5924 printTimer(DUMP_MASTER, NULL);
5926 return;
5927 } else if (strncasecmp(cmdline, "list", len) == 0) {
5928 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5930 return;
5931 } else if (strncasecmp(cmdline, "show", len) == 0) {
5932 showTimers();
5934 return;
5937 char *pch = NULL;
5938 char *saveptr;
5940 ioTag_t ioTag = IO_TAG_NONE;
5941 pch = strtok_r(cmdline, " ", &saveptr);
5942 if (!pch || !strToPin(pch, &ioTag)) {
5943 cliShowParseError(cmdName);
5945 return;
5946 } else if (!IOGetByTag(ioTag)) {
5947 cliPrintErrorLinef(cmdName, "PIN NOT USED ON BOARD.");
5949 return;
5952 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5953 bool isExistingTimerOpt = false;
5954 /* find existing entry, or go for next available */
5955 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5956 if (timerIOConfig(i)->ioTag == ioTag) {
5957 timerIOIndex = i;
5958 isExistingTimerOpt = true;
5960 break;
5963 /* first available empty slot */
5964 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5965 timerIOIndex = i;
5969 if (timerIOIndex < 0) {
5970 cliPrintErrorLinef(cmdName, "PIN TIMER MAP FULL.");
5972 return;
5975 pch = strtok_r(NULL, " ", &saveptr);
5976 if (pch) {
5977 int timerIndex = TIMER_INDEX_UNDEFINED;
5978 if (strcasecmp(pch, "list") == 0) {
5979 /* output the list of available options */
5980 const timerHardware_t *timer;
5981 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5982 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5983 timer->alternateFunction,
5984 timerGetTIMNumber(timer->tim),
5985 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5986 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
5990 return;
5991 } else if (strncasecmp(pch, "af", 2) == 0) {
5992 unsigned alternateFunction = atoi(&pch[2]);
5994 const timerHardware_t *timer;
5995 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5996 if (timer->alternateFunction == alternateFunction) {
5997 timerIndex = index;
5999 break;
6003 if (!timer) {
6004 cliPrintErrorLinef(cmdName, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
6006 return;
6008 } else if (strcasecmp(pch, "none") != 0) {
6009 cliPrintErrorLinef(cmdName, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
6011 return;
6014 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
6015 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
6016 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
6017 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
6019 char optvalString[DMA_OPT_STRING_BUFSIZE];
6020 alternateFunctionToString(ioTag, timerIndex, optvalString);
6022 char orgvalString[DMA_OPT_STRING_BUFSIZE];
6023 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
6025 if (timerIndex == oldTimerIndex) {
6026 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
6027 } else {
6028 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
6031 return;
6032 } else {
6033 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
6035 return;
6038 #endif
6040 #if defined(USE_RESOURCE_MGMT)
6041 static void cliResource(const char *cmdName, char *cmdline)
6043 char *pch = NULL;
6044 char *saveptr;
6046 pch = strtok_r(cmdline, " ", &saveptr);
6047 if (!pch) {
6048 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
6050 return;
6051 } else if (strcasecmp(pch, "show") == 0) {
6052 #ifdef MINIMAL_CLI
6053 cliPrintLine("IO");
6054 #else
6055 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
6056 cliRepeat('-', 20);
6057 #endif
6058 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
6059 const char* owner;
6060 owner = ownerNames[ioRecs[i].owner];
6062 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
6063 if (ioRecs[i].index > 0) {
6064 cliPrintf(" %d", ioRecs[i].index);
6066 cliPrintLinefeed();
6069 pch = strtok_r(NULL, " ", &saveptr);
6070 if (strcasecmp(pch, "all") == 0) {
6071 #if defined(USE_TIMER_MGMT)
6072 cliTimer(cmdName, "show");
6073 #endif
6074 #if defined(USE_DMA)
6075 cliDma(cmdName, "show");
6076 #endif
6079 return;
6082 unsigned resourceIndex = 0;
6083 for (; ; resourceIndex++) {
6084 if (resourceIndex >= ARRAYLEN(resourceTable)) {
6085 cliPrintErrorLinef(cmdName, "INVALID RESOURCE NAME: '%s'", pch);
6086 return;
6089 const char *resourceName = ownerNames[resourceTable[resourceIndex].owner];
6090 if (strncasecmp(pch, resourceName, strlen(resourceName)) == 0) {
6091 break;
6095 pch = strtok_r(NULL, " ", &saveptr);
6096 int index = atoi(pch);
6098 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
6099 if (index <= 0 || index > RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex)) {
6100 cliShowArgumentRangeError(cmdName, "INDEX", 1, RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex));
6101 return;
6103 index -= 1;
6105 pch = strtok_r(NULL, " ", &saveptr);
6108 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
6110 if (strlen(pch) > 0) {
6111 if (strToPin(pch, tag)) {
6112 if (*tag == IO_TAG_NONE) {
6113 #ifdef MINIMAL_CLI
6114 cliPrintLine("Freed");
6115 #else
6116 cliPrintLine("Resource is freed");
6117 #endif
6118 return;
6119 } else {
6120 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
6121 if (rec) {
6122 resourceCheck(resourceIndex, index, *tag);
6123 #ifdef MINIMAL_CLI
6124 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6125 #else
6126 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6127 #endif
6128 } else {
6129 cliShowParseError(cmdName);
6131 return;
6136 cliShowParseError(cmdName);
6138 #endif
6140 #ifdef USE_DSHOT_TELEMETRY
6143 static void cliDshotTelemetryInfo(const char *cmdName, char *cmdline)
6145 UNUSED(cmdName);
6146 UNUSED(cmdline);
6148 if (useDshotTelemetry) {
6149 cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
6150 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
6151 int32_t directionChangeCycles = cmp32(dshotDMAHandlerCycleCounters.changeDirectionCompletedAt, dshotDMAHandlerCycleCounters.irqAt);
6152 int32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
6153 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
6154 cliPrintLinefeed();
6156 #ifdef USE_DSHOT_TELEMETRY_STATS
6157 cliPrintLine("Motor Type eRPM RPM Hz Invalid TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6158 cliPrintLine("===== ====== ====== ====== ====== ======= ====== ====== ====== ====== ====== ====== ======");
6159 #else
6160 cliPrintLine("Motor Type eRPM RPM Hz TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6161 cliPrintLine("===== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======");
6162 #endif
6164 for (uint8_t i = 0; i < getMotorCount(); i++) {
6165 const uint16_t erpm = getDshotTelemetry(i);
6166 const uint16_t rpm = erpmToRpm(erpm);
6168 cliPrintf("%5d %c%c%c%c%c %6d %6d %6d",
6169 i + 1,
6170 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_eRPM)) ? 'R' : '-'),
6171 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE)) ? 'T' : '-'),
6172 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_VOLTAGE)) ? 'V' : '-'),
6173 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_CURRENT)) ? 'C' : '-'),
6174 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_STATE_EVENTS)) ? 'S' : '-'),
6175 erpm * 100, rpm, rpm / 60);
6177 #ifdef USE_DSHOT_TELEMETRY_STATS
6178 if (isDshotMotorTelemetryActive(i)) {
6179 int32_t calcPercent = getDshotTelemetryMotorInvalidPercent(i);
6180 cliPrintf(" %3d.%02d%%", calcPercent / 100, calcPercent % 100);
6181 } else {
6182 cliPrint(" NO DATA");
6184 #endif
6186 cliPrintLinef(" %6d %3d.%02d %6d %6d %6d %6d %6d",
6187 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_TEMPERATURE],
6188 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] / 4,
6189 25 * (dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] % 4),
6190 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_CURRENT],
6191 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_STATE_EVENTS],
6192 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG1],
6193 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG2],
6194 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG3]
6197 cliPrintLinefeed();
6199 const int len = MAX_GCR_EDGES;
6200 #ifdef DEBUG_BBDECODE
6201 extern uint16_t bbBuffer[134];
6202 for (int i = 0; i < 134; i++) {
6203 cliPrintf("%u ", (int)bbBuffer[i]);
6205 cliPrintLinefeed();
6206 #endif
6207 for (int i = 0; i < len; i++) {
6208 cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
6210 cliPrintLinefeed();
6211 for (int i = 1; i < len; i++) {
6212 cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
6214 cliPrintLinefeed();
6215 } else {
6216 cliPrintLine("Dshot telemetry not enabled");
6220 #endif
6222 static void printConfig(const char *cmdName, char *cmdline, bool doDiff)
6224 dumpFlags_t dumpMask = DUMP_MASTER;
6225 char *options;
6226 if ((options = checkCommand(cmdline, "master"))) {
6227 dumpMask = DUMP_MASTER; // only
6228 } else if ((options = checkCommand(cmdline, "profile"))) {
6229 dumpMask = DUMP_PROFILE; // only
6230 } else if ((options = checkCommand(cmdline, "rates"))) {
6231 dumpMask = DUMP_RATES; // only
6232 } else if ((options = checkCommand(cmdline, "hardware"))) {
6233 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
6234 } else if ((options = checkCommand(cmdline, "all"))) {
6235 dumpMask = DUMP_ALL; // all profiles and rates
6236 } else {
6237 options = cmdline;
6240 if (doDiff) {
6241 dumpMask = dumpMask | DO_DIFF;
6244 if (checkCommand(options, "defaults")) {
6245 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
6246 } else if (checkCommand(options, "bare")) {
6247 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
6250 backupAndResetConfigs((dumpMask & BARE) == 0);
6252 #ifdef USE_CLI_BATCH
6253 bool batchModeEnabled = false;
6254 #endif
6255 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
6256 cliPrintHashLine("version");
6257 printVersion(cmdName, false);
6259 if (!(dumpMask & BARE)) {
6260 #ifdef USE_CLI_BATCH
6261 cliPrintHashLine("start the command batch");
6262 cliPrintLine("batch start");
6263 batchModeEnabled = true;
6264 #endif
6266 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
6267 cliPrintHashLine("reset configuration to default settings");
6268 cliPrintLine("defaults nosave");
6272 #if defined(USE_BOARD_INFO)
6273 cliPrintLinefeed();
6274 printBoardName(dumpMask);
6275 printManufacturerId(dumpMask);
6276 #endif
6278 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
6279 cliMcuId(cmdName, "");
6280 #if defined(USE_SIGNATURE)
6281 cliSignature(cmdName, "");
6282 #endif
6285 if (!(dumpMask & HARDWARE_ONLY)) {
6286 printCraftName(dumpMask, &pilotConfig_Copy);
6289 #ifdef USE_RESOURCE_MGMT
6290 printResource(dumpMask, "resources");
6291 #if defined(USE_TIMER_MGMT)
6292 printTimer(dumpMask, "timer");
6293 #endif
6294 #ifdef USE_DMA_SPEC
6295 printDmaopt(dumpMask, "dma");
6296 #endif
6297 #endif
6299 printFeature(dumpMask, featureConfig_Copy.enabledFeatures, featureConfig()->enabledFeatures, "feature");
6301 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
6303 if (!(dumpMask & HARDWARE_ONLY)) {
6304 #ifndef USE_QUAD_MIXER_ONLY
6305 const char *mixerHeadingStr = "mixer";
6306 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
6307 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
6308 const char *formatMixer = "mixer %s";
6309 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
6310 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
6312 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
6314 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
6316 #ifdef USE_SERVOS
6317 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
6319 const char *servoMixHeadingStr = "servo mixer";
6320 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
6321 cliPrintHashLine(servoMixHeadingStr);
6322 cliPrintLine("smix reset\r\n");
6323 servoMixHeadingStr = NULL;
6325 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
6326 #endif
6327 #endif
6329 #if defined(USE_BEEPER)
6330 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
6332 #if defined(USE_DSHOT)
6333 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
6334 #endif
6335 #endif // USE_BEEPER
6337 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
6339 #ifdef USE_LED_STRIP_STATUS_MODE
6340 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
6342 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
6344 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
6345 #endif
6347 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
6349 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
6351 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
6353 #ifdef USE_VTX_TABLE
6354 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
6355 #endif
6357 #ifdef USE_VTX_CONTROL
6358 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
6359 #endif
6361 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
6364 if (dumpMask & HARDWARE_ONLY) {
6365 dumpAllValues(cmdName, HARDWARE_VALUE, dumpMask, "master");
6366 } else {
6367 dumpAllValues(cmdName, MASTER_VALUE, dumpMask, "master");
6369 if (dumpMask & DUMP_ALL) {
6370 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
6371 cliDumpPidProfile(cmdName, pidProfileIndex, dumpMask);
6374 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
6376 if (!(dumpMask & BARE)) {
6377 cliPrintHashLine("restore original profile selection");
6379 cliProfile(cmdName, "");
6382 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
6384 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
6385 cliDumpRateProfile(cmdName, rateIndex, dumpMask);
6388 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
6390 if (!(dumpMask & BARE)) {
6391 cliPrintHashLine("restore original rateprofile selection");
6393 cliRateProfile(cmdName, "");
6395 cliPrintHashLine("save configuration");
6396 cliPrint("save");
6397 #ifdef USE_CLI_BATCH
6398 batchModeEnabled = false;
6399 #endif
6402 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
6403 } else {
6404 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6406 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6409 } else if (dumpMask & DUMP_PROFILE) {
6410 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6411 } else if (dumpMask & DUMP_RATES) {
6412 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6415 #ifdef USE_CLI_BATCH
6416 if (batchModeEnabled) {
6417 cliPrintHashLine("end the command batch");
6418 cliPrintLine("batch end");
6420 #endif
6422 // restore configs from copies
6423 restoreConfigs(0);
6426 static void cliDump(const char *cmdName, char *cmdline)
6428 printConfig(cmdName, cmdline, false);
6431 static void cliDiff(const char *cmdName, char *cmdline)
6433 printConfig(cmdName, cmdline, true);
6436 #if defined(USE_USB_MSC)
6437 static void cliMsc(const char *cmdName, char *cmdline)
6439 if (mscCheckFilesystemReady()) {
6440 #ifdef USE_RTC_TIME
6441 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
6442 if (!isEmpty(cmdline)) {
6443 timezoneOffsetMinutes = atoi(cmdline);
6444 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
6445 cliPrintErrorLinef(cmdName, "INVALID TIMEZONE OFFSET");
6446 return;
6449 #else
6450 int timezoneOffsetMinutes = 0;
6451 UNUSED(cmdline);
6452 #endif
6453 cliPrintHashLine("Restarting in mass storage mode");
6454 cliPrint("\r\nRebooting");
6455 cliWriterFlush();
6456 waitForSerialPortToFinishTransmitting(cliPort);
6457 motorShutdown();
6459 systemResetToMsc(timezoneOffsetMinutes);
6460 } else {
6461 cliPrintHashLine("Storage not present or failed to initialize!");
6464 #endif
6466 typedef void cliCommandFn(const char* name, char *cmdline);
6468 typedef struct {
6469 const char *name;
6470 #ifndef MINIMAL_CLI
6471 const char *description;
6472 const char *args;
6473 #endif
6474 cliCommandFn *cliCommand;
6475 } clicmd_t;
6477 #ifndef MINIMAL_CLI
6478 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6480 name , \
6481 description , \
6482 args , \
6483 cliCommand \
6485 #else
6486 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6488 name, \
6489 cliCommand \
6491 #endif
6493 static void cliHelp(const char *cmdName, char *cmdline);
6495 // should be sorted a..z for bsearch()
6496 const clicmd_t cmdTable[] = {
6497 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
6498 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
6499 #ifdef USE_CLI_BATCH
6500 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
6501 #endif
6502 #if defined(USE_BEEPER)
6503 #if defined(USE_DSHOT)
6504 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6505 "\t<->[name]", cliBeacon),
6506 #endif
6507 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6508 "\t<->[name]", cliBeeper),
6509 #endif // USE_BEEPER
6510 #if defined(USE_RX_BIND)
6511 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
6512 #endif
6513 #if defined(USE_FLASH_BOOT_LOADER)
6514 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6515 #else
6516 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6517 #endif
6518 #if defined(USE_BOARD_INFO)
6519 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6520 #endif
6521 #ifdef USE_LED_STRIP_STATUS_MODE
6522 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6523 #endif
6524 #if defined(USE_CUSTOM_DEFAULTS)
6525 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{show} {nosave} {bare} {group_id <id>}", cliDefaults),
6526 #else
6527 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{nosave}", cliDefaults),
6528 #endif
6529 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6530 #ifdef USE_RESOURCE_MGMT
6532 #ifdef USE_DMA
6533 #ifdef USE_DMA_SPEC
6534 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6535 #else
6536 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6537 #endif
6538 #endif
6540 #endif
6541 #ifdef USE_DSHOT_TELEMETRY
6542 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6543 #endif
6544 #ifdef USE_DSHOT
6545 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6546 #endif
6547 CLI_COMMAND_DEF("dump", "dump configuration",
6548 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6549 #ifdef USE_ESCSERIAL
6550 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6551 #endif
6552 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
6553 CLI_COMMAND_DEF("feature", "configure features",
6554 "list\r\n"
6555 "\t<->[name]", cliFeature),
6556 #ifdef USE_FLASHFS
6557 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6558 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6559 #ifdef USE_FLASH_TOOLS
6560 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6561 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6562 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6563 #endif
6564 #endif
6565 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6566 #ifdef USE_GPS
6567 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6568 #endif
6569 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6570 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6571 #endif
6572 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp),
6573 #ifdef USE_LED_STRIP_STATUS_MODE
6574 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6575 #endif
6576 #if defined(USE_BOARD_INFO)
6577 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6578 #endif
6579 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6580 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6581 #ifndef USE_QUAD_MIXER_ONLY
6582 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6583 #endif
6584 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6585 #ifdef USE_LED_STRIP_STATUS_MODE
6586 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6587 #endif
6588 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6589 #ifdef USE_USB_MSC
6590 #ifdef USE_RTC_TIME
6591 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6592 #else
6593 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6594 #endif
6595 #endif
6596 #ifndef MINIMAL_CLI
6597 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6598 #endif
6599 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6600 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6601 #ifdef USE_RC_SMOOTHING_FILTER
6602 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6603 #endif // USE_RC_SMOOTHING_FILTER
6604 #ifdef USE_RESOURCE_MGMT
6605 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6606 #endif
6607 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6608 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6609 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
6610 #ifdef USE_SDCARD
6611 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6612 #endif
6613 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6614 #if defined(USE_SERIAL_PASSTHROUGH)
6615 #if defined(USE_PINIO)
6616 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),
6617 #else
6618 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6619 #endif
6620 #endif
6621 #ifdef USE_SERVOS
6622 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6623 #endif
6624 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6625 #if defined(USE_SIGNATURE)
6626 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6627 #endif
6628 #if defined(USE_SIMPLIFIED_TUNING)
6629 CLI_COMMAND_DEF("simplified_tuning", "applies or disables simplified tuning", "apply | disable", cliSimplifiedTuning),
6630 #endif
6631 #ifdef USE_SERVOS
6632 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6633 "\treset\r\n"
6634 "\tload <mixer>\r\n"
6635 "\treverse <servo> <source> r|n", cliServoMix),
6636 #endif
6637 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6638 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6639 #ifdef USE_TIMER_MGMT
6640 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6641 #endif
6642 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6643 #ifdef USE_VTX_CONTROL
6644 #ifdef MINIMAL_CLI
6645 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6646 #else
6647 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6648 #endif
6649 #endif
6650 #ifdef USE_VTX_TABLE
6651 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL, cliVtxInfo),
6652 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6653 #endif
6656 static void cliHelp(const char *cmdName, char *cmdline)
6658 bool anyMatches = false;
6660 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6661 bool printEntry = false;
6662 if (isEmpty(cmdline)) {
6663 printEntry = true;
6664 } else {
6665 if (strcasestr(cmdTable[i].name, cmdline)
6666 #ifndef MINIMAL_CLI
6667 || strcasestr(cmdTable[i].description, cmdline)
6668 #endif
6670 printEntry = true;
6674 if (printEntry) {
6675 anyMatches = true;
6676 cliPrint(cmdTable[i].name);
6677 #ifndef MINIMAL_CLI
6678 if (cmdTable[i].description) {
6679 cliPrintf(" - %s", cmdTable[i].description);
6681 if (cmdTable[i].args) {
6682 cliPrintf("\r\n\t%s", cmdTable[i].args);
6684 #endif
6685 cliPrintLinefeed();
6688 if (!isEmpty(cmdline) && !anyMatches) {
6689 cliPrintErrorLinef(cmdName, "NO MATCHES FOR '%s'", cmdline);
6693 static void processCharacter(const char c)
6695 if (bufferIndex && (c == '\n' || c == '\r')) {
6696 // enter pressed
6697 cliPrintLinefeed();
6699 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
6700 if (processingCustomDefaults) {
6701 cliPrint("d: ");
6703 #endif
6705 // Strip comment starting with # from line
6706 char *p = cliBuffer;
6707 p = strchr(p, '#');
6708 if (NULL != p) {
6709 bufferIndex = (uint32_t)(p - cliBuffer);
6712 // Strip trailing whitespace
6713 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6714 bufferIndex--;
6717 // Process non-empty lines
6718 if (bufferIndex > 0) {
6719 cliBuffer[bufferIndex] = 0; // null terminate
6721 const clicmd_t *cmd;
6722 char *options;
6723 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6724 if ((options = checkCommand(cliBuffer, cmd->name))) {
6725 break;
6728 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6729 cmd->cliCommand(cmd->name, options);
6730 } else {
6731 cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
6733 bufferIndex = 0;
6736 memset(cliBuffer, 0, sizeof(cliBuffer));
6738 // 'exit' will reset this flag, so we don't need to print prompt again
6739 if (!cliMode) {
6740 return;
6743 cliPrompt();
6744 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6745 if (!bufferIndex && c == ' ')
6746 return; // Ignore leading spaces
6747 cliBuffer[bufferIndex++] = c;
6748 cliWrite(c);
6752 static void processCharacterInteractive(const char c)
6754 if (c == '\t' || c == '?') {
6755 // do tab completion
6756 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6757 uint32_t i = bufferIndex;
6758 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6759 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) {
6760 continue;
6762 if (!pstart) {
6763 pstart = cmd;
6765 pend = cmd;
6767 if (pstart) { /* Buffer matches one or more commands */
6768 for (; ; bufferIndex++) {
6769 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6770 break;
6771 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6772 /* Unambiguous -- append a space */
6773 cliBuffer[bufferIndex++] = ' ';
6774 cliBuffer[bufferIndex] = '\0';
6775 break;
6777 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6780 if (!bufferIndex || pstart != pend) {
6781 /* Print list of ambiguous matches */
6782 cliPrint("\r\n\033[K");
6783 for (cmd = pstart; cmd <= pend; cmd++) {
6784 cliPrint(cmd->name);
6785 cliWrite('\t');
6787 cliPrompt();
6788 i = 0; /* Redraw prompt */
6790 for (; i < bufferIndex; i++)
6791 cliWrite(cliBuffer[i]);
6792 } else if (!bufferIndex && c == 4) { // CTRL-D
6793 cliExit("", cliBuffer);
6794 return;
6795 } else if (c == 12) { // NewPage / CTRL-L
6796 // clear screen
6797 cliPrint("\033[2J\033[1;1H");
6798 cliPrompt();
6799 } else if (c == 127) {
6800 // backspace
6801 if (bufferIndex) {
6802 cliBuffer[--bufferIndex] = 0;
6803 cliPrint("\010 \010");
6805 } else {
6806 processCharacter(c);
6810 void cliProcess(void)
6812 if (!cliWriter) {
6813 return;
6816 // Flush the buffer to get rid of any MSP data polls sent by configurator after CLI was invoked
6817 cliWriterFlush();
6819 while (serialRxBytesWaiting(cliPort)) {
6820 uint8_t c = serialRead(cliPort);
6822 processCharacterInteractive(c);
6826 #if defined(USE_CUSTOM_DEFAULTS)
6827 static bool cliProcessCustomDefaults(bool quiet)
6829 if (processingCustomDefaults || !hasCustomDefaults()) {
6830 return false;
6833 bufWriter_t *cliWriterTemp = NULL;
6834 if (quiet
6835 #if !defined(DEBUG_CUSTOM_DEFAULTS)
6836 || true
6837 #endif
6839 cliWriterTemp = cliWriter;
6840 cliWriter = NULL;
6842 if (quiet) {
6843 cliErrorWriter = NULL;
6846 memcpy(cliBufferTemp, cliBuffer, sizeof(cliBuffer));
6847 uint32_t bufferIndexTemp = bufferIndex;
6848 bufferIndex = 0;
6849 processingCustomDefaults = true;
6851 char *customDefaultsPtr = customDefaultsStart;
6852 while (customDefaultsHasNext(customDefaultsPtr)) {
6853 processCharacter(*customDefaultsPtr++);
6856 // Process a newline at the very end so that the last command gets executed,
6857 // even when the file did not contain a trailing newline
6858 processCharacter('\r');
6860 processingCustomDefaults = false;
6862 if (cliWriterTemp) {
6863 cliWriter = cliWriterTemp;
6864 cliErrorWriter = cliWriter;
6867 memcpy(cliBuffer, cliBufferTemp, sizeof(cliBuffer));
6868 bufferIndex = bufferIndexTemp;
6870 systemConfigMutable()->configurationState = CONFIGURATION_STATE_DEFAULTS_CUSTOM;
6872 return true;
6874 #endif
6876 void cliEnter(serialPort_t *serialPort)
6878 cliMode = true;
6879 cliPort = serialPort;
6880 setPrintfSerialPort(cliPort);
6881 bufWriterInit(&cliWriterDesc, cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6882 cliErrorWriter = cliWriter = &cliWriterDesc;
6884 #ifndef MINIMAL_CLI
6885 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6886 #else
6887 cliPrintLine("\r\nCLI");
6888 #endif
6889 setArmingDisabled(ARMING_DISABLED_CLI);
6891 cliPrompt();
6893 #ifdef USE_CLI_BATCH
6894 resetCommandBatch();
6895 #endif
6898 #endif // USE_CLI