Merge pull request #11198 from SteveCEvans/sce_rc2
[betaflight.git] / src / main / cli / cli.c
bloba11b1394a94a2aa6fa44a4fad5943250ba690fb5
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/esc_sensor.h"
169 #include "sensors/gyro.h"
170 #include "sensors/gyro_init.h"
171 #include "sensors/sensors.h"
173 #include "telemetry/frsky_hub.h"
174 #include "telemetry/telemetry.h"
176 #include "cli.h"
178 static serialPort_t *cliPort = NULL;
180 #ifdef STM32F1
181 #define CLI_IN_BUFFER_SIZE 128
182 #else
183 // Space required to set array parameters
184 #define CLI_IN_BUFFER_SIZE 256
185 #endif
186 #define CLI_OUT_BUFFER_SIZE 64
188 static bufWriter_t *cliWriter = NULL;
189 static bufWriter_t *cliErrorWriter = NULL;
190 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
192 static char cliBuffer[CLI_IN_BUFFER_SIZE];
193 static uint32_t bufferIndex = 0;
195 static bool configIsInCopy = false;
197 #define CURRENT_PROFILE_INDEX -1
198 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
199 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
201 #ifdef USE_CLI_BATCH
202 static bool commandBatchActive = false;
203 static bool commandBatchError = false;
204 #endif
206 #if defined(USE_BOARD_INFO)
207 static bool boardInformationUpdated = false;
208 #if defined(USE_SIGNATURE)
209 static bool signatureUpdated = false;
210 #endif
211 #endif // USE_BOARD_INFO
213 static const char* const emptyName = "-";
214 static const char* const emptyString = "";
216 #if !defined(USE_CUSTOM_DEFAULTS)
217 #define CUSTOM_DEFAULTS_START ((char*)0)
218 #define CUSTOM_DEFAULTS_END ((char *)0)
219 #else
220 extern char __custom_defaults_start;
221 extern char __custom_defaults_end;
222 #define CUSTOM_DEFAULTS_START (&__custom_defaults_start)
223 #define CUSTOM_DEFAULTS_END (&__custom_defaults_end)
225 static bool processingCustomDefaults = false;
226 static char cliBufferTemp[CLI_IN_BUFFER_SIZE];
228 #define CUSTOM_DEFAULTS_START_PREFIX ("# " FC_FIRMWARE_NAME)
229 #define CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX "# config: manufacturer_id: "
230 #define CUSTOM_DEFAULTS_BOARD_NAME_PREFIX ", board_name: "
231 #define CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX ", version: "
232 #define CUSTOM_DEFAULTS_DATE_PREFIX ", date: "
234 #define MAX_CHANGESET_ID_LENGTH 8
235 #define MAX_DATE_LENGTH 20
237 static bool customDefaultsHeaderParsed = false;
238 static bool customDefaultsFound = false;
239 static char customDefaultsManufacturerId[MAX_MANUFACTURER_ID_LENGTH + 1] = { 0 };
240 static char customDefaultsBoardName[MAX_BOARD_NAME_LENGTH + 1] = { 0 };
241 static char customDefaultsChangesetId[MAX_CHANGESET_ID_LENGTH + 1] = { 0 };
242 static char customDefaultsDate[MAX_DATE_LENGTH + 1] = { 0 };
243 #endif
245 #if defined(USE_CUSTOM_DEFAULTS_ADDRESS)
246 static char __attribute__ ((section(".custom_defaults_start_address"))) *customDefaultsStart = CUSTOM_DEFAULTS_START;
247 static char __attribute__ ((section(".custom_defaults_end_address"))) *customDefaultsEnd = CUSTOM_DEFAULTS_END;
248 #endif
250 #ifndef USE_QUAD_MIXER_ONLY
251 // sync this with mixerMode_e
252 static const char * const mixerNames[] = {
253 "TRI", "QUADP", "QUADX", "BI",
254 "GIMBAL", "Y6", "HEX6",
255 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
256 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
257 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
258 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
260 #endif
262 // sync this with features_e
263 static const char * const featureNames[] = {
264 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
265 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
266 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
267 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
268 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
269 "", "", "RX_SPI", "", "ESC_SENSOR", "ANTI_GRAVITY", "", NULL
272 // sync this with rxFailsafeChannelMode_e
273 static const char rxFailsafeModeCharacters[] = "ahs";
275 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
276 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
277 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
280 #if defined(USE_SENSOR_NAMES)
281 // sync this with sensors_e
282 static const char *const sensorTypeNames[] = {
283 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
286 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
288 static const char * const *sensorHardwareNames[] = {
289 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
291 #endif // USE_SENSOR_NAMES
293 // Needs to be aligned with mcuTypeId_e
294 static const char *mcuTypeNames[] = {
295 "SIMULATOR",
296 "F103",
297 "F303",
298 "F40X",
299 "F411",
300 "F446",
301 "F722",
302 "F745",
303 "F746",
304 "F765",
305 "H750",
306 "H743 (Rev Unknown)",
307 "H743 (Rev.Y)",
308 "H743 (Rev.X)",
309 "H743 (Rev.V)",
310 "H7A3",
311 "H723/H725",
312 "G474",
313 "H730",
316 static const char *configurationStates[] = { "UNCONFIGURED", "CUSTOM DEFAULTS", "CONFIGURED" };
318 typedef enum dumpFlags_e {
319 DUMP_MASTER = (1 << 0),
320 DUMP_PROFILE = (1 << 1),
321 DUMP_RATES = (1 << 2),
322 DUMP_ALL = (1 << 3),
323 DO_DIFF = (1 << 4),
324 SHOW_DEFAULTS = (1 << 5),
325 HIDE_UNUSED = (1 << 6),
326 HARDWARE_ONLY = (1 << 7),
327 BARE = (1 << 8),
328 } dumpFlags_t;
330 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
332 typedef enum {
333 REBOOT_TARGET_FIRMWARE,
334 REBOOT_TARGET_BOOTLOADER_ROM,
335 REBOOT_TARGET_BOOTLOADER_FLASH,
336 } rebootTarget_e;
338 typedef struct serialPassthroughPort_s {
339 int id;
340 uint32_t baud;
341 unsigned mode;
342 serialPort_t *port;
343 } serialPassthroughPort_t;
345 static void cliWriterFlushInternal(bufWriter_t *writer)
347 if (writer) {
348 bufWriterFlush(writer);
352 static void cliPrintInternal(bufWriter_t *writer, const char *str)
354 if (writer) {
355 while (*str) {
356 bufWriterAppend(writer, *str++);
358 cliWriterFlushInternal(writer);
362 static void cliWriterFlush()
364 cliWriterFlushInternal(cliWriter);
367 void cliPrint(const char *str)
369 cliPrintInternal(cliWriter, str);
372 void cliPrintLinefeed(void)
374 cliPrint("\r\n");
377 void cliPrintLine(const char *str)
379 cliPrint(str);
380 cliPrintLinefeed();
383 #ifdef MINIMAL_CLI
384 #define cliPrintHashLine(str)
385 #else
386 static void cliPrintHashLine(const char *str)
388 cliPrint("\r\n# ");
389 cliPrintLine(str);
391 #endif
393 static void cliPutp(void *p, char ch)
395 bufWriterAppend(p, ch);
398 static void cliPrintfva(const char *format, va_list va)
400 if (cliWriter) {
401 tfp_format(cliWriter, cliPutp, format, va);
402 cliWriterFlush();
406 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
408 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
409 va_list va;
410 va_start(va, format);
411 cliPrintfva(format, va);
412 va_end(va);
413 cliPrintLinefeed();
414 return true;
415 } else {
416 return false;
420 static void cliWrite(uint8_t ch)
422 if (cliWriter) {
423 bufWriterAppend(cliWriter, ch);
427 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
429 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
430 cliWrite('#');
432 va_list va;
433 va_start(va, format);
434 cliPrintfva(format, va);
435 va_end(va);
436 cliPrintLinefeed();
437 return true;
438 } else {
439 return false;
443 void cliPrintf(const char *format, ...)
445 va_list va;
446 va_start(va, format);
447 cliPrintfva(format, va);
448 va_end(va);
452 void cliPrintLinef(const char *format, ...)
454 va_list va;
455 va_start(va, format);
456 cliPrintfva(format, va);
457 va_end(va);
458 cliPrintLinefeed();
461 static void cliPrintErrorVa(const char *cmdName, const char *format, va_list va)
463 if (cliErrorWriter) {
464 cliPrintInternal(cliErrorWriter, "###ERROR IN ");
465 cliPrintInternal(cliErrorWriter, cmdName);
466 cliPrintInternal(cliErrorWriter, ": ");
468 tfp_format(cliErrorWriter, cliPutp, format, va);
469 va_end(va);
471 cliPrintInternal(cliErrorWriter, "###");
474 #ifdef USE_CLI_BATCH
475 if (commandBatchActive) {
476 commandBatchError = true;
478 #endif
481 static void cliPrintError(const char *cmdName, const char *format, ...)
483 va_list va;
484 va_start(va, format);
485 cliPrintErrorVa(cmdName, format, va);
487 if (!cliWriter) {
488 // Supply our own linefeed in case we are printing inside a custom defaults operation
489 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
490 // instead of expecting the directly following command to supply the line feed.
491 cliPrintInternal(cliErrorWriter, "\r\n");
495 static void cliPrintErrorLinef(const char *cmdName, const char *format, ...)
497 va_list va;
498 va_start(va, format);
499 cliPrintErrorVa(cmdName, format, va);
500 cliPrintInternal(cliErrorWriter, "\r\n");
503 static void getMinMax(const clivalue_t *var, int *min, int *max)
505 switch (var->type & VALUE_TYPE_MASK) {
506 case VAR_UINT8:
507 case VAR_UINT16:
508 *min = var->config.minmaxUnsigned.min;
509 *max = var->config.minmaxUnsigned.max;
511 break;
512 default:
513 *min = var->config.minmax.min;
514 *max = var->config.minmax.max;
516 break;
520 static void printValuePointer(const char *cmdName, const clivalue_t *var, const void *valuePointer, bool full)
522 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
523 for (int i = 0; i < var->config.array.length; i++) {
524 switch (var->type & VALUE_TYPE_MASK) {
525 default:
526 case VAR_UINT8:
527 // uint8_t array
528 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
529 break;
531 case VAR_INT8:
532 // int8_t array
533 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
534 break;
536 case VAR_UINT16:
537 // uin16_t array
538 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
539 break;
541 case VAR_INT16:
542 // int16_t array
543 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
544 break;
546 case VAR_UINT32:
547 // uin32_t array
548 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
549 break;
552 if (i < var->config.array.length - 1) {
553 cliPrint(",");
556 } else {
557 int value = 0;
559 switch (var->type & VALUE_TYPE_MASK) {
560 case VAR_UINT8:
561 value = *(uint8_t *)valuePointer;
563 break;
564 case VAR_INT8:
565 value = *(int8_t *)valuePointer;
567 break;
568 case VAR_UINT16:
569 value = *(uint16_t *)valuePointer;
571 break;
572 case VAR_INT16:
573 value = *(int16_t *)valuePointer;
575 break;
576 case VAR_UINT32:
577 value = *(uint32_t *)valuePointer;
579 break;
582 bool valueIsCorrupted = false;
583 switch (var->type & VALUE_MODE_MASK) {
584 case MODE_DIRECT:
585 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
586 cliPrintf("%u", (uint32_t)value);
587 if ((uint32_t)value > var->config.u32Max) {
588 valueIsCorrupted = true;
589 } else if (full) {
590 cliPrintf(" 0 %u", var->config.u32Max);
592 } else {
593 int min;
594 int max;
595 getMinMax(var, &min, &max);
597 cliPrintf("%d", value);
598 if ((value < min) || (value > max)) {
599 valueIsCorrupted = true;
600 } else if (full) {
601 cliPrintf(" %d %d", min, max);
604 break;
605 case MODE_LOOKUP:
606 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
607 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
608 } else {
609 valueIsCorrupted = true;
611 break;
612 case MODE_BITSET:
613 if (value & 1 << var->config.bitpos) {
614 cliPrintf("ON");
615 } else {
616 cliPrintf("OFF");
618 break;
619 case MODE_STRING:
620 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
621 break;
624 if (valueIsCorrupted) {
625 cliPrintLinefeed();
626 cliPrintError(cmdName, "CORRUPTED CONFIG: %s = %d", var->name, value);
632 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
634 bool result = true;
635 int elementCount = 1;
636 uint32_t mask = 0xffffffff;
638 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
639 elementCount = var->config.array.length;
641 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
642 mask = 1 << var->config.bitpos;
644 for (int i = 0; i < elementCount; i++) {
645 switch (var->type & VALUE_TYPE_MASK) {
646 case VAR_UINT8:
647 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
648 break;
650 case VAR_INT8:
651 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
652 break;
654 case VAR_UINT16:
655 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
656 break;
657 case VAR_INT16:
658 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
659 break;
660 case VAR_UINT32:
661 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
662 break;
666 return result;
669 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
671 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
672 cliPrintHashLine(headingStr);
673 return NULL;
674 } else {
675 return headingStr;
679 static void backupPgConfig(const pgRegistry_t *pg)
681 memcpy(pg->copy, pg->address, pg->size);
684 static void restorePgConfig(const pgRegistry_t *pg, uint16_t notToRestoreGroupId)
686 if (!notToRestoreGroupId || pgN(pg) != notToRestoreGroupId) {
687 memcpy(pg->address, pg->copy, pg->size);
691 static void backupConfigs(void)
693 if (configIsInCopy) {
694 return;
697 PG_FOREACH(pg) {
698 backupPgConfig(pg);
701 configIsInCopy = true;
704 static void restoreConfigs(uint16_t notToRestoreGroupId)
706 if (!configIsInCopy) {
707 return;
710 PG_FOREACH(pg) {
711 restorePgConfig(pg, notToRestoreGroupId);
714 configIsInCopy = false;
717 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
718 static bool isReadingConfigFromCopy()
720 return configIsInCopy;
722 #endif
724 static bool isWritingConfigToCopy()
726 return configIsInCopy
727 #if defined(USE_CUSTOM_DEFAULTS)
728 && !processingCustomDefaults
729 #endif
733 #if defined(USE_CUSTOM_DEFAULTS)
734 static bool cliProcessCustomDefaults(bool quiet);
735 #endif
737 static void backupAndResetConfigs(const bool useCustomDefaults)
739 backupConfigs();
741 // reset all configs to defaults to do differencing
742 resetConfig();
744 #if defined(USE_CUSTOM_DEFAULTS)
745 if (useCustomDefaults) {
746 if (!cliProcessCustomDefaults(true)) {
747 cliPrintLine("###WARNING: NO CUSTOM DEFAULTS FOUND###");
750 #else
751 UNUSED(useCustomDefaults);
752 #endif
755 static uint8_t getPidProfileIndexToUse()
757 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
760 static uint8_t getRateProfileIndexToUse()
762 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
766 static uint16_t getValueOffset(const clivalue_t *value)
768 switch (value->type & VALUE_SECTION_MASK) {
769 case MASTER_VALUE:
770 case HARDWARE_VALUE:
771 return value->offset;
772 case PROFILE_VALUE:
773 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
774 case PROFILE_RATE_VALUE:
775 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
777 return 0;
780 STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
782 const pgRegistry_t* rec = pgFind(value->pgn);
783 if (isWritingConfigToCopy()) {
784 return CONST_CAST(void *, rec->copy + getValueOffset(value));
785 } else {
786 return CONST_CAST(void *, rec->address + getValueOffset(value));
790 static const char *dumpPgValue(const char *cmdName, const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
792 const pgRegistry_t *pg = pgFind(value->pgn);
793 #ifdef DEBUG
794 if (!pg) {
795 cliPrintLinef("VALUE %s ERROR", value->name);
796 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
798 #endif
800 const char *format = "set %s = ";
801 const char *defaultFormat = "#set %s = ";
802 const int valueOffset = getValueOffset(value);
803 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
805 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
806 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
807 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
808 cliPrintf(defaultFormat, value->name);
809 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
810 cliPrintLinefeed();
812 cliPrintf(format, value->name);
813 printValuePointer(cmdName, value, pg->copy + valueOffset, false);
814 cliPrintLinefeed();
816 return headingStr;
819 static void dumpAllValues(const char *cmdName, uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
821 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
823 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
824 const clivalue_t *value = &valueTable[i];
825 cliWriterFlush();
826 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
827 headingStr = dumpPgValue(cmdName, value, dumpMask, headingStr);
832 static void cliPrintVar(const char *cmdName, const clivalue_t *var, bool full)
834 const void *ptr = cliGetValuePointer(var);
836 printValuePointer(cmdName, var, ptr, full);
839 static void cliPrintVarRange(const clivalue_t *var)
841 switch (var->type & VALUE_MODE_MASK) {
842 case (MODE_DIRECT): {
843 switch (var->type & VALUE_TYPE_MASK) {
844 case VAR_UINT32:
845 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
847 break;
848 case VAR_UINT8:
849 case VAR_UINT16:
850 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
852 break;
853 default:
854 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
856 break;
859 break;
860 case (MODE_LOOKUP): {
861 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
862 cliPrint("Allowed values: ");
863 bool firstEntry = true;
864 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
865 if (tableEntry->values[i]) {
866 if (!firstEntry) {
867 cliPrint(", ");
869 cliPrintf("%s", tableEntry->values[i]);
870 firstEntry = false;
873 cliPrintLinefeed();
875 break;
876 case (MODE_ARRAY): {
877 cliPrintLinef("Array length: %d", var->config.array.length);
879 break;
880 case (MODE_STRING): {
881 cliPrintLinef("String length: %d - %d", var->config.string.minlength, var->config.string.maxlength);
883 break;
884 case (MODE_BITSET): {
885 cliPrintLinef("Allowed values: OFF, ON");
887 break;
891 static void cliSetVar(const clivalue_t *var, const uint32_t value)
893 void *ptr = cliGetValuePointer(var);
894 uint32_t workValue;
895 uint32_t mask;
897 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
898 switch (var->type & VALUE_TYPE_MASK) {
899 case VAR_UINT8:
900 mask = (1 << var->config.bitpos) & 0xff;
901 if (value) {
902 workValue = *(uint8_t *)ptr | mask;
903 } else {
904 workValue = *(uint8_t *)ptr & ~mask;
906 *(uint8_t *)ptr = workValue;
907 break;
909 case VAR_UINT16:
910 mask = (1 << var->config.bitpos) & 0xffff;
911 if (value) {
912 workValue = *(uint16_t *)ptr | mask;
913 } else {
914 workValue = *(uint16_t *)ptr & ~mask;
916 *(uint16_t *)ptr = workValue;
917 break;
919 case VAR_UINT32:
920 mask = 1 << var->config.bitpos;
921 if (value) {
922 workValue = *(uint32_t *)ptr | mask;
923 } else {
924 workValue = *(uint32_t *)ptr & ~mask;
926 *(uint32_t *)ptr = workValue;
927 break;
929 } else {
930 switch (var->type & VALUE_TYPE_MASK) {
931 case VAR_UINT8:
932 *(uint8_t *)ptr = value;
933 break;
935 case VAR_INT8:
936 *(int8_t *)ptr = value;
937 break;
939 case VAR_UINT16:
940 *(uint16_t *)ptr = value;
941 break;
943 case VAR_INT16:
944 *(int16_t *)ptr = value;
945 break;
947 case VAR_UINT32:
948 *(uint32_t *)ptr = value;
949 break;
954 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
955 static void cliRepeat(char ch, uint8_t len)
957 if (cliWriter) {
958 for (int i = 0; i < len; i++) {
959 bufWriterAppend(cliWriter, ch);
961 cliPrintLinefeed();
964 #endif
966 static void cliPrompt(void)
968 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
969 if (processingCustomDefaults) {
970 cliPrint("\r\nd: #");
971 } else
972 #endif
974 cliPrint("\r\n# ");
978 static void cliShowParseError(const char *cmdName)
980 cliPrintErrorLinef(cmdName, "PARSING FAILED");
983 static void cliShowInvalidArgumentCountError(const char *cmdName)
985 cliPrintErrorLinef(cmdName, "INVALID ARGUMENT COUNT", cmdName);
988 static void cliShowArgumentRangeError(const char *cmdName, char *name, int min, int max)
990 if (name) {
991 cliPrintErrorLinef(cmdName, "%s NOT BETWEEN %d AND %d", name, min, max);
992 } else {
993 cliPrintErrorLinef(cmdName, "ARGUMENT OUT OF RANGE");
997 static const char *nextArg(const char *currentArg)
999 const char *ptr = strchr(currentArg, ' ');
1000 while (ptr && *ptr == ' ') {
1001 ptr++;
1004 return ptr;
1007 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
1009 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
1010 ptr = nextArg(ptr);
1011 if (ptr) {
1012 int val = atoi(ptr);
1013 val = CHANNEL_VALUE_TO_STEP(val);
1014 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
1015 if (argIndex == 0) {
1016 range->startStep = val;
1017 } else {
1018 range->endStep = val;
1020 (*validArgumentCount)++;
1025 return ptr;
1028 // Check if a string's length is zero
1029 static bool isEmpty(const char *string)
1031 return (string == NULL || *string == '\0') ? true : false;
1034 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
1036 // print out rxConfig failsafe settings
1037 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1038 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1039 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
1040 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
1041 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
1042 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1043 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1044 if (requireValue) {
1045 const char *format = "rxfail %u %c %d";
1046 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1047 channel,
1048 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
1049 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
1051 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1052 channel,
1053 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
1054 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1056 } else {
1057 const char *format = "rxfail %u %c";
1058 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1059 channel,
1060 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
1062 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1063 channel,
1064 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
1070 static void cliRxFailsafe(const char *cmdName, char *cmdline)
1072 uint8_t channel;
1073 char buf[3];
1075 if (isEmpty(cmdline)) {
1076 // print out rxConfig failsafe settings
1077 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1078 cliRxFailsafe(cmdName, itoa(channel, buf, 10));
1080 } else {
1081 const char *ptr = cmdline;
1082 channel = atoi(ptr++);
1083 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
1085 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
1087 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
1088 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
1089 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1091 ptr = nextArg(ptr);
1092 if (ptr) {
1093 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
1094 if (p) {
1095 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
1096 mode = rxFailsafeModesTable[type][requestedMode];
1097 } else {
1098 mode = RX_FAILSAFE_MODE_INVALID;
1100 if (mode == RX_FAILSAFE_MODE_INVALID) {
1101 cliShowParseError(cmdName);
1102 return;
1105 requireValue = mode == RX_FAILSAFE_MODE_SET;
1107 ptr = nextArg(ptr);
1108 if (ptr) {
1109 if (!requireValue) {
1110 cliShowParseError(cmdName);
1111 return;
1113 uint16_t value = atoi(ptr);
1114 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
1115 if (value > MAX_RXFAIL_RANGE_STEP) {
1116 cliPrintErrorLinef(cmdName, "value out of range: %d", value);
1117 return;
1120 channelFailsafeConfig->step = value;
1121 } else if (requireValue) {
1122 cliShowInvalidArgumentCountError(cmdName);
1123 return;
1125 channelFailsafeConfig->mode = mode;
1128 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
1130 // double use of cliPrintf below
1131 // 1. acknowledge interpretation on command,
1132 // 2. query current setting on single item,
1134 if (requireValue) {
1135 cliPrintLinef("rxfail %u %c %d",
1136 channel,
1137 modeCharacter,
1138 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1140 } else {
1141 cliPrintLinef("rxfail %u %c",
1142 channel,
1143 modeCharacter
1146 } else {
1147 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1152 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1154 const char *format = "aux %u %u %u %u %u %u %u";
1155 // print out aux channel settings
1156 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1157 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1158 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1159 bool equalsDefault = false;
1160 if (defaultModeActivationConditions) {
1161 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1162 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1163 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1164 const box_t *box = findBoxByBoxId(macDefault->modeId);
1165 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1166 if (box) {
1167 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1169 box->permanentId,
1170 macDefault->auxChannelIndex,
1171 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1172 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1173 macDefault->modeLogic,
1174 linkedTo ? linkedTo->permanentId : 0
1178 const box_t *box = findBoxByBoxId(mac->modeId);
1179 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1180 if (box) {
1181 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1183 box->permanentId,
1184 mac->auxChannelIndex,
1185 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1186 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1187 mac->modeLogic,
1188 linkedTo ? linkedTo->permanentId : 0
1194 static void cliAux(const char *cmdName, char *cmdline)
1196 int i, val = 0;
1197 const char *ptr;
1199 if (isEmpty(cmdline)) {
1200 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1201 } else {
1202 ptr = cmdline;
1203 i = atoi(ptr++);
1204 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1205 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1206 uint8_t validArgumentCount = 0;
1207 ptr = nextArg(ptr);
1208 if (ptr) {
1209 val = atoi(ptr);
1210 const box_t *box = findBoxByPermanentId(val);
1211 if (box) {
1212 mac->modeId = box->boxId;
1213 validArgumentCount++;
1216 ptr = nextArg(ptr);
1217 if (ptr) {
1218 val = atoi(ptr);
1219 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1220 mac->auxChannelIndex = val;
1221 validArgumentCount++;
1224 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1225 ptr = nextArg(ptr);
1226 if (ptr) {
1227 val = atoi(ptr);
1228 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1229 mac->modeLogic = val;
1230 validArgumentCount++;
1233 ptr = nextArg(ptr);
1234 if (ptr) {
1235 val = atoi(ptr);
1236 const box_t *box = findBoxByPermanentId(val);
1237 if (box) {
1238 mac->linkedTo = box->boxId;
1239 validArgumentCount++;
1242 if (validArgumentCount == 4) { // for backwards compatibility
1243 mac->modeLogic = MODELOGIC_OR;
1244 mac->linkedTo = 0;
1245 } else if (validArgumentCount == 5) { // for backwards compatibility
1246 mac->linkedTo = 0;
1247 } else if (validArgumentCount != 6) {
1248 memset(mac, 0, sizeof(modeActivationCondition_t));
1250 analyzeModeActivationConditions();
1251 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1253 findBoxByBoxId(mac->modeId)->permanentId,
1254 mac->auxChannelIndex,
1255 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1256 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1257 mac->modeLogic,
1258 findBoxByBoxId(mac->linkedTo)->permanentId
1260 } else {
1261 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1266 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1268 const char *format = "serial %d %d %ld %ld %ld %ld";
1269 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1270 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
1271 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1272 continue;
1274 bool equalsDefault = false;
1275 if (serialConfigDefault) {
1276 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1277 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1278 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1279 serialConfigDefault->portConfigs[i].identifier,
1280 serialConfigDefault->portConfigs[i].functionMask,
1281 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1282 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1283 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1284 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1287 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1288 serialConfig->portConfigs[i].identifier,
1289 serialConfig->portConfigs[i].functionMask,
1290 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1291 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1292 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1293 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1298 static void cliSerial(const char *cmdName, char *cmdline)
1300 const char *format = "serial %d %d %ld %ld %ld %ld";
1301 if (isEmpty(cmdline)) {
1302 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1303 return;
1305 serialPortConfig_t portConfig;
1306 memset(&portConfig, 0 , sizeof(portConfig));
1309 uint8_t validArgumentCount = 0;
1311 const char *ptr = cmdline;
1313 int val = atoi(ptr++);
1314 serialPortConfig_t *currentConfig = serialFindPortConfigurationMutable(val);
1316 if (currentConfig) {
1317 portConfig.identifier = val;
1318 validArgumentCount++;
1321 ptr = nextArg(ptr);
1322 if (ptr) {
1323 val = strtoul(ptr, NULL, 10);
1324 portConfig.functionMask = val;
1325 validArgumentCount++;
1328 for (int i = 0; i < 4; i ++) {
1329 ptr = nextArg(ptr);
1330 if (!ptr) {
1331 break;
1334 val = atoi(ptr);
1336 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1337 if (baudRates[baudRateIndex] != (uint32_t) val) {
1338 break;
1341 switch (i) {
1342 case 0:
1343 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1344 continue;
1346 portConfig.msp_baudrateIndex = baudRateIndex;
1347 break;
1348 case 1:
1349 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1350 continue;
1352 portConfig.gps_baudrateIndex = baudRateIndex;
1353 break;
1354 case 2:
1355 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1356 continue;
1358 portConfig.telemetry_baudrateIndex = baudRateIndex;
1359 break;
1360 case 3:
1361 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1362 continue;
1364 portConfig.blackbox_baudrateIndex = baudRateIndex;
1365 break;
1368 validArgumentCount++;
1371 if (validArgumentCount < 6) {
1372 cliShowInvalidArgumentCountError(cmdName);
1373 return;
1376 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1378 cliDumpPrintLinef(0, false, format,
1379 portConfig.identifier,
1380 portConfig.functionMask,
1381 baudRates[portConfig.msp_baudrateIndex],
1382 baudRates[portConfig.gps_baudrateIndex],
1383 baudRates[portConfig.telemetry_baudrateIndex],
1384 baudRates[portConfig.blackbox_baudrateIndex]
1389 #if defined(USE_SERIAL_PASSTHROUGH)
1390 static void cbCtrlLine(void *context, uint16_t ctrl)
1392 #ifdef USE_PINIO
1393 int contextValue = (int)(long)context;
1394 if (contextValue) {
1395 pinioSet(contextValue - 1, !(ctrl & CTRL_LINE_STATE_DTR));
1396 } else
1397 #endif /* USE_PINIO */
1398 UNUSED(context);
1400 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1401 systemReset();
1405 static int cliParseSerialMode(const char *tok)
1407 int mode = 0;
1409 if (strcasestr(tok, "rx")) {
1410 mode |= MODE_RX;
1412 if (strcasestr(tok, "tx")) {
1413 mode |= MODE_TX;
1416 return mode;
1419 static void cliSerialPassthrough(const char *cmdName, char *cmdline)
1421 if (isEmpty(cmdline)) {
1422 cliShowInvalidArgumentCountError(cmdName);
1423 return;
1426 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, NULL}, {cliPort->identifier, 0, 0, cliPort} };
1427 bool enableBaudCb = false;
1428 int port1PinioDtr = 0;
1429 bool port1ResetOnDtr = false;
1430 bool escSensorPassthrough = false;
1431 char *saveptr;
1432 char* tok = strtok_r(cmdline, " ", &saveptr);
1433 int index = 0;
1435 while (tok != NULL) {
1436 switch (index) {
1437 case 0:
1438 if (strcasestr(tok, "esc_sensor")) {
1439 escSensorPassthrough = true;
1440 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1441 ports[0].id = portConfig->identifier;
1442 } else {
1443 ports[0].id = atoi(tok);
1445 break;
1446 case 1:
1447 ports[0].baud = atoi(tok);
1448 break;
1449 case 2:
1450 ports[0].mode = cliParseSerialMode(tok);
1451 break;
1452 case 3:
1453 if (strncasecmp(tok, "reset", strlen(tok)) == 0) {
1454 port1ResetOnDtr = true;
1455 #ifdef USE_PINIO
1456 } else if (strncasecmp(tok, "none", strlen(tok)) == 0) {
1457 port1PinioDtr = 0;
1458 } else {
1459 port1PinioDtr = atoi(tok);
1460 if (port1PinioDtr < 0 || port1PinioDtr > PINIO_COUNT) {
1461 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1462 return ;
1464 #endif /* USE_PINIO */
1466 break;
1467 case 4:
1468 ports[1].id = atoi(tok);
1469 ports[1].port = NULL;
1470 break;
1471 case 5:
1472 ports[1].baud = atoi(tok);
1473 break;
1474 case 6:
1475 ports[1].mode = cliParseSerialMode(tok);
1476 break;
1478 index++;
1479 tok = strtok_r(NULL, " ", &saveptr);
1482 // Port checks
1483 if (ports[0].id == ports[1].id) {
1484 cliPrintLinef("Port1 and port2 are same");
1485 return ;
1488 for (int i = 0; i < 2; i++) {
1489 if (findSerialPortIndexByIdentifier(ports[i].id) == -1) {
1490 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1491 return ;
1492 } else {
1493 cliPrintLinef("Port%d: %d ", i + 1, ports[i].id);
1497 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1498 enableBaudCb = true;
1501 for (int i = 0; i < 2; i++) {
1502 serialPort_t **port = &(ports[i].port);
1503 if (*port != NULL) {
1504 continue;
1507 int portIndex = i + 1;
1508 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(ports[i].id);
1509 if (!portUsage || portUsage->serialPort == NULL) {
1510 bool isUseDefaultBaud = false;
1511 if (ports[i].baud == 0) {
1512 // Set default baud
1513 ports[i].baud = 57600;
1514 isUseDefaultBaud = true;
1517 if (!ports[i].mode) {
1518 ports[i].mode = MODE_RXTX;
1521 *port = openSerialPort(ports[i].id, FUNCTION_NONE, NULL, NULL,
1522 ports[i].baud, ports[i].mode,
1523 SERIAL_NOT_INVERTED);
1524 if (!*port) {
1525 cliPrintLinef("Port%d could not be opened.", portIndex);
1526 return;
1529 if (isUseDefaultBaud) {
1530 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex, ports[i].baud);
1531 } else {
1532 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex, ports[i].baud);
1534 } else {
1535 *port = portUsage->serialPort;
1536 // If the user supplied a mode, override the port's mode, otherwise
1537 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1538 // Set the baud rate if specified
1539 if (ports[i].baud) {
1540 cliPrintf("Port%d is already open, setting baud = %d.\r\n", portIndex, ports[i].baud);
1541 serialSetBaudRate(*port, ports[i].baud);
1542 } else {
1543 cliPrintf("Port%d is already open, baud = %d.\r\n", portIndex, (*port)->baudRate);
1546 if (ports[i].mode && (*port)->mode != ports[i].mode) {
1547 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1548 portIndex, (*port)->mode, ports[i].mode);
1549 serialSetMode(*port, ports[i].mode);
1552 // If this port has a rx callback associated we need to remove it now.
1553 // Otherwise no data will be pushed in the serial port buffer!
1554 if ((*port)->rxCallback) {
1555 (*port)->rxCallback = NULL;
1560 // If no baud rate is specified allow to be set via USB
1561 if (enableBaudCb) {
1562 cliPrintLine("Port1 baud rate change over USB enabled.");
1563 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1564 // baud rate over USB without setting it using the serialpassthrough command
1565 serialSetBaudRateCb(ports[1].port, serialSetBaudRate, ports[0].port);
1568 char *resetMessage = "";
1569 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1570 resetMessage = "or drop DTR ";
1573 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1575 if ((ports[1].id == SERIAL_PORT_USB_VCP) && (port1ResetOnDtr
1576 #ifdef USE_PINIO
1577 || port1PinioDtr
1578 #endif /* USE_PINIO */
1579 )) {
1580 // Register control line state callback
1581 serialSetCtrlLineStateCb(ports[0].port, cbCtrlLine, (void *)(intptr_t)(port1PinioDtr));
1584 // XXX Review ESC pass through under refactored motor handling
1585 #ifdef USE_PWM_OUTPUT
1586 if (escSensorPassthrough) {
1587 // pwmDisableMotors();
1588 motorDisable();
1589 delay(5);
1590 for (unsigned i = 0; i < getMotorCount(); i++) {
1591 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1592 if (tag) {
1593 const timerHardware_t *timerHardware = timerGetConfiguredByTag(tag);
1594 if (timerHardware) {
1595 IO_t io = IOGetByTag(tag);
1596 IOInit(io, OWNER_MOTOR, 0);
1597 IOConfigGPIO(io, IOCFG_OUT_PP);
1598 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1599 IOLo(io);
1600 } else {
1601 IOHi(io);
1607 #endif
1609 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1611 #endif
1613 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1615 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1616 // print out adjustment ranges channel settings
1617 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1618 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1619 const adjustmentRange_t *ar = &adjustmentRanges[i];
1620 bool equalsDefault = false;
1621 if (defaultAdjustmentRanges) {
1622 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1623 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1624 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1625 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1627 arDefault->auxChannelIndex,
1628 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1629 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1630 arDefault->adjustmentConfig,
1631 arDefault->auxSwitchChannelIndex,
1632 arDefault->adjustmentCenter,
1633 arDefault->adjustmentScale
1636 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1638 ar->auxChannelIndex,
1639 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1640 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1641 ar->adjustmentConfig,
1642 ar->auxSwitchChannelIndex,
1643 ar->adjustmentCenter,
1644 ar->adjustmentScale
1649 static void cliAdjustmentRange(const char *cmdName, char *cmdline)
1651 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1652 int i, val = 0;
1653 const char *ptr;
1655 if (isEmpty(cmdline)) {
1656 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1657 } else {
1658 ptr = cmdline;
1659 i = atoi(ptr++);
1660 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1661 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1662 uint8_t validArgumentCount = 0;
1664 ptr = nextArg(ptr);
1665 if (ptr) {
1666 val = atoi(ptr);
1667 // Was: slot
1668 // Keeping the parameter to retain backwards compatibility for the command format.
1669 validArgumentCount++;
1671 ptr = nextArg(ptr);
1672 if (ptr) {
1673 val = atoi(ptr);
1674 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1675 ar->auxChannelIndex = val;
1676 validArgumentCount++;
1680 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1682 ptr = nextArg(ptr);
1683 if (ptr) {
1684 val = atoi(ptr);
1685 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1686 ar->adjustmentConfig = val;
1687 validArgumentCount++;
1690 ptr = nextArg(ptr);
1691 if (ptr) {
1692 val = atoi(ptr);
1693 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1694 ar->auxSwitchChannelIndex = val;
1695 validArgumentCount++;
1699 if (validArgumentCount != 6) {
1700 memset(ar, 0, sizeof(adjustmentRange_t));
1701 cliShowInvalidArgumentCountError(cmdName);
1702 return;
1705 // Optional arguments
1706 ar->adjustmentCenter = 0;
1707 ar->adjustmentScale = 0;
1709 ptr = nextArg(ptr);
1710 if (ptr) {
1711 val = atoi(ptr);
1712 ar->adjustmentCenter = val;
1713 validArgumentCount++;
1715 ptr = nextArg(ptr);
1716 if (ptr) {
1717 val = atoi(ptr);
1718 ar->adjustmentScale = val;
1719 validArgumentCount++;
1722 activeAdjustmentRangeReset();
1724 cliDumpPrintLinef(0, false, format,
1726 ar->auxChannelIndex,
1727 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1728 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1729 ar->adjustmentConfig,
1730 ar->auxSwitchChannelIndex,
1731 ar->adjustmentCenter,
1732 ar->adjustmentScale
1735 } else {
1736 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1741 #ifndef USE_QUAD_MIXER_ONLY
1742 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1744 const char *format = "mmix %d %s %s %s %s";
1745 char buf0[FTOA_BUFFER_LENGTH];
1746 char buf1[FTOA_BUFFER_LENGTH];
1747 char buf2[FTOA_BUFFER_LENGTH];
1748 char buf3[FTOA_BUFFER_LENGTH];
1749 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1750 if (customMotorMixer[i].throttle == 0.0f)
1751 break;
1752 const float thr = customMotorMixer[i].throttle;
1753 const float roll = customMotorMixer[i].roll;
1754 const float pitch = customMotorMixer[i].pitch;
1755 const float yaw = customMotorMixer[i].yaw;
1756 bool equalsDefault = false;
1757 if (defaultCustomMotorMixer) {
1758 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1759 const float rollDefault = defaultCustomMotorMixer[i].roll;
1760 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1761 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1762 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1764 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1765 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1767 ftoa(thrDefault, buf0),
1768 ftoa(rollDefault, buf1),
1769 ftoa(pitchDefault, buf2),
1770 ftoa(yawDefault, buf3));
1772 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1774 ftoa(thr, buf0),
1775 ftoa(roll, buf1),
1776 ftoa(pitch, buf2),
1777 ftoa(yaw, buf3));
1780 #endif // USE_QUAD_MIXER_ONLY
1782 static void cliMotorMix(const char *cmdName, char *cmdline)
1784 #ifdef USE_QUAD_MIXER_ONLY
1785 UNUSED(cmdName);
1786 UNUSED(cmdline);
1787 #else
1788 int check = 0;
1789 uint8_t len;
1790 const char *ptr;
1792 if (isEmpty(cmdline)) {
1793 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1794 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1795 // erase custom mixer
1796 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1797 customMotorMixerMutable(i)->throttle = 0.0f;
1799 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1800 ptr = nextArg(cmdline);
1801 if (ptr) {
1802 len = strlen(ptr);
1803 for (uint32_t i = 0; ; i++) {
1804 if (mixerNames[i] == NULL) {
1805 cliPrintErrorLinef(cmdName, "INVALID NAME");
1806 break;
1808 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1809 mixerLoadMix(i, customMotorMixerMutable(0));
1810 cliPrintLinef("Loaded %s", mixerNames[i]);
1811 cliMotorMix(cmdName, "");
1812 break;
1816 } else {
1817 ptr = cmdline;
1818 uint32_t i = atoi(ptr); // get motor number
1819 if (i < MAX_SUPPORTED_MOTORS) {
1820 ptr = nextArg(ptr);
1821 if (ptr) {
1822 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1823 check++;
1825 ptr = nextArg(ptr);
1826 if (ptr) {
1827 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1828 check++;
1830 ptr = nextArg(ptr);
1831 if (ptr) {
1832 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1833 check++;
1835 ptr = nextArg(ptr);
1836 if (ptr) {
1837 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1838 check++;
1840 if (check != 4) {
1841 cliShowInvalidArgumentCountError(cmdName);
1842 } else {
1843 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1845 } else {
1846 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1849 #endif
1852 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1854 const char *format = "rxrange %u %u %u";
1855 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1856 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1857 bool equalsDefault = false;
1858 if (defaultChannelRangeConfigs) {
1859 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1860 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1861 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1863 defaultChannelRangeConfigs[i].min,
1864 defaultChannelRangeConfigs[i].max
1867 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1869 channelRangeConfigs[i].min,
1870 channelRangeConfigs[i].max
1875 static void cliRxRange(const char *cmdName, char *cmdline)
1877 const char *format = "rxrange %u %u %u";
1878 int i, validArgumentCount = 0;
1879 const char *ptr;
1881 if (isEmpty(cmdline)) {
1882 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1883 } else if (strcasecmp(cmdline, "reset") == 0) {
1884 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1885 } else {
1886 ptr = cmdline;
1887 i = atoi(ptr);
1888 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1889 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1891 ptr = nextArg(ptr);
1892 if (ptr) {
1893 rangeMin = atoi(ptr);
1894 validArgumentCount++;
1897 ptr = nextArg(ptr);
1898 if (ptr) {
1899 rangeMax = atoi(ptr);
1900 validArgumentCount++;
1903 if (validArgumentCount != 2) {
1904 cliShowInvalidArgumentCountError(cmdName);
1905 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1906 cliShowArgumentRangeError(cmdName, "range min/max", PWM_PULSE_MIN, PWM_PULSE_MAX);
1907 } else {
1908 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1909 channelRangeConfig->min = rangeMin;
1910 channelRangeConfig->max = rangeMax;
1911 cliDumpPrintLinef(0, false, format,
1913 channelRangeConfig->min,
1914 channelRangeConfig->max
1918 } else {
1919 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1924 #ifdef USE_LED_STRIP_STATUS_MODE
1925 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1927 const char *format = "led %u %s";
1928 char ledConfigBuffer[20];
1929 char ledConfigDefaultBuffer[20];
1930 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1931 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1932 ledConfig_t ledConfig = ledConfigs[i];
1933 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1934 bool equalsDefault = false;
1935 if (defaultLedConfigs) {
1936 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1937 equalsDefault = ledConfig == ledConfigDefault;
1938 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1939 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1940 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1942 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1946 static void cliLed(const char *cmdName, char *cmdline)
1948 const char *format = "led %u %s";
1949 char ledConfigBuffer[20];
1950 int i;
1951 const char *ptr;
1953 if (isEmpty(cmdline)) {
1954 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
1955 } else {
1956 ptr = cmdline;
1957 i = atoi(ptr);
1958 if (i >= 0 && i < LED_MAX_STRIP_LENGTH) {
1959 ptr = nextArg(cmdline);
1960 if (parseLedStripConfig(i, ptr)) {
1961 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1962 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1963 } else {
1964 cliShowParseError(cmdName);
1966 } else {
1967 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_MAX_STRIP_LENGTH - 1);
1972 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
1974 const char *format = "color %u %d,%u,%u";
1975 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1976 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1977 const hsvColor_t *color = &colors[i];
1978 bool equalsDefault = false;
1979 if (defaultColors) {
1980 const hsvColor_t *colorDefault = &defaultColors[i];
1981 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1982 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1983 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1985 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1989 static void cliColor(const char *cmdName, char *cmdline)
1991 const char *format = "color %u %d,%u,%u";
1992 if (isEmpty(cmdline)) {
1993 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
1994 } else {
1995 const char *ptr = cmdline;
1996 const int i = atoi(ptr);
1997 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1998 ptr = nextArg(cmdline);
1999 if (parseColor(i, ptr)) {
2000 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
2001 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
2002 } else {
2003 cliShowParseError(cmdName);
2005 } else {
2006 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
2011 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
2013 const char *format = "mode_color %u %u %u";
2014 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2015 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
2016 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
2017 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
2018 bool equalsDefault = false;
2019 if (defaultLedStripConfig) {
2020 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
2021 equalsDefault = colorIndex == colorIndexDefault;
2022 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2023 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
2025 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
2029 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
2030 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
2031 bool equalsDefault = false;
2032 if (defaultLedStripConfig) {
2033 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
2034 equalsDefault = colorIndex == colorIndexDefault;
2035 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2036 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
2038 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
2041 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
2042 bool equalsDefault = false;
2043 if (defaultLedStripConfig) {
2044 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
2045 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
2046 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2047 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
2049 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
2052 static void cliModeColor(const char *cmdName, char *cmdline)
2054 if (isEmpty(cmdline)) {
2055 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
2056 } else {
2057 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
2058 int args[ARGS_COUNT];
2059 int argNo = 0;
2060 char *saveptr;
2061 const char* ptr = strtok_r(cmdline, " ", &saveptr);
2062 while (ptr && argNo < ARGS_COUNT) {
2063 args[argNo++] = atoi(ptr);
2064 ptr = strtok_r(NULL, " ", &saveptr);
2067 if (ptr != NULL || argNo != ARGS_COUNT) {
2068 cliShowInvalidArgumentCountError(cmdName);
2069 return;
2072 int modeIdx = args[MODE];
2073 int funIdx = args[FUNCTION];
2074 int color = args[COLOR];
2075 if (!setModeColor(modeIdx, funIdx, color)) {
2076 cliShowParseError(cmdName);
2077 return;
2079 // values are validated
2080 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
2083 #endif
2085 #ifdef USE_SERVOS
2086 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
2088 // print out servo settings
2089 const char *format = "servo %u %d %d %d %d %d";
2090 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2091 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2092 const servoParam_t *servoConf = &servoParams[i];
2093 bool equalsDefault = false;
2094 if (defaultServoParams) {
2095 const servoParam_t *defaultServoConf = &defaultServoParams[i];
2096 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
2097 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2098 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2100 defaultServoConf->min,
2101 defaultServoConf->max,
2102 defaultServoConf->middle,
2103 defaultServoConf->rate,
2104 defaultServoConf->forwardFromChannel
2107 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2109 servoConf->min,
2110 servoConf->max,
2111 servoConf->middle,
2112 servoConf->rate,
2113 servoConf->forwardFromChannel
2116 // print servo directions
2117 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2118 const char *format = "smix reverse %d %d r";
2119 const servoParam_t *servoConf = &servoParams[i];
2120 const servoParam_t *servoConfDefault = &defaultServoParams[i];
2121 if (defaultServoParams) {
2122 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
2123 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2124 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
2125 if (servoConfDefault->reversedSources & (1 << channel)) {
2126 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
2128 if (servoConf->reversedSources & (1 << channel)) {
2129 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
2132 } else {
2133 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2134 if (servoConf->reversedSources & (1 << channel)) {
2135 cliDumpPrintLinef(dumpMask, true, format, i , channel);
2142 static void cliServo(const char *cmdName, char *cmdline)
2144 const char *format = "servo %u %d %d %d %d %d";
2145 enum { SERVO_ARGUMENT_COUNT = 6 };
2146 int16_t arguments[SERVO_ARGUMENT_COUNT];
2148 servoParam_t *servo;
2150 int i;
2151 char *ptr;
2153 if (isEmpty(cmdline)) {
2154 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2155 } else {
2156 int validArgumentCount = 0;
2158 ptr = cmdline;
2160 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2162 // If command line doesn't fit the format, don't modify the config
2163 while (*ptr) {
2164 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2165 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2166 cliShowInvalidArgumentCountError(cmdName);
2167 return;
2170 arguments[validArgumentCount++] = atoi(ptr);
2172 do {
2173 ptr++;
2174 } while (*ptr >= '0' && *ptr <= '9');
2175 } else if (*ptr == ' ') {
2176 ptr++;
2177 } else {
2178 cliShowParseError(cmdName);
2179 return;
2183 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2185 i = arguments[INDEX];
2187 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2188 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2189 cliShowInvalidArgumentCountError(cmdName);
2190 return;
2193 servo = servoParamsMutable(i);
2195 if (
2196 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
2197 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
2198 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2199 arguments[MIN] > arguments[MAX] ||
2200 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2201 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2203 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2204 return;
2207 servo->min = arguments[MIN];
2208 servo->max = arguments[MAX];
2209 servo->middle = arguments[MIDDLE];
2210 servo->rate = arguments[RATE];
2211 servo->forwardFromChannel = arguments[FORWARD];
2213 cliDumpPrintLinef(0, false, format,
2215 servo->min,
2216 servo->max,
2217 servo->middle,
2218 servo->rate,
2219 servo->forwardFromChannel
2224 #endif
2226 #ifdef USE_SERVOS
2227 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2229 const char *format = "smix %d %d %d %d %d %d %d %d";
2230 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2231 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2232 const servoMixer_t customServoMixer = customServoMixers[i];
2233 if (customServoMixer.rate == 0) {
2234 break;
2237 bool equalsDefault = false;
2238 if (defaultCustomServoMixers) {
2239 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2240 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2242 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2243 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2245 customServoMixerDefault.targetChannel,
2246 customServoMixerDefault.inputSource,
2247 customServoMixerDefault.rate,
2248 customServoMixerDefault.speed,
2249 customServoMixerDefault.min,
2250 customServoMixerDefault.max,
2251 customServoMixerDefault.box
2254 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2256 customServoMixer.targetChannel,
2257 customServoMixer.inputSource,
2258 customServoMixer.rate,
2259 customServoMixer.speed,
2260 customServoMixer.min,
2261 customServoMixer.max,
2262 customServoMixer.box
2267 static void cliServoMix(const char *cmdName, char *cmdline)
2269 int args[8], check = 0;
2270 int len = strlen(cmdline);
2272 if (len == 0) {
2273 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2274 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2275 // erase custom mixer
2276 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2277 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2278 servoParamsMutable(i)->reversedSources = 0;
2280 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2281 const char *ptr = nextArg(cmdline);
2282 if (ptr) {
2283 len = strlen(ptr);
2284 for (uint32_t i = 0; ; i++) {
2285 if (mixerNames[i] == NULL) {
2286 cliPrintErrorLinef(cmdName, "INVALID NAME");
2287 break;
2289 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2290 servoMixerLoadMix(i);
2291 cliPrintLinef("Loaded %s", mixerNames[i]);
2292 cliServoMix(cmdName, "");
2293 break;
2297 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2298 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2299 char *ptr = strchr(cmdline, ' ');
2301 if (ptr == NULL) {
2302 cliPrintf("s");
2303 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2304 cliPrintf("\ti%d", inputSource);
2305 cliPrintLinefeed();
2307 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2308 cliPrintf("%d", servoIndex);
2309 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2310 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2312 cliPrintLinefeed();
2314 return;
2317 char *saveptr;
2318 ptr = strtok_r(ptr, " ", &saveptr);
2319 while (ptr != NULL && check < ARGS_COUNT - 1) {
2320 args[check++] = atoi(ptr);
2321 ptr = strtok_r(NULL, " ", &saveptr);
2324 if (ptr == NULL || check != ARGS_COUNT - 1) {
2325 cliShowInvalidArgumentCountError(cmdName);
2326 return;
2329 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2330 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2331 && (*ptr == 'r' || *ptr == 'n')) {
2332 if (*ptr == 'r') {
2333 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2334 } else {
2335 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2337 } else {
2338 cliShowArgumentRangeError(cmdName, "servo", 0, MAX_SUPPORTED_SERVOS);
2339 return;
2342 cliServoMix(cmdName, "reverse");
2343 } else {
2344 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2345 char *saveptr;
2346 char *ptr = strtok_r(cmdline, " ", &saveptr);
2347 while (ptr != NULL && check < ARGS_COUNT) {
2348 args[check++] = atoi(ptr);
2349 ptr = strtok_r(NULL, " ", &saveptr);
2352 if (ptr != NULL || check != ARGS_COUNT) {
2353 cliShowInvalidArgumentCountError(cmdName);
2354 return;
2357 int32_t i = args[RULE];
2358 if (i >= 0 && i < MAX_SERVO_RULES &&
2359 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2360 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2361 args[RATE] >= -100 && args[RATE] <= 100 &&
2362 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2363 args[MIN] >= 0 && args[MIN] <= 100 &&
2364 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2365 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2366 customServoMixersMutable(i)->targetChannel = args[TARGET];
2367 customServoMixersMutable(i)->inputSource = args[INPUT];
2368 customServoMixersMutable(i)->rate = args[RATE];
2369 customServoMixersMutable(i)->speed = args[SPEED];
2370 customServoMixersMutable(i)->min = args[MIN];
2371 customServoMixersMutable(i)->max = args[MAX];
2372 customServoMixersMutable(i)->box = args[BOX];
2373 cliServoMix(cmdName, "");
2374 } else {
2375 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2379 #endif
2381 #ifdef USE_SDCARD
2383 static void cliWriteBytes(const uint8_t *buffer, int count)
2385 while (count > 0) {
2386 cliWrite(*buffer);
2387 buffer++;
2388 count--;
2392 static void cliSdInfo(const char *cmdName, char *cmdline)
2394 UNUSED(cmdName);
2395 UNUSED(cmdline);
2397 cliPrint("SD card: ");
2399 if (sdcardConfig()->mode == SDCARD_MODE_NONE) {
2400 cliPrintLine("Not configured");
2402 return;
2405 if (!sdcard_isInserted()) {
2406 cliPrintLine("None inserted");
2407 return;
2410 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2411 cliPrintLine("Startup failed");
2412 return;
2415 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2417 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2418 metadata->manufacturerID,
2419 metadata->numBlocks / 2, /* One block is half a kB */
2420 metadata->productionMonth,
2421 metadata->productionYear,
2422 metadata->productRevisionMajor,
2423 metadata->productRevisionMinor
2426 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2428 cliPrint("'\r\n" "Filesystem: ");
2430 switch (afatfs_getFilesystemState()) {
2431 case AFATFS_FILESYSTEM_STATE_READY:
2432 cliPrint("Ready");
2433 break;
2434 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2435 cliPrint("Initializing");
2436 break;
2437 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2438 case AFATFS_FILESYSTEM_STATE_FATAL:
2439 cliPrint("Fatal");
2441 switch (afatfs_getLastError()) {
2442 case AFATFS_ERROR_BAD_MBR:
2443 cliPrint(" - no FAT MBR partitions");
2444 break;
2445 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2446 cliPrint(" - bad FAT header");
2447 break;
2448 case AFATFS_ERROR_GENERIC:
2449 case AFATFS_ERROR_NONE:
2450 ; // Nothing more detailed to print
2451 break;
2453 break;
2455 cliPrintLinefeed();
2458 #endif
2460 #ifdef USE_FLASH_CHIP
2462 static void cliFlashInfo(const char *cmdName, char *cmdline)
2464 UNUSED(cmdName);
2465 UNUSED(cmdline);
2467 const flashGeometry_t *layout = flashGetGeometry();
2469 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2470 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2472 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2473 const flashPartition_t *partition;
2474 if (index == 0) {
2475 cliPrintLine("Paritions:");
2477 partition = flashPartitionFindByIndex(index);
2478 if (!partition) {
2479 break;
2481 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2483 #ifdef USE_FLASHFS
2484 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2486 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2487 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2488 flashfsGetOffset()
2490 #endif
2494 static void cliFlashErase(const char *cmdName, char *cmdline)
2496 UNUSED(cmdName);
2497 UNUSED(cmdline);
2499 if (!flashfsIsSupported()) {
2500 return;
2503 #ifndef MINIMAL_CLI
2504 uint32_t i = 0;
2505 cliPrintLine("Erasing, please wait ... ");
2506 #else
2507 cliPrintLine("Erasing,");
2508 #endif
2510 cliWriterFlush();
2511 flashfsEraseCompletely();
2513 while (!flashfsIsReady()) {
2514 #ifndef MINIMAL_CLI
2515 cliPrintf(".");
2516 if (i++ > 120) {
2517 i=0;
2518 cliPrintLinefeed();
2521 cliWriterFlush();
2522 #endif
2523 delay(100);
2525 beeper(BEEPER_BLACKBOX_ERASE);
2526 cliPrintLinefeed();
2527 cliPrintLine("Done.");
2530 #ifdef USE_FLASH_TOOLS
2532 static void cliFlashVerify(const char *cmdName, char *cmdline)
2534 UNUSED(cmdline);
2536 cliPrintLine("Verifying");
2537 if (flashfsVerifyEntireFlash()) {
2538 cliPrintLine("Success");
2539 } else {
2540 cliPrintErrorLinef(cmdName, "Failed");
2544 static void cliFlashWrite(const char *cmdName, char *cmdline)
2546 const uint32_t address = atoi(cmdline);
2547 const char *text = strchr(cmdline, ' ');
2549 if (!text) {
2550 cliShowInvalidArgumentCountError(cmdName);
2551 } else {
2552 flashfsSeekAbs(address);
2553 flashfsWrite((uint8_t*)text, strlen(text), true);
2554 flashfsFlushSync();
2556 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2560 static void cliFlashRead(const char *cmdName, char *cmdline)
2562 uint32_t address = atoi(cmdline);
2564 const char *nextArg = strchr(cmdline, ' ');
2566 if (!nextArg) {
2567 cliShowInvalidArgumentCountError(cmdName);
2568 } else {
2569 uint32_t length = atoi(nextArg);
2571 cliPrintLinef("Reading %u bytes at %u:", length, address);
2573 uint8_t buffer[32];
2574 while (length > 0) {
2575 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2577 for (int i = 0; i < bytesRead; i++) {
2578 cliWrite(buffer[i]);
2581 length -= bytesRead;
2582 address += bytesRead;
2584 if (bytesRead == 0) {
2585 //Assume we reached the end of the volume or something fatal happened
2586 break;
2589 cliPrintLinefeed();
2593 #endif
2594 #endif
2596 #ifdef USE_VTX_CONTROL
2597 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2599 // print out vtx channel settings
2600 const char *format = "vtx %u %u %u %u %u %u %u";
2601 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2602 bool equalsDefault = false;
2603 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2604 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2605 if (vtxConfigDefault) {
2606 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2607 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2608 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2609 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2611 cacDefault->auxChannelIndex,
2612 cacDefault->band,
2613 cacDefault->channel,
2614 cacDefault->power,
2615 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2616 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2619 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2621 cac->auxChannelIndex,
2622 cac->band,
2623 cac->channel,
2624 cac->power,
2625 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2626 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2631 static void cliVtx(const char *cmdName, char *cmdline)
2633 const char *format = "vtx %u %u %u %u %u %u %u";
2634 int i, val = 0;
2635 const char *ptr;
2637 if (isEmpty(cmdline)) {
2638 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2639 } else {
2640 #ifdef USE_VTX_TABLE
2641 const uint8_t maxBandIndex = vtxTableConfig()->bands;
2642 const uint8_t maxChannelIndex = vtxTableConfig()->channels;
2643 const uint8_t maxPowerIndex = vtxTableConfig()->powerLevels;
2644 #else
2645 const uint8_t maxBandIndex = VTX_TABLE_MAX_BANDS;
2646 const uint8_t maxChannelIndex = VTX_TABLE_MAX_CHANNELS;
2647 const uint8_t maxPowerIndex = VTX_TABLE_MAX_POWER_LEVELS;
2648 #endif
2649 ptr = cmdline;
2650 i = atoi(ptr++);
2651 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2652 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2653 uint8_t validArgumentCount = 0;
2654 ptr = nextArg(ptr);
2655 if (ptr) {
2656 val = atoi(ptr);
2657 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2658 cac->auxChannelIndex = val;
2659 validArgumentCount++;
2662 ptr = nextArg(ptr);
2663 if (ptr) {
2664 val = atoi(ptr);
2665 if (val >= 0 && val <= maxBandIndex) {
2666 cac->band = val;
2667 validArgumentCount++;
2670 ptr = nextArg(ptr);
2671 if (ptr) {
2672 val = atoi(ptr);
2673 if (val >= 0 && val <= maxChannelIndex) {
2674 cac->channel = val;
2675 validArgumentCount++;
2678 ptr = nextArg(ptr);
2679 if (ptr) {
2680 val = atoi(ptr);
2681 if (val >= 0 && val <= maxPowerIndex) {
2682 cac->power= val;
2683 validArgumentCount++;
2686 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2688 if (validArgumentCount != 6) {
2689 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2690 cliShowInvalidArgumentCountError(cmdName);
2691 } else {
2692 cliDumpPrintLinef(0, false, format,
2694 cac->auxChannelIndex,
2695 cac->band,
2696 cac->channel,
2697 cac->power,
2698 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2699 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2702 } else {
2703 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2708 #endif // VTX_CONTROL
2710 #ifdef USE_VTX_TABLE
2712 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2714 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2715 char freqtmp[5 + 1];
2716 freqbuf[0] = 0;
2717 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2718 for (int channel = 0; channel < channels; channel++) {
2719 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2720 strcat(freqbuf, freqtmp);
2722 return freqbuf;
2725 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2727 char *fmt = "vtxtable band %d %s %c%s";
2728 bool equalsDefault = false;
2730 if (defaultConfig) {
2731 equalsDefault = true;
2732 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2733 equalsDefault = false;
2735 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2736 equalsDefault = false;
2738 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2739 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2740 equalsDefault = false;
2743 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2744 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2745 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2748 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2749 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2750 return headingStr;
2753 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2755 // (max 4 digit + 1 space) per level
2756 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2757 char pwrtmp[5 + 1];
2758 pwrbuf[0] = 0;
2759 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2760 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2761 strcat(pwrbuf, pwrtmp);
2763 return pwrbuf;
2766 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2768 char *fmt = "vtxtable powervalues%s";
2769 bool equalsDefault = false;
2770 if (defaultConfig) {
2771 equalsDefault = true;
2772 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2773 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2774 equalsDefault = false;
2777 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2778 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2779 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2782 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2783 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2784 return headingStr;
2787 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2789 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2790 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2791 pwrbuf[0] = 0;
2792 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2793 strcat(pwrbuf, " ");
2794 strcpy(pwrtmp, labels[pwrindex]);
2795 // trim trailing space
2796 char *sp;
2797 while ((sp = strchr(pwrtmp, ' '))) {
2798 *sp = 0;
2800 strcat(pwrbuf, pwrtmp);
2802 return pwrbuf;
2805 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2807 char *fmt = "vtxtable powerlabels%s";
2808 bool equalsDefault = false;
2809 if (defaultConfig) {
2810 equalsDefault = true;
2811 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2812 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2813 equalsDefault = false;
2816 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2817 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2818 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2821 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2822 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2823 return headingStr;
2826 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2828 bool equalsDefault;
2829 char *fmt;
2831 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2833 // bands
2834 equalsDefault = false;
2835 fmt = "vtxtable bands %d";
2836 if (defaultConfig) {
2837 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2838 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2839 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2841 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2843 // channels
2844 equalsDefault = false;
2845 fmt = "vtxtable channels %d";
2846 if (defaultConfig) {
2847 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2848 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2849 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2851 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2853 // band
2855 for (int band = 0; band < currentConfig->bands; band++) {
2856 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2859 // powerlevels
2861 equalsDefault = false;
2862 fmt = "vtxtable powerlevels %d";
2863 if (defaultConfig) {
2864 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2865 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2866 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2868 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2870 // powervalues
2872 // powerlabels
2873 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2874 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2877 static void cliVtxTable(const char *cmdName, char *cmdline)
2879 char *tok;
2880 char *saveptr;
2882 // Band number or nothing
2883 tok = strtok_r(cmdline, " ", &saveptr);
2885 if (!tok) {
2886 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2887 return;
2890 if (strcasecmp(tok, "bands") == 0) {
2891 tok = strtok_r(NULL, " ", &saveptr);
2892 int bands = atoi(tok);
2893 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2894 cliShowArgumentRangeError(cmdName, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS);
2895 return;
2897 if (bands < vtxTableConfigMutable()->bands) {
2898 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2899 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2902 vtxTableConfigMutable()->bands = bands;
2904 } else if (strcasecmp(tok, "channels") == 0) {
2905 tok = strtok_r(NULL, " ", &saveptr);
2907 int channels = atoi(tok);
2908 if (channels < 0 || channels > VTX_TABLE_MAX_CHANNELS) {
2909 cliShowArgumentRangeError(cmdName, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS);
2910 return;
2912 if (channels < vtxTableConfigMutable()->channels) {
2913 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2914 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2917 vtxTableConfigMutable()->channels = channels;
2919 } else if (strcasecmp(tok, "powerlevels") == 0) {
2920 // Number of power levels
2921 tok = strtok_r(NULL, " ", &saveptr);
2922 if (tok) {
2923 int levels = atoi(tok);
2924 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2925 cliShowArgumentRangeError(cmdName, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS);
2926 } else {
2927 if (levels < vtxTableConfigMutable()->powerLevels) {
2928 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2929 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2931 vtxTableConfigMutable()->powerLevels = levels;
2933 } else {
2934 // XXX Show current level count?
2936 return;
2938 } else if (strcasecmp(tok, "powervalues") == 0) {
2939 // Power values
2940 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2941 int count;
2942 int levels = vtxTableConfigMutable()->powerLevels;
2944 memset(power, 0, sizeof(power));
2946 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2947 int value = atoi(tok);
2948 power[count] = value;
2951 // Check remaining tokens
2953 if (count < levels) {
2954 cliPrintErrorLinef(cmdName, "NOT ENOUGH VALUES (EXPECTED %d)", levels);
2955 return;
2956 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2957 cliPrintErrorLinef(cmdName, "TOO MANY VALUES (EXPECTED %d)", levels);
2958 return;
2961 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
2962 vtxTableConfigMutable()->powerValues[i] = power[i];
2965 } else if (strcasecmp(tok, "powerlabels") == 0) {
2966 // Power labels
2967 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
2968 int levels = vtxTableConfigMutable()->powerLevels;
2969 int count;
2970 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2971 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
2972 for (unsigned i = 0; i < strlen(label[count]); i++) {
2973 label[count][i] = toupper(label[count][i]);
2977 // Check remaining tokens
2979 if (count < levels) {
2980 cliPrintErrorLinef(cmdName, "NOT ENOUGH LABELS (EXPECTED %d)", levels);
2981 return;
2982 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2983 cliPrintErrorLinef(cmdName, "TOO MANY LABELS (EXPECTED %d)", levels);
2984 return;
2987 for (int i = 0; i < count; i++) {
2988 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
2990 } else if (strcasecmp(tok, "band") == 0) {
2992 int bands = vtxTableConfigMutable()->bands;
2994 tok = strtok_r(NULL, " ", &saveptr);
2995 if (!tok) {
2996 return;
2999 int band = atoi(tok);
3000 --band;
3002 if (band < 0 || band >= bands) {
3003 cliShowArgumentRangeError(cmdName, "BAND NUMBER", 1, bands);
3004 return;
3007 // Band name
3008 tok = strtok_r(NULL, " ", &saveptr);
3010 if (!tok) {
3011 return;
3014 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
3015 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
3016 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
3017 for (unsigned i = 0; i < strlen(bandname); i++) {
3018 bandname[i] = toupper(bandname[i]);
3021 // Band letter
3022 tok = strtok_r(NULL, " ", &saveptr);
3024 if (!tok) {
3025 return;
3028 char bandletter = toupper(tok[0]);
3030 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
3031 int channel = 0;
3032 int channels = vtxTableConfigMutable()->channels;
3033 bool isFactory = false;
3035 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
3036 if (channel == 0 && !isdigit(tok[0])) {
3037 channel -= 1;
3038 if (strcasecmp(tok, "FACTORY") == 0) {
3039 isFactory = true;
3040 } else if (strcasecmp(tok, "CUSTOM") == 0) {
3041 isFactory = false;
3042 } else {
3043 cliPrintErrorLinef(cmdName, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
3044 return;
3047 int freq = atoi(tok);
3048 if (freq < 0) {
3049 cliPrintErrorLinef(cmdName, "INVALID FREQUENCY %s", tok);
3050 return;
3052 bandfreq[channel] = freq;
3055 if (channel < channels) {
3056 cliPrintErrorLinef(cmdName, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
3057 return;
3058 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3059 cliPrintErrorLinef(cmdName, "TOO MANY FREQUENCIES (EXPECTED %d)", channels);
3060 return;
3063 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
3064 vtxTableConfigMutable()->bandLetters[band] = bandletter;
3066 for (int i = 0; i < channel; i++) {
3067 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
3069 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
3070 } else {
3071 // Bad subcommand
3072 cliPrintErrorLinef(cmdName, "INVALID SUBCOMMAND %s", tok);
3076 static void cliVtxInfo(const char *cmdName, char *cmdline)
3078 UNUSED(cmdline);
3080 // Display the available power levels
3081 uint16_t levels[VTX_TABLE_MAX_POWER_LEVELS];
3082 uint16_t powers[VTX_TABLE_MAX_POWER_LEVELS];
3083 vtxDevice_t *vtxDevice = vtxCommonDevice();
3084 if (vtxDevice) {
3085 uint8_t level_count = vtxCommonGetVTXPowerLevels(vtxDevice, levels, powers);
3087 if (level_count) {
3088 for (int i = 0; i < level_count; i++) {
3089 cliPrintLinef("level %d dBm, power %d mW", levels[i], powers[i]);
3091 } else {
3092 cliPrintErrorLinef(cmdName, "NO POWER VALUES DEFINED");
3094 } else {
3095 cliPrintErrorLinef(cmdName, "NO VTX");
3098 #endif // USE_VTX_TABLE
3100 #if defined(USE_SIMPLIFIED_TUNING)
3101 static void applySimplifiedTuningAllProfiles(void)
3103 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3104 applySimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3108 static void cliSimplifiedTuning(const char *cmdName, char *cmdline)
3110 if (strcasecmp(cmdline, "apply") == 0) {
3111 applySimplifiedTuningAllProfiles();
3113 cliPrintLine("Applied simplified tuning.");
3114 } else if (strcasecmp(cmdline, "disable") == 0) {
3115 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3116 disableSimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3119 cliPrintLine("Disabled simplified tuning.");
3120 } else {
3121 cliShowParseError(cmdName);
3124 #endif
3126 static void printName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
3128 const bool equalsDefault = strlen(pilotConfig->name) == 0;
3129 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->name);
3132 #if defined(USE_BOARD_INFO)
3134 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
3136 static void printBoardName(dumpFlags_t dumpMask)
3138 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
3139 cliPrintLinef("board_name %s", getBoardName());
3143 static void cliBoardName(const char *cmdName, char *cmdline)
3145 const unsigned int len = strlen(cmdline);
3146 const char *boardName = getBoardName();
3147 if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
3148 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "BOARD_NAME", boardName);
3149 } else {
3150 if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
3151 boardInformationUpdated = true;
3153 cliPrintHashLine("Set board_name.");
3155 printBoardName(DUMP_ALL);
3159 static void printManufacturerId(dumpFlags_t dumpMask)
3161 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
3162 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3166 static void cliManufacturerId(const char *cmdName, char *cmdline)
3168 const unsigned int len = strlen(cmdline);
3169 const char *manufacturerId = getManufacturerId();
3170 if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
3171 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
3172 } else {
3173 if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
3174 boardInformationUpdated = true;
3176 cliPrintHashLine("Set manufacturer_id.");
3178 printManufacturerId(DUMP_ALL);
3182 #if defined(USE_SIGNATURE)
3183 static void writeSignature(char *signatureStr, uint8_t *signature)
3185 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3186 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
3190 static void cliSignature(const char *cmdName, char *cmdline)
3192 const int len = strlen(cmdline);
3194 uint8_t signature[SIGNATURE_LENGTH] = {0};
3195 if (len > 0) {
3196 if (len != 2 * SIGNATURE_LENGTH) {
3197 cliPrintErrorLinef(cmdName, "INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
3199 return;
3202 #define BLOCK_SIZE 2
3203 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3204 char temp[BLOCK_SIZE + 1];
3205 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3206 temp[BLOCK_SIZE] = '\0';
3207 char *end;
3208 unsigned result = strtoul(temp, &end, 16);
3209 if (end == &temp[BLOCK_SIZE]) {
3210 signature[i] = result;
3211 } else {
3212 cliPrintErrorLinef(cmdName, "INVALID CHARACTER FOUND: %c", end[0]);
3214 return;
3217 #undef BLOCK_SIZE
3220 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3221 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3222 writeSignature(signatureStr, getSignature());
3223 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "SIGNATURE", signatureStr);
3224 } else {
3225 if (len > 0 && !configIsInCopy && setSignature(signature)) {
3226 signatureUpdated = true;
3228 writeSignature(signatureStr, getSignature());
3230 cliPrintHashLine("Set signature.");
3231 } else if (signatureUpdated || signatureIsSet()) {
3232 writeSignature(signatureStr, getSignature());
3235 cliPrintLinef("signature %s", signatureStr);
3238 #endif
3240 #undef ERROR_MESSAGE
3242 #endif // USE_BOARD_INFO
3244 static void cliMcuId(const char *cmdName, char *cmdline)
3246 UNUSED(cmdName);
3247 UNUSED(cmdline);
3249 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3252 static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
3254 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3255 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
3256 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3257 const char *format = "feature -%s";
3258 const bool equalsDefault = (~defaultMask | mask) & (1 << i);
3259 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3260 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
3261 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3264 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
3265 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3266 const char *format = "feature %s";
3267 if (defaultMask & (1 << i)) {
3268 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
3270 if (mask & (1 << i)) {
3271 const bool equalsDefault = (defaultMask | ~mask) & (1 << i);
3272 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3273 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3279 static void cliFeature(const char *cmdName, char *cmdline)
3281 uint32_t len = strlen(cmdline);
3282 const uint32_t mask = featureConfig()->enabledFeatures;
3283 if (len == 0) {
3284 cliPrint("Enabled: ");
3285 for (uint32_t i = 0; ; i++) {
3286 if (featureNames[i] == NULL) {
3287 break;
3289 if (mask & (1 << i)) {
3290 cliPrintf("%s ", featureNames[i]);
3293 cliPrintLinefeed();
3294 } else if (strncasecmp(cmdline, "list", len) == 0) {
3295 cliPrint("Available:");
3296 for (uint32_t i = 0; ; i++) {
3297 if (featureNames[i] == NULL)
3298 break;
3299 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
3300 cliPrintf(" %s", featureNames[i]);
3302 cliPrintLinefeed();
3303 return;
3304 } else {
3305 uint32_t feature;
3307 bool remove = false;
3308 if (cmdline[0] == '-') {
3309 // remove feature
3310 remove = true;
3311 cmdline++; // skip over -
3312 len--;
3315 for (uint32_t i = 0; ; i++) {
3316 if (featureNames[i] == NULL) {
3317 cliPrintErrorLinef(cmdName, "INVALID NAME");
3318 break;
3321 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
3322 feature = 1 << i;
3323 #ifndef USE_GPS
3324 if (feature & FEATURE_GPS) {
3325 cliPrintLine("unavailable");
3326 break;
3328 #endif
3329 #ifndef USE_RANGEFINDER
3330 if (feature & FEATURE_RANGEFINDER) {
3331 cliPrintLine("unavailable");
3332 break;
3334 #endif
3335 if (remove) {
3336 featureConfigClear(feature);
3337 cliPrint("Disabled");
3338 } else {
3339 featureConfigSet(feature);
3340 cliPrint("Enabled");
3342 cliPrintLinef(" %s", featureNames[i]);
3343 break;
3349 #if defined(USE_BEEPER)
3350 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3352 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3353 const uint8_t beeperCount = beeperTableEntryCount();
3354 for (int32_t i = 0; i < beeperCount - 1; i++) {
3355 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3356 const char *formatOff = "%s -%s";
3357 const char *formatOn = "%s %s";
3358 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3359 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3360 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3361 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3362 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3367 static void processBeeperCommand(const char *cmdName, char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3369 uint32_t len = strlen(cmdline);
3370 uint8_t beeperCount = beeperTableEntryCount();
3372 if (len == 0) {
3373 cliPrintf("Disabled:");
3374 for (int32_t i = 0; ; i++) {
3375 if (i == beeperCount - 1) {
3376 if (*offFlags == 0)
3377 cliPrint(" none");
3378 break;
3381 if (beeperModeMaskForTableIndex(i) & *offFlags)
3382 cliPrintf(" %s", beeperNameForTableIndex(i));
3384 cliPrintLinefeed();
3385 } else if (strncasecmp(cmdline, "list", len) == 0) {
3386 cliPrint("Available:");
3387 for (uint32_t i = 0; i < beeperCount; i++) {
3388 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3389 cliPrintf(" %s", beeperNameForTableIndex(i));
3392 cliPrintLinefeed();
3393 } else {
3394 bool remove = false;
3395 if (cmdline[0] == '-') {
3396 remove = true; // this is for beeper OFF condition
3397 cmdline++;
3398 len--;
3401 for (uint32_t i = 0; ; i++) {
3402 if (i == beeperCount) {
3403 cliPrintErrorLinef(cmdName, "INVALID NAME");
3404 break;
3406 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3407 if (remove) { // beeper off
3408 if (i == BEEPER_ALL - 1) {
3409 *offFlags = allowedFlags;
3410 } else {
3411 *offFlags |= beeperModeMaskForTableIndex(i);
3413 cliPrint("Disabled");
3415 else { // beeper on
3416 if (i == BEEPER_ALL - 1) {
3417 *offFlags = 0;
3418 } else {
3419 *offFlags &= ~beeperModeMaskForTableIndex(i);
3421 cliPrint("Enabled");
3423 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3424 break;
3430 #if defined(USE_DSHOT)
3431 static void cliBeacon(const char *cmdName, char *cmdline)
3433 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3435 #endif
3437 static void cliBeeper(const char *cmdName, char *cmdline)
3439 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3441 #endif
3443 #if defined(USE_RX_BIND)
3444 static void cliRxBind(const char *cmdName, char *cmdline)
3446 UNUSED(cmdline);
3447 if (!startRxBind()) {
3448 cliPrintErrorLinef(cmdName, "Not supported.");
3449 } else {
3450 cliPrintLinef("Binding...");
3453 #endif
3455 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3457 bool equalsDefault = true;
3458 char buf[16];
3459 char bufDefault[16];
3460 uint32_t i;
3462 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3463 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3464 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3465 if (defaultRxConfig) {
3466 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3467 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3470 buf[i] = '\0';
3472 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3473 const char *formatMap = "map %s";
3474 if (defaultRxConfig) {
3475 bufDefault[i] = '\0';
3476 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3478 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3482 static void cliMap(const char *cmdName, char *cmdline)
3484 uint32_t i;
3485 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3487 uint32_t len = strlen(cmdline);
3488 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3490 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3491 buf[i] = toupper((unsigned char)cmdline[i]);
3493 buf[i] = '\0';
3495 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3496 buf[i] = toupper((unsigned char)cmdline[i]);
3498 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3499 continue;
3501 cliShowParseError(cmdName);
3502 return;
3504 parseRcChannels(buf, rxConfigMutable());
3505 } else if (len > 0) {
3506 cliShowInvalidArgumentCountError(cmdName);
3507 return;
3510 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3511 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3514 buf[i] = '\0';
3515 cliPrintLinef("map %s", buf);
3518 static char *skipSpace(char *buffer)
3520 while (*(buffer) == ' ') {
3521 buffer++;
3524 return buffer;
3527 static char *checkCommand(char *cmdline, const char *command)
3529 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3530 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3531 return skipSpace(cmdline + strlen(command) + 1);
3532 } else {
3533 return 0;
3537 static void cliRebootEx(rebootTarget_e rebootTarget)
3539 cliPrint("\r\nRebooting");
3540 cliWriterFlush();
3541 waitForSerialPortToFinishTransmitting(cliPort);
3542 motorShutdown();
3544 switch (rebootTarget) {
3545 case REBOOT_TARGET_BOOTLOADER_ROM:
3546 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3548 break;
3549 #if defined(USE_FLASH_BOOT_LOADER)
3550 case REBOOT_TARGET_BOOTLOADER_FLASH:
3551 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3553 break;
3554 #endif
3555 case REBOOT_TARGET_FIRMWARE:
3556 default:
3557 systemReset();
3559 break;
3563 static void cliReboot(void)
3565 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3568 static void cliBootloader(const char *cmdName, char *cmdline)
3570 rebootTarget_e rebootTarget;
3571 if (
3572 #if !defined(USE_FLASH_BOOT_LOADER)
3573 isEmpty(cmdline) ||
3574 #endif
3575 strncasecmp(cmdline, "rom", 3) == 0) {
3576 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3578 cliPrintHashLine("restarting in ROM bootloader mode");
3579 #if defined(USE_FLASH_BOOT_LOADER)
3580 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3581 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3583 cliPrintHashLine("restarting in flash bootloader mode");
3584 #endif
3585 } else {
3586 cliPrintErrorLinef(cmdName, "Invalid option");
3588 return;
3591 cliRebootEx(rebootTarget);
3594 static void cliExit(const char *cmdName, char *cmdline)
3596 UNUSED(cmdName);
3597 UNUSED(cmdline);
3599 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3600 cliWriterFlush();
3602 *cliBuffer = '\0';
3603 bufferIndex = 0;
3604 cliMode = false;
3605 // incase a motor was left running during motortest, clear it here
3606 mixerResetDisarmedMotors();
3607 cliReboot();
3610 #ifdef USE_GPS
3611 static void cliGpsPassthrough(const char *cmdName, char *cmdline)
3613 UNUSED(cmdName);
3614 UNUSED(cmdline);
3616 gpsEnablePassthrough(cliPort);
3618 #endif
3620 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3621 static void cliPrintGyroRegisters(uint8_t whichSensor)
3623 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3624 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3625 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3628 static void cliDumpGyroRegisters(const char *cmdName, char *cmdline)
3630 UNUSED(cmdName);
3631 UNUSED(cmdline);
3633 #ifdef USE_MULTI_GYRO
3634 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3635 cliPrintLinef("\r\n# Gyro 1");
3636 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3638 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3639 cliPrintLinef("\r\n# Gyro 2");
3640 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3642 #else
3643 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3644 #endif
3646 #endif
3649 static int parseOutputIndex(const char *cmdName, char *pch, bool allowAllEscs) {
3650 int outputIndex = atoi(pch);
3651 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3652 cliPrintLinef("Using output %d.", outputIndex);
3653 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3654 cliPrintLinef("Using all outputs.");
3655 } else {
3656 cliPrintErrorLinef(cmdName, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3658 return -1;
3661 return outputIndex;
3664 #if defined(USE_DSHOT)
3665 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3667 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3668 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3669 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3671 enum {
3672 ESC_INFO_KISS_V1,
3673 ESC_INFO_KISS_V2,
3674 ESC_INFO_BLHELI32
3677 #define ESC_INFO_VERSION_POSITION 12
3679 static void printEscInfo(const char *cmdName, const uint8_t *escInfoBuffer, uint8_t bytesRead)
3681 bool escInfoReceived = false;
3682 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3683 uint8_t escInfoVersion;
3684 uint8_t frameLength;
3685 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3686 escInfoVersion = ESC_INFO_BLHELI32;
3687 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3688 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3689 escInfoVersion = ESC_INFO_KISS_V2;
3690 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3691 } else {
3692 escInfoVersion = ESC_INFO_KISS_V1;
3693 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3696 if (bytesRead == frameLength) {
3697 escInfoReceived = true;
3699 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3700 uint8_t firmwareVersion = 0;
3701 uint8_t firmwareSubVersion = 0;
3702 uint8_t escType = 0;
3703 switch (escInfoVersion) {
3704 case ESC_INFO_KISS_V1:
3705 firmwareVersion = escInfoBuffer[12];
3706 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3707 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3709 break;
3710 case ESC_INFO_KISS_V2:
3711 firmwareVersion = escInfoBuffer[13];
3712 firmwareSubVersion = escInfoBuffer[14];
3713 escType = escInfoBuffer[15];
3715 break;
3716 case ESC_INFO_BLHELI32:
3717 firmwareVersion = escInfoBuffer[13];
3718 firmwareSubVersion = escInfoBuffer[14];
3719 escType = escInfoBuffer[15];
3721 break;
3724 cliPrint("ESC Type: ");
3725 switch (escInfoVersion) {
3726 case ESC_INFO_KISS_V1:
3727 case ESC_INFO_KISS_V2:
3728 switch (escType) {
3729 case 1:
3730 cliPrintLine("KISS8A");
3732 break;
3733 case 2:
3734 cliPrintLine("KISS16A");
3736 break;
3737 case 3:
3738 cliPrintLine("KISS24A");
3740 break;
3741 case 5:
3742 cliPrintLine("KISS Ultralite");
3744 break;
3745 default:
3746 cliPrintLine("unknown");
3748 break;
3751 break;
3752 case ESC_INFO_BLHELI32:
3754 char *escType = (char *)(escInfoBuffer + 31);
3755 escType[32] = 0;
3756 cliPrintLine(escType);
3759 break;
3762 cliPrint("MCU Serial No: 0x");
3763 for (int i = 0; i < 12; i++) {
3764 if (i && (i % 3 == 0)) {
3765 cliPrint("-");
3767 cliPrintf("%02x", escInfoBuffer[i]);
3769 cliPrintLinefeed();
3771 switch (escInfoVersion) {
3772 case ESC_INFO_KISS_V1:
3773 case ESC_INFO_KISS_V2:
3774 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3776 break;
3777 case ESC_INFO_BLHELI32:
3778 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3780 break;
3782 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3783 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3784 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3785 if (escInfoVersion == ESC_INFO_BLHELI32) {
3786 uint8_t setting = escInfoBuffer[18];
3787 cliPrint("Low voltage Limit: ");
3788 switch (setting) {
3789 case 0:
3790 cliPrintLine("off");
3792 break;
3793 case 255:
3794 cliPrintLine("unsupported");
3796 break;
3797 default:
3798 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3800 break;
3803 setting = escInfoBuffer[19];
3804 cliPrint("Current Limit: ");
3805 switch (setting) {
3806 case 0:
3807 cliPrintLine("off");
3809 break;
3810 case 255:
3811 cliPrintLine("unsupported");
3813 break;
3814 default:
3815 cliPrintLinef("%d", setting);
3817 break;
3820 for (int i = 0; i < 4; i++) {
3821 setting = escInfoBuffer[i + 20];
3822 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3826 } else {
3827 cliPrintErrorLinef(cmdName, "CHECKSUM ERROR.");
3832 if (!escInfoReceived) {
3833 cliPrintLine("No Info.");
3837 static void executeEscInfoCommand(const char *cmdName, uint8_t escIndex)
3839 cliPrintLinef("Info for ESC %d:", escIndex);
3841 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3843 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3845 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, DSHOT_CMD_TYPE_BLOCKING);
3847 delay(10);
3849 printEscInfo(cmdName, escInfoBuffer, getNumberEscBytesRead());
3851 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3853 static void cliDshotProg(const char *cmdName, char *cmdline)
3855 if (isEmpty(cmdline) || !isMotorProtocolDshot()) {
3856 cliShowParseError(cmdName);
3858 return;
3861 char *saveptr;
3862 char *pch = strtok_r(cmdline, " ", &saveptr);
3863 int pos = 0;
3864 int escIndex = 0;
3865 bool firstCommand = true;
3866 while (pch != NULL) {
3867 switch (pos) {
3868 case 0:
3869 escIndex = parseOutputIndex(cmdName, pch, true);
3870 if (escIndex == -1) {
3871 return;
3874 break;
3875 default:
3877 int command = atoi(pch);
3878 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3879 if (firstCommand) {
3880 // pwmDisableMotors();
3881 motorDisable();
3883 firstCommand = false;
3886 if (command != DSHOT_CMD_ESC_INFO) {
3887 dshotCommandWrite(escIndex, getMotorCount(), command, DSHOT_CMD_TYPE_BLOCKING);
3888 } else {
3889 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3890 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3891 if (escIndex != ALL_MOTORS) {
3892 executeEscInfoCommand(cmdName, escIndex);
3893 } else {
3894 for (uint8_t i = 0; i < getMotorCount(); i++) {
3895 executeEscInfoCommand(cmdName, i);
3898 } else
3899 #endif
3901 cliPrintLine("Not supported.");
3905 cliPrintLinef("Command Sent: %d", command);
3907 } else {
3908 cliPrintErrorLinef(cmdName, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3912 break;
3915 pos++;
3916 pch = strtok_r(NULL, " ", &saveptr);
3919 motorEnable();
3921 #endif // USE_DSHOT
3923 #ifdef USE_ESCSERIAL
3924 static void cliEscPassthrough(const char *cmdName, char *cmdline)
3926 if (isEmpty(cmdline)) {
3927 cliShowInvalidArgumentCountError(cmdName);
3929 return;
3932 char *saveptr;
3933 char *pch = strtok_r(cmdline, " ", &saveptr);
3934 int pos = 0;
3935 uint8_t mode = 0;
3936 int escIndex = 0;
3937 while (pch != NULL) {
3938 switch (pos) {
3939 case 0:
3940 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3941 mode = PROTOCOL_SIMONK;
3942 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3943 mode = PROTOCOL_BLHELI;
3944 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3945 mode = PROTOCOL_KISS;
3946 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3947 mode = PROTOCOL_KISSALL;
3948 } else {
3949 cliShowParseError(cmdName);
3951 return;
3953 break;
3954 case 1:
3955 escIndex = parseOutputIndex(cmdName, pch, mode == PROTOCOL_KISS);
3956 if (escIndex == -1) {
3957 return;
3960 break;
3961 default:
3962 cliShowInvalidArgumentCountError(cmdName);
3964 return;
3966 break;
3969 pos++;
3970 pch = strtok_r(NULL, " ", &saveptr);
3973 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
3974 cliPrintErrorLinef(cmdName, "Error starting ESC connection");
3977 #endif
3979 #ifndef USE_QUAD_MIXER_ONLY
3980 static void cliMixer(const char *cmdName, char *cmdline)
3982 int len;
3984 len = strlen(cmdline);
3986 if (len == 0) {
3987 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3988 return;
3989 } else if (strncasecmp(cmdline, "list", len) == 0) {
3990 cliPrint("Available:");
3991 for (uint32_t i = 0; ; i++) {
3992 if (mixerNames[i] == NULL)
3993 break;
3994 cliPrintf(" %s", mixerNames[i]);
3996 cliPrintLinefeed();
3997 return;
4000 for (uint32_t i = 0; ; i++) {
4001 if (mixerNames[i] == NULL) {
4002 cliPrintErrorLinef(cmdName, "INVALID NAME");
4003 return;
4005 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
4006 mixerConfigMutable()->mixerMode = i + 1;
4007 break;
4011 cliMixer(cmdName, "");
4013 #endif
4015 static void cliMotor(const char *cmdName, char *cmdline)
4017 if (isEmpty(cmdline)) {
4018 cliShowInvalidArgumentCountError(cmdName);
4020 return;
4023 int motorIndex = 0;
4024 int motorValue = 0;
4026 char *saveptr;
4027 char *pch = strtok_r(cmdline, " ", &saveptr);
4028 int index = 0;
4029 while (pch != NULL) {
4030 switch (index) {
4031 case 0:
4032 motorIndex = parseOutputIndex(cmdName, pch, true);
4033 if (motorIndex == -1) {
4034 return;
4037 break;
4038 case 1:
4039 motorValue = atoi(pch);
4041 break;
4043 index++;
4044 pch = strtok_r(NULL, " ", &saveptr);
4047 if (index == 2) {
4048 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
4049 cliShowArgumentRangeError(cmdName, "VALUE", 1000, 2000);
4050 } else {
4051 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
4053 if (motorIndex != ALL_MOTORS) {
4054 motor_disarmed[motorIndex] = motorOutputValue;
4056 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
4057 } else {
4058 for (int i = 0; i < getMotorCount(); i++) {
4059 motor_disarmed[i] = motorOutputValue;
4062 cliPrintLinef("all motors: %d", motorOutputValue);
4065 } else {
4066 cliShowInvalidArgumentCountError(cmdName);
4070 #ifndef MINIMAL_CLI
4071 static void cliPlaySound(const char *cmdName, char *cmdline)
4073 int i;
4074 const char *name;
4075 static int lastSoundIdx = -1;
4077 if (isEmpty(cmdline)) {
4078 i = lastSoundIdx + 1; //next sound index
4079 if ((name=beeperNameForTableIndex(i)) == NULL) {
4080 while (true) { //no name for index; try next one
4081 if (++i >= beeperTableEntryCount())
4082 i = 0; //if end then wrap around to first entry
4083 if ((name=beeperNameForTableIndex(i)) != NULL)
4084 break; //if name OK then play sound below
4085 if (i == lastSoundIdx + 1) { //prevent infinite loop
4086 cliPrintErrorLinef(cmdName, "ERROR PLAYING SOUND");
4087 return;
4091 } else { //index value was given
4092 i = atoi(cmdline);
4093 if ((name=beeperNameForTableIndex(i)) == NULL) {
4094 cliPrintLinef("No sound for index %d", i);
4095 return;
4098 lastSoundIdx = i;
4099 beeperSilence();
4100 cliPrintLinef("Playing sound %d: %s", i, name);
4101 beeper(beeperModeForTableIndex(i));
4103 #endif
4105 static void cliProfile(const char *cmdName, char *cmdline)
4107 if (isEmpty(cmdline)) {
4108 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4109 return;
4110 } else {
4111 const int i = atoi(cmdline);
4112 if (i >= 0 && i < PID_PROFILE_COUNT) {
4113 changePidProfile(i);
4114 cliProfile(cmdName, "");
4115 } else {
4116 cliPrintErrorLinef(cmdName, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
4121 static void cliRateProfile(const char *cmdName, char *cmdline)
4123 if (isEmpty(cmdline)) {
4124 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4125 return;
4126 } else {
4127 const int i = atoi(cmdline);
4128 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
4129 changeControlRateProfile(i);
4130 cliRateProfile(cmdName, "");
4131 } else {
4132 cliPrintErrorLinef(cmdName, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
4137 static void cliDumpPidProfile(const char *cmdName, uint8_t pidProfileIndex, dumpFlags_t dumpMask)
4139 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4140 // Faulty values
4141 return;
4144 pidProfileIndexToUse = pidProfileIndex;
4146 cliPrintLinefeed();
4147 cliProfile(cmdName, "");
4149 char profileStr[10];
4150 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4151 dumpAllValues(cmdName, PROFILE_VALUE, dumpMask, profileStr);
4153 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4156 static void cliDumpRateProfile(const char *cmdName, uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4158 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4159 // Faulty values
4160 return;
4163 rateProfileIndexToUse = rateProfileIndex;
4165 cliPrintLinefeed();
4166 cliRateProfile(cmdName, "");
4168 char rateProfileStr[14];
4169 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4170 dumpAllValues(cmdName, PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4172 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4175 #ifdef USE_CLI_BATCH
4176 static void cliPrintCommandBatchWarning(const char *cmdName, const char *warning)
4178 cliPrintErrorLinef(cmdName, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4179 if (warning) {
4180 cliPrintErrorLinef(cmdName, warning);
4184 static void resetCommandBatch(void)
4186 commandBatchActive = false;
4187 commandBatchError = false;
4190 static void cliBatch(const char *cmdName, char *cmdline)
4192 if (strncasecmp(cmdline, "start", 5) == 0) {
4193 if (!commandBatchActive) {
4194 commandBatchActive = true;
4195 commandBatchError = false;
4197 cliPrintLine("Command batch started");
4198 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4199 if (commandBatchActive && commandBatchError) {
4200 cliPrintCommandBatchWarning(cmdName, NULL);
4201 } else {
4202 cliPrintLine("Command batch ended");
4204 resetCommandBatch();
4205 } else {
4206 cliPrintErrorLinef(cmdName, "Invalid option");
4209 #endif
4211 static bool prepareSave(void)
4213 #if defined(USE_CUSTOM_DEFAULTS)
4214 if (processingCustomDefaults) {
4215 return true;
4217 #endif
4219 #ifdef USE_CLI_BATCH
4220 if (commandBatchActive && commandBatchError) {
4221 return false;
4223 #endif
4225 #if defined(USE_BOARD_INFO)
4226 if (boardInformationUpdated) {
4227 persistBoardInformation();
4229 #if defined(USE_SIGNATURE)
4230 if (signatureUpdated) {
4231 persistSignature();
4233 #endif
4234 #endif // USE_BOARD_INFO
4236 return true;
4239 bool tryPrepareSave(const char *cmdName)
4241 bool success = prepareSave();
4242 #if defined(USE_CLI_BATCH)
4243 if (!success) {
4244 cliPrintCommandBatchWarning(cmdName, "PLEASE FIX ERRORS THEN 'SAVE'");
4245 resetCommandBatch();
4247 return false;
4249 #else
4250 UNUSED(cmdName);
4251 UNUSED(success);
4252 #endif
4254 return true;
4257 static void cliSave(const char *cmdName, char *cmdline)
4259 UNUSED(cmdline);
4261 if (tryPrepareSave(cmdName)) {
4262 writeEEPROM();
4263 cliPrintHashLine("saving");
4265 cliReboot();
4269 #if defined(USE_CUSTOM_DEFAULTS)
4270 bool resetConfigToCustomDefaults(void)
4272 resetConfig();
4274 #ifdef USE_CLI_BATCH
4275 commandBatchError = false;
4276 #endif
4278 cliProcessCustomDefaults(true);
4280 #if defined(USE_SIMPLIFIED_TUNING)
4281 applySimplifiedTuningAllProfiles();
4282 #endif
4284 return prepareSave();
4287 static bool customDefaultsHasNext(const char *customDefaultsPtr)
4289 return *customDefaultsPtr && *customDefaultsPtr != 0xFF && customDefaultsPtr < customDefaultsEnd;
4292 static const char *parseCustomDefaultsHeaderElement(char *dest, const char *customDefaultsPtr, const char *prefix, const char terminator, const unsigned maxLength)
4294 char *endPtr = NULL;
4295 unsigned len = strlen(prefix);
4296 if (customDefaultsPtr && customDefaultsHasNext(customDefaultsPtr) && strncmp(customDefaultsPtr, prefix, len) == 0) {
4297 customDefaultsPtr += len;
4298 endPtr = strchr(customDefaultsPtr, terminator);
4301 if (endPtr && customDefaultsHasNext(endPtr)) {
4302 len = endPtr - customDefaultsPtr;
4303 memcpy(dest, customDefaultsPtr, MIN(len, maxLength));
4305 customDefaultsPtr += len;
4307 return customDefaultsPtr;
4310 return NULL;
4313 static void parseCustomDefaultsHeader(void)
4315 const char *customDefaultsPtr = customDefaultsStart;
4316 if (strncmp(customDefaultsPtr, CUSTOM_DEFAULTS_START_PREFIX, strlen(CUSTOM_DEFAULTS_START_PREFIX)) == 0) {
4317 customDefaultsFound = true;
4319 customDefaultsPtr = strchr(customDefaultsPtr, '\n');
4320 if (customDefaultsPtr && customDefaultsHasNext(customDefaultsPtr)) {
4321 customDefaultsPtr++;
4324 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsManufacturerId, customDefaultsPtr, CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX, CUSTOM_DEFAULTS_BOARD_NAME_PREFIX[0], MAX_MANUFACTURER_ID_LENGTH);
4326 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsBoardName, customDefaultsPtr, CUSTOM_DEFAULTS_BOARD_NAME_PREFIX, CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX[0], MAX_BOARD_NAME_LENGTH);
4328 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsChangesetId, customDefaultsPtr, CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX, CUSTOM_DEFAULTS_DATE_PREFIX[0], MAX_CHANGESET_ID_LENGTH);
4330 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsDate, customDefaultsPtr, CUSTOM_DEFAULTS_DATE_PREFIX, '\n', MAX_DATE_LENGTH);
4333 customDefaultsHeaderParsed = true;
4336 bool hasCustomDefaults(void)
4338 if (!customDefaultsHeaderParsed) {
4339 parseCustomDefaultsHeader();
4342 return customDefaultsFound;
4344 #endif
4346 static void cliDefaults(const char *cmdName, char *cmdline)
4348 bool saveConfigs = true;
4349 uint16_t parameterGroupId = 0;
4350 #if defined(USE_CUSTOM_DEFAULTS)
4351 bool useCustomDefaults = true;
4352 #elif defined(USE_CUSTOM_DEFAULTS_ADDRESS)
4353 // Required to keep the linker from eliminating these
4354 if (customDefaultsStart != customDefaultsEnd) {
4355 delay(0);
4357 #endif
4359 char *saveptr;
4360 char* tok = strtok_r(cmdline, " ", &saveptr);
4361 int index = 0;
4362 bool expectParameterGroupId = false;
4363 while (tok != NULL) {
4364 if (expectParameterGroupId) {
4365 parameterGroupId = atoi(tok);
4366 expectParameterGroupId = false;
4368 if (!parameterGroupId) {
4369 cliShowParseError(cmdName);
4371 return;
4373 } else if (strcasestr(tok, "group_id")) {
4374 expectParameterGroupId = true;
4375 } else if (strcasestr(tok, "nosave")) {
4376 saveConfigs = false;
4377 #if defined(USE_CUSTOM_DEFAULTS)
4378 } else if (strcasestr(tok, "bare")) {
4379 useCustomDefaults = false;
4380 } else if (strcasestr(tok, "show")) {
4381 if (index != 0) {
4382 cliShowParseError(cmdName);
4383 } else if (hasCustomDefaults()) {
4384 char *customDefaultsPtr = customDefaultsStart;
4385 while (customDefaultsHasNext(customDefaultsPtr)) {
4386 if (*customDefaultsPtr != '\n') {
4387 cliPrintf("%c", *customDefaultsPtr++);
4388 } else {
4389 cliPrintLinefeed();
4390 customDefaultsPtr++;
4393 } else {
4394 cliPrintError(cmdName, "NO CUSTOM DEFAULTS FOUND");
4397 return;
4398 #endif
4399 } else {
4400 cliShowParseError(cmdName);
4402 return;
4405 index++;
4406 tok = strtok_r(NULL, " ", &saveptr);
4409 if (expectParameterGroupId) {
4410 cliShowParseError(cmdName);
4412 return;
4415 if (parameterGroupId) {
4416 cliPrintLinef("\r\n# resetting group %d to defaults", parameterGroupId);
4417 backupConfigs();
4418 } else {
4419 cliPrintHashLine("resetting to defaults");
4422 resetConfig();
4424 #ifdef USE_CLI_BATCH
4425 // Reset only the error state and allow the batch active state to remain.
4426 // This way if a "defaults nosave" was issued after the "batch on" we'll
4427 // only reset the current error state but the batch will still be active
4428 // for subsequent commands.
4429 commandBatchError = false;
4430 #endif
4432 #if defined(USE_CUSTOM_DEFAULTS)
4433 if (useCustomDefaults) {
4434 cliProcessCustomDefaults(false);
4436 #endif
4438 #if defined(USE_SIMPLIFIED_TUNING)
4439 applySimplifiedTuningAllProfiles();
4440 #endif
4442 if (parameterGroupId) {
4443 restoreConfigs(parameterGroupId);
4446 if (saveConfigs && tryPrepareSave(cmdName)) {
4447 writeUnmodifiedConfigToEEPROM();
4449 cliReboot();
4453 static void cliPrintVarDefault(const char *cmdName, const clivalue_t *value)
4455 const pgRegistry_t *pg = pgFind(value->pgn);
4456 if (pg) {
4457 const char *defaultFormat = "Default value: ";
4458 const int valueOffset = getValueOffset(value);
4459 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
4460 if (!equalsDefault) {
4461 cliPrintf(defaultFormat, value->name);
4462 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
4463 cliPrintLinefeed();
4468 STATIC_UNIT_TESTED void cliGet(const char *cmdName, char *cmdline)
4470 const clivalue_t *val;
4471 int matchedCommands = 0;
4473 pidProfileIndexToUse = getCurrentPidProfileIndex();
4474 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4476 backupAndResetConfigs(true);
4478 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4479 if (strcasestr(valueTable[i].name, cmdline)) {
4480 val = &valueTable[i];
4481 if (matchedCommands > 0) {
4482 cliPrintLinefeed();
4484 cliPrintf("%s = ", valueTable[i].name);
4485 cliPrintVar(cmdName, val, 0);
4486 cliPrintLinefeed();
4487 switch (val->type & VALUE_SECTION_MASK) {
4488 case PROFILE_VALUE:
4489 cliProfile(cmdName, "");
4491 break;
4492 case PROFILE_RATE_VALUE:
4493 cliRateProfile(cmdName, "");
4495 break;
4496 default:
4498 break;
4500 cliPrintVarRange(val);
4501 cliPrintVarDefault(cmdName, val);
4503 matchedCommands++;
4507 restoreConfigs(0);
4509 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4510 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4512 if (!matchedCommands) {
4513 cliPrintErrorLinef(cmdName, "INVALID NAME");
4517 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
4519 while (*(bufEnd - 1) == ' ') {
4520 bufEnd--;
4523 return bufEnd - bufBegin;
4526 uint16_t cliGetSettingIndex(char *name, uint8_t length)
4528 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4529 const char *settingName = valueTable[i].name;
4531 // ensure exact match when setting to prevent setting variables with shorter names
4532 if (strncasecmp(name, settingName, strlen(settingName)) == 0 && length == strlen(settingName)) {
4533 return i;
4536 return valueTableEntryCount;
4539 STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
4541 const uint32_t len = strlen(cmdline);
4542 char *eqptr;
4544 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4545 cliPrintLine("Current settings: ");
4547 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4548 const clivalue_t *val = &valueTable[i];
4549 cliPrintf("%s = ", valueTable[i].name);
4550 cliPrintVar(cmdName, val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4551 cliPrintLinefeed();
4553 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4554 // has equals
4556 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4558 // skip the '=' and any ' ' characters
4559 eqptr++;
4560 eqptr = skipSpace(eqptr);
4562 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4563 if (index >= valueTableEntryCount) {
4564 cliPrintErrorLinef(cmdName, "INVALID NAME");
4565 return;
4567 const clivalue_t *val = &valueTable[index];
4569 bool valueChanged = false;
4570 int16_t value = 0;
4571 switch (val->type & VALUE_MODE_MASK) {
4572 case MODE_DIRECT: {
4573 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4574 uint32_t value = strtoul(eqptr, NULL, 10);
4576 if (value <= val->config.u32Max) {
4577 cliSetVar(val, value);
4578 valueChanged = true;
4580 } else {
4581 int value = atoi(eqptr);
4583 int min;
4584 int max;
4585 getMinMax(val, &min, &max);
4587 if (value >= min && value <= max) {
4588 cliSetVar(val, value);
4589 valueChanged = true;
4594 break;
4595 case MODE_LOOKUP:
4596 case MODE_BITSET: {
4597 int tableIndex;
4598 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4599 tableIndex = TABLE_OFF_ON;
4600 } else {
4601 tableIndex = val->config.lookup.tableIndex;
4603 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4604 bool matched = false;
4605 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4606 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4608 if (matched) {
4609 value = tableValueIndex;
4611 cliSetVar(val, value);
4612 valueChanged = true;
4617 break;
4619 case MODE_ARRAY: {
4620 const uint8_t arrayLength = val->config.array.length;
4621 char *valPtr = eqptr;
4623 int i = 0;
4624 while (i < arrayLength && valPtr != NULL) {
4625 // skip spaces
4626 valPtr = skipSpace(valPtr);
4628 // process substring starting at valPtr
4629 // note: no need to copy substrings for atoi()
4630 // it stops at the first character that cannot be converted...
4631 switch (val->type & VALUE_TYPE_MASK) {
4632 default:
4633 case VAR_UINT8:
4635 // fetch data pointer
4636 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4637 // store value
4638 *data = (uint8_t)atoi((const char*) valPtr);
4641 break;
4642 case VAR_INT8:
4644 // fetch data pointer
4645 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4646 // store value
4647 *data = (int8_t)atoi((const char*) valPtr);
4650 break;
4651 case VAR_UINT16:
4653 // fetch data pointer
4654 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4655 // store value
4656 *data = (uint16_t)atoi((const char*) valPtr);
4659 break;
4660 case VAR_INT16:
4662 // fetch data pointer
4663 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4664 // store value
4665 *data = (int16_t)atoi((const char*) valPtr);
4668 break;
4669 case VAR_UINT32:
4671 // fetch data pointer
4672 uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
4673 // store value
4674 *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
4677 break;
4680 // find next comma (or end of string)
4681 valPtr = strchr(valPtr, ',') + 1;
4683 i++;
4687 // mark as changed
4688 valueChanged = true;
4690 break;
4691 case MODE_STRING: {
4692 char *valPtr = eqptr;
4693 valPtr = skipSpace(valPtr);
4695 const unsigned int len = strlen(valPtr);
4696 const uint8_t min = val->config.string.minlength;
4697 const uint8_t max = val->config.string.maxlength;
4698 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4699 strlen((char *)cliGetValuePointer(val)) == 0 ||
4700 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4702 if (updatable && len > 0 && len <= max) {
4703 memset((char *)cliGetValuePointer(val), 0, max);
4704 if (len >= min && strncmp(valPtr, emptyName, len)) {
4705 strncpy((char *)cliGetValuePointer(val), valPtr, len);
4707 valueChanged = true;
4708 } else {
4709 cliPrintErrorLinef(cmdName, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4712 break;
4715 if (valueChanged) {
4716 cliPrintf("%s set to ", val->name);
4717 cliPrintVar(cmdName, val, 0);
4718 } else {
4719 cliPrintErrorLinef(cmdName, "INVALID VALUE");
4720 cliPrintVarRange(val);
4723 return;
4724 } else {
4725 // no equals, check for matching variables.
4726 cliGet(cmdName, cmdline);
4730 static const char *getMcuTypeById(mcuTypeId_e id)
4732 if (id < MCU_TYPE_UNKNOWN) {
4733 return mcuTypeNames[id];
4734 } else {
4735 return "UNKNOWN";
4739 static void cliStatus(const char *cmdName, char *cmdline)
4741 UNUSED(cmdName);
4742 UNUSED(cmdline);
4744 // MCU type, clock, vrefint, core temperature
4746 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock / 1000000));
4748 #if defined(STM32F4) || defined(STM32G4)
4749 // Only F4 and G4 is capable of switching between HSE/HSI (for now)
4750 int sysclkSource = SystemSYSCLKSource();
4752 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4753 const char *PLLSource[] = { "-HSI", "-HSE" };
4755 int pllSource;
4757 if (sysclkSource >= 2) {
4758 pllSource = SystemPLLSource();
4761 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4762 #endif
4764 #ifdef USE_ADC_INTERNAL
4765 uint16_t vrefintMv = getVrefMv();
4766 int16_t coretemp = getCoreTemperatureCelsius();
4767 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4768 #else
4769 cliPrintLinefeed();
4770 #endif
4772 // Stack and config sizes and usages
4774 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4775 #ifdef USE_STACK_CHECK
4776 cliPrintf(", Stack used: %d", stackUsedSize());
4777 #endif
4778 cliPrintLinefeed();
4780 cliPrintLinef("Configuration: %s, size: %d, max available: %d", configurationStates[systemConfigMutable()->configurationState], getEEPROMConfigSize(), getEEPROMStorageSize());
4782 // Devices
4783 #if defined(USE_SPI) || defined(USE_I2C)
4784 cliPrint("Devices detected:");
4785 #if defined(USE_SPI)
4786 cliPrintf(" SPI:%d", spiGetRegisteredDeviceCount());
4787 #if defined(USE_I2C)
4788 cliPrint(",");
4789 #endif
4790 #endif
4791 #if defined(USE_I2C)
4792 cliPrintf(" I2C:%d", i2cGetRegisteredDeviceCount());
4793 #endif
4794 cliPrintLinefeed();
4795 #endif
4797 // Sensors
4798 cliPrint("Gyros detected:");
4799 bool found = false;
4800 for (unsigned pos = 0; pos < 7; pos++) {
4801 if (gyroConfig()->gyrosDetected & BIT(pos)) {
4802 if (found) {
4803 cliPrint(",");
4804 } else {
4805 found = true;
4807 cliPrintf(" gyro %d", pos + 1);
4810 #ifdef USE_SPI
4811 #ifdef USE_GYRO_EXTI
4812 if (gyroActiveDev()->gyroModeSPI != GYRO_EXTI_NO_INT) {
4813 cliPrintf(" locked");
4815 if (gyroActiveDev()->gyroModeSPI == GYRO_EXTI_INT_DMA) {
4816 cliPrintf(" dma");
4818 #endif
4819 if (spiGetExtDeviceCount(&gyroActiveDev()->dev) > 1) {
4820 cliPrintf(" shared");
4822 #endif
4823 cliPrintLinefeed();
4825 #if defined(USE_SENSOR_NAMES)
4826 const uint32_t detectedSensorsMask = sensorsMask();
4827 for (uint32_t i = 0; ; i++) {
4828 if (sensorTypeNames[i] == NULL) {
4829 break;
4831 const uint32_t mask = (1 << i);
4832 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4833 const uint8_t sensorHardwareIndex = detectedSensors[i];
4834 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4835 if (i) {
4836 cliPrint(", ");
4838 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4839 #if defined(USE_ACC)
4840 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4841 cliPrintf(".%c", acc.dev.revisionCode);
4843 #endif
4846 cliPrintLinefeed();
4847 #endif /* USE_SENSOR_NAMES */
4849 #if defined(USE_OSD)
4850 osdDisplayPortDevice_e displayPortDeviceType;
4851 osdGetDisplayPort(&displayPortDeviceType);
4853 cliPrintLinef("OSD: %s", lookupTableOsdDisplayPortDevice[displayPortDeviceType]);
4854 #endif
4856 // Uptime and wall clock
4858 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4860 #ifdef USE_RTC_TIME
4861 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4862 dateTime_t dt;
4863 if (rtcGetDateTime(&dt)) {
4864 dateTimeFormatLocal(buf, &dt);
4865 cliPrintf(", Current Time: %s", buf);
4867 #endif
4868 cliPrintLinefeed();
4870 // Run status
4872 const int gyroRate = getTaskDeltaTimeUs(TASK_GYRO) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_GYRO)));
4873 int rxRate = getCurrentRxRefreshRate();
4874 if (rxRate != 0) {
4875 rxRate = (int)(1000000.0f / ((float)rxRate));
4877 const int systemRate = getTaskDeltaTimeUs(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_SYSTEM)));
4878 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4879 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE), getTaskDeltaTimeUs(TASK_GYRO), gyroRate, rxRate, systemRate);
4881 // Battery meter
4883 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4885 // Other devices and status
4887 #ifdef USE_I2C
4888 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4889 #else
4890 const uint16_t i2cErrorCounter = 0;
4891 #endif
4892 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4894 #ifdef USE_SDCARD
4895 cliSdInfo(cmdName, "");
4896 #endif
4898 cliPrint("Arming disable flags:");
4899 armingDisableFlags_e flags = getArmingDisableFlags();
4900 while (flags) {
4901 const int bitpos = ffs(flags) - 1;
4902 flags &= ~(1 << bitpos);
4903 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4905 cliPrintLinefeed();
4908 static void cliTasks(const char *cmdName, char *cmdline)
4910 UNUSED(cmdName);
4911 UNUSED(cmdline);
4912 int averageLoadSum = 0;
4914 #ifndef MINIMAL_CLI
4915 if (systemConfig()->task_statistics) {
4916 #if defined(USE_LATE_TASK_STATISTICS)
4917 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms late run reqd/us");
4918 #else
4919 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4920 #endif
4921 } else {
4922 cliPrintLine("Task list");
4924 #endif
4925 for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4926 taskInfo_t taskInfo;
4927 getTaskInfo(taskId, &taskInfo);
4928 if (taskInfo.isEnabled) {
4929 int taskFrequency = taskInfo.averageDeltaTime10thUs == 0 ? 0 : lrintf(1e7f / taskInfo.averageDeltaTime10thUs);
4930 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4931 const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 : (taskInfo.maxExecutionTimeUs * taskFrequency) / 1000;
4932 const int averageLoad = taskInfo.averageExecutionTimeUs == 0 ? 0 : (taskInfo.averageExecutionTimeUs * taskFrequency) / 1000;
4933 if (taskId != TASK_SERIAL) {
4934 averageLoadSum += averageLoad;
4936 if (systemConfig()->task_statistics) {
4937 #if defined(USE_LATE_TASK_STATISTICS)
4938 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d %6d %6d %7d",
4939 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTimeUs,
4940 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4941 taskInfo.totalExecutionTimeUs / 1000,
4942 taskInfo.lateCount, taskInfo.runCount, taskInfo.execTime);
4943 #else
4944 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4945 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTimeUs,
4946 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4947 taskInfo.totalExecutionTimeUs / 1000);
4948 #endif
4949 } else {
4950 cliPrintLinef("%6d", taskFrequency);
4953 schedulerResetTaskMaxExecutionTime(taskId);
4956 if (systemConfig()->task_statistics) {
4957 cfCheckFuncInfo_t checkFuncInfo;
4958 getCheckFuncInfo(&checkFuncInfo);
4959 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTimeUs, checkFuncInfo.averageExecutionTimeUs, checkFuncInfo.totalExecutionTimeUs / 1000);
4960 cliPrintLinef("Total (excluding SERIAL) %33d.%1d%%", averageLoadSum/10, averageLoadSum%10);
4961 if (debugMode == DEBUG_SCHEDULER_DETERMINISM) {
4962 extern int32_t schedLoopStartCycles, taskGuardCycles;
4964 cliPrintLinef("Scheduler start cycles %d guard cycles %d", schedLoopStartCycles, taskGuardCycles);
4966 schedulerResetCheckFunctionMaxExecutionTime();
4970 static void printVersion(const char *cmdName, bool printBoardInfo)
4972 #if !(defined(USE_CUSTOM_DEFAULTS) && defined(USE_UNIFIED_TARGET))
4973 UNUSED(cmdName);
4974 UNUSED(printBoardInfo);
4975 #endif
4977 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4978 FC_FIRMWARE_NAME,
4979 targetName,
4980 systemConfig()->boardIdentifier,
4981 FC_VERSION_STRING,
4982 buildDate,
4983 buildTime,
4984 shortGitRevision,
4985 MSP_API_VERSION_STRING
4988 #ifdef FEATURE_CUT_LEVEL
4989 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL);
4990 #else
4991 cliPrintLinefeed();
4992 #endif
4994 #if defined(USE_CUSTOM_DEFAULTS)
4995 if (hasCustomDefaults()) {
4996 if (strlen(customDefaultsManufacturerId) || strlen(customDefaultsBoardName) || strlen(customDefaultsChangesetId) || strlen(customDefaultsDate)) {
4997 cliPrintLinef("%s%s%s%s%s%s%s%s",
4998 CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX, customDefaultsManufacturerId,
4999 CUSTOM_DEFAULTS_BOARD_NAME_PREFIX, customDefaultsBoardName,
5000 CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX, customDefaultsChangesetId,
5001 CUSTOM_DEFAULTS_DATE_PREFIX, customDefaultsDate
5003 } else {
5004 cliPrintHashLine("config: YES");
5006 } else {
5007 #if defined(USE_UNIFIED_TARGET)
5008 cliPrintError(cmdName, "NO CONFIG FOUND");
5009 #else
5010 cliPrintHashLine("NO CUSTOM DEFAULTS FOUND");
5011 #endif // USE_UNIFIED_TARGET
5013 #endif // USE_CUSTOM_DEFAULTS
5015 #if defined(USE_UNIFIED_TARGET) && defined(USE_BOARD_INFO)
5016 if (printBoardInfo && strlen(getManufacturerId()) && strlen(getBoardName())) {
5017 cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
5019 #endif
5022 static void cliVersion(const char *cmdName, char *cmdline)
5024 UNUSED(cmdline);
5026 printVersion(cmdName, true);
5029 #ifdef USE_RC_SMOOTHING_FILTER
5030 static void cliRcSmoothing(const char *cmdName, char *cmdline)
5032 UNUSED(cmdName);
5033 UNUSED(cmdline);
5034 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
5035 cliPrint("# RC Smoothing Type: ");
5036 if (rxConfig()->rc_smoothing_mode) {
5037 cliPrintLine("FILTER");
5038 if (rcSmoothingAutoCalculate()) {
5039 const uint16_t avgRxFrameUs = rcSmoothingData->averageFrameTimeUs;
5040 cliPrint("# Detected RX frame rate: ");
5041 if (avgRxFrameUs == 0) {
5042 cliPrintLine("NO SIGNAL");
5043 } else {
5044 cliPrintLinef("%d.%03dms", avgRxFrameUs / 1000, avgRxFrameUs % 1000);
5047 cliPrintf("# Active setpoint cutoff: %dhz ", rcSmoothingData->setpointCutoffFrequency);
5048 if (rcSmoothingData->setpointCutoffSetting) {
5049 cliPrintLine("(manual)");
5050 } else {
5051 cliPrintLine("(auto)");
5053 cliPrintf("# Active FF cutoff: %dhz ", rcSmoothingData->feedforwardCutoffFrequency);
5054 if (rcSmoothingData->ffCutoffSetting) {
5055 cliPrintLine("(manual)");
5056 } else {
5057 cliPrintLine("(auto)");
5059 cliPrintf("# Active throttle cutoff: %dhz ", rcSmoothingData->throttleCutoffFrequency);
5060 if (rcSmoothingData->throttleCutoffSetting) {
5061 cliPrintLine("(manual)");
5062 } else {
5063 cliPrintLine("(auto)");
5065 } else {
5066 cliPrintLine("OFF");
5069 #endif // USE_RC_SMOOTHING_FILTER
5071 #if defined(USE_RESOURCE_MGMT)
5073 #define RESOURCE_VALUE_MAX_INDEX(x) ((x) == 0 ? 1 : (x))
5075 typedef struct {
5076 const uint8_t owner;
5077 pgn_t pgn;
5078 uint8_t stride;
5079 uint8_t offset;
5080 const uint8_t maxIndex;
5081 } cliResourceValue_t;
5083 // Handy macros for keeping the table tidy.
5084 // DEFS : Single entry
5085 // DEFA : Array of uint8_t (stride = 1)
5086 // DEFW : Wider stride case; array of structs.
5088 #define DEFS(owner, pgn, type, member) \
5089 { owner, pgn, 0, offsetof(type, member), 0 }
5091 #define DEFA(owner, pgn, type, member, max) \
5092 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
5094 #define DEFW(owner, pgn, type, member, max) \
5095 { owner, pgn, sizeof(type), offsetof(type, member), max }
5097 const cliResourceValue_t resourceTable[] = {
5098 #ifdef USE_BEEPER
5099 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
5100 #endif
5101 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
5102 #ifdef USE_SERVOS
5103 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
5104 #endif
5105 #if defined(USE_PPM)
5106 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
5107 #endif
5108 #if defined(USE_PWM)
5109 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
5110 #endif
5111 #ifdef USE_RANGEFINDER_HCSR04
5112 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
5113 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
5114 #endif
5115 #ifdef USE_LED_STRIP
5116 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
5117 #endif
5118 #ifdef USE_UART
5119 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
5120 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
5121 #endif
5122 #ifdef USE_INVERTER
5123 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
5124 #endif
5125 #ifdef USE_I2C
5126 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
5127 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
5128 #endif
5129 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
5130 #ifdef USE_SPEKTRUM_BIND
5131 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
5132 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
5133 #endif
5134 #ifdef USE_TRANSPONDER
5135 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
5136 #endif
5137 #ifdef USE_SPI
5138 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
5139 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
5140 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
5141 #endif
5142 #ifdef USE_ESCSERIAL
5143 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
5144 #endif
5145 #ifdef USE_CAMERA_CONTROL
5146 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
5147 #endif
5148 #ifdef USE_ADC
5149 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
5150 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
5151 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
5152 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
5153 #endif
5154 #ifdef USE_BARO
5155 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
5156 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
5157 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
5158 #endif
5159 #ifdef USE_MAG
5160 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
5161 #ifdef USE_MAG_DATA_READY_SIGNAL
5162 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
5163 #endif
5164 #endif
5165 #ifdef USE_SDCARD_SPI
5166 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
5167 #endif
5168 #ifdef USE_SDCARD
5169 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
5170 #endif
5171 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
5172 DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
5173 DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
5174 DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
5175 DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
5176 DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
5177 DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
5178 #endif
5179 #ifdef USE_PINIO
5180 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
5181 #endif
5182 #if defined(USE_USB_MSC)
5183 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
5184 #endif
5185 #ifdef USE_FLASH_CHIP
5186 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
5187 #endif
5188 #ifdef USE_MAX7456
5189 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
5190 #endif
5191 #ifdef USE_RX_SPI
5192 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
5193 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
5194 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
5195 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
5196 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
5197 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
5198 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
5199 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
5200 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
5201 #endif
5202 #endif
5203 #if defined(USE_RX_EXPRESSLRS)
5204 DEFS( OWNER_RX_SPI_EXPRESSLRS_RESET, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, resetIoTag ),
5205 DEFS( OWNER_RX_SPI_EXPRESSLRS_BUSY, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, busyIoTag ),
5206 #endif
5207 #endif
5208 #ifdef USE_GYRO_EXTI
5209 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
5210 #endif
5211 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
5212 #ifdef USE_USB_DETECT
5213 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
5214 #endif
5215 #ifdef USE_VTX_RTC6705
5216 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
5217 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
5218 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
5219 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
5220 #endif
5221 #ifdef USE_PIN_PULL_UP_DOWN
5222 DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5223 DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5224 #endif
5227 #undef DEFS
5228 #undef DEFA
5229 #undef DEFW
5231 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
5233 const pgRegistry_t* rec = pgFind(value.pgn);
5234 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
5237 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
5239 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5240 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
5241 const char* owner = ownerNames[resourceTable[i].owner];
5242 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
5243 const void *currentConfig;
5244 const void *defaultConfig;
5245 if (isReadingConfigFromCopy()) {
5246 currentConfig = pg->copy;
5247 defaultConfig = pg->address;
5248 } else {
5249 currentConfig = pg->address;
5250 defaultConfig = NULL;
5253 for (int index = 0; index < RESOURCE_VALUE_MAX_INDEX(resourceTable[i].maxIndex); index++) {
5254 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5255 ioTag_t ioTagDefault = NULL;
5256 if (defaultConfig) {
5257 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5260 const bool equalsDefault = ioTag == ioTagDefault;
5261 const char *format = "resource %s %d %c%02d";
5262 const char *formatUnassigned = "resource %s %d NONE";
5263 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5264 if (ioTagDefault) {
5265 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
5266 } else if (defaultConfig) {
5267 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5269 if (ioTag) {
5270 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
5271 } else if (!(dumpMask & HIDE_UNUSED)) {
5272 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5278 static void printResourceOwner(uint8_t owner, uint8_t index)
5280 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
5282 if (resourceTable[owner].maxIndex > 0) {
5283 cliPrintf(" %d", RESOURCE_INDEX(index));
5287 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
5289 if (!newTag) {
5290 return;
5293 const char * format = "\r\nNOTE: %c%02d already assigned to ";
5294 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
5295 for (int i = 0; i < RESOURCE_VALUE_MAX_INDEX(resourceTable[r].maxIndex); i++) {
5296 ioTag_t *tag = getIoTag(resourceTable[r], i);
5297 if (*tag == newTag) {
5298 bool cleared = false;
5299 if (r == resourceIndex) {
5300 if (i == index) {
5301 continue;
5303 *tag = IO_TAG_NONE;
5304 cleared = true;
5307 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
5309 printResourceOwner(r, i);
5311 if (cleared) {
5312 cliPrintf(". ");
5313 printResourceOwner(r, i);
5314 cliPrintf(" disabled");
5317 cliPrintLine(".");
5323 static bool strToPin(char *ptr, ioTag_t *tag)
5325 if (strcasecmp(ptr, "NONE") == 0) {
5326 *tag = IO_TAG_NONE;
5328 return true;
5329 } else {
5330 const unsigned port = (*ptr >= 'a') ? *ptr - 'a' : *ptr - 'A';
5331 if (port < 8) {
5332 ptr++;
5334 char *end;
5335 const long pin = strtol(ptr, &end, 10);
5336 if (end != ptr && pin >= 0 && pin < 16) {
5337 *tag = DEFIO_TAG_MAKE(port, pin);
5339 return true;
5344 return false;
5347 #ifdef USE_DMA
5348 static void showDma(void)
5350 cliPrintLinefeed();
5352 #ifdef MINIMAL_CLI
5353 cliPrintLine("DMA:");
5354 #else
5355 cliPrintLine("Currently active DMA:");
5356 cliRepeat('-', 20);
5357 #endif
5358 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
5359 const resourceOwner_t *owner = dmaGetOwner(i);
5361 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
5362 if (owner->resourceIndex > 0) {
5363 cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
5364 } else {
5365 cliPrintLinef(" %s", ownerNames[owner->owner]);
5369 #endif
5371 #ifdef USE_DMA_SPEC
5373 typedef struct dmaoptEntry_s {
5374 char *device;
5375 dmaPeripheral_e peripheral;
5376 pgn_t pgn;
5377 uint8_t stride;
5378 uint8_t offset;
5379 uint8_t maxIndex;
5380 uint32_t presenceMask;
5381 } dmaoptEntry_t;
5383 #define MASK_IGNORED (0)
5385 // Handy macros for keeping the table tidy.
5386 // DEFS : Single entry
5387 // DEFA : Array of uint8_t (stride = 1)
5388 // DEFW : Wider stride case; array of structs.
5390 #define DEFS(device, peripheral, pgn, type, member) \
5391 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5393 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5394 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5396 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5397 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
5399 dmaoptEntry_t dmaoptEntryTable[] = {
5400 DEFW("SPI_MOSI", DMA_PERIPH_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5401 DEFW("SPI_MISO", DMA_PERIPH_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5402 // SPI_TX/SPI_RX for backwards compatibility with unified configs defined for 4.2.x
5403 DEFW("SPI_TX", DMA_PERIPH_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5404 DEFW("SPI_RX", DMA_PERIPH_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5405 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
5406 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
5407 DEFW("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5408 DEFW("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5409 #if defined(STM32H7) || defined(STM32G4)
5410 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
5411 #endif
5414 #undef DEFS
5415 #undef DEFA
5416 #undef DEFW
5418 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5419 #define DMA_OPT_STRING_BUFSIZE 5
5421 #if defined(STM32H7) || defined(STM32G4)
5422 #define DMA_CHANREQ_STRING "Request"
5423 #else
5424 #define DMA_CHANREQ_STRING "Channel"
5425 #endif
5427 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
5428 #define DMA_STCH_STRING "Stream"
5429 #else
5430 #define DMA_STCH_STRING "Channel"
5431 #endif
5433 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5435 static void optToString(int optval, char *buf)
5437 if (optval == DMA_OPT_UNUSED) {
5438 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
5439 } else {
5440 tfp_sprintf(buf, "%d", optval);
5444 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5446 // We compute number to display for different peripherals in advance.
5447 // This is done to deal with TIMUP which numbered non-contiguously.
5448 // Note that using timerGetNumberByIndex is not a generic solution,
5449 // but we are lucky that TIMUP is the only peripheral with non-contiguous numbering.
5451 int uiIndex;
5453 if (entry->presenceMask) {
5454 uiIndex = timerGetNumberByIndex(index);
5455 } else {
5456 uiIndex = DMA_OPT_UI_INDEX(index);
5459 if (dmaopt != DMA_OPT_UNUSED) {
5460 printValue(dumpMask, equalsDefault,
5461 "dma %s %d %d",
5462 entry->device, uiIndex, dmaopt);
5464 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5465 dmaCode_t dmaCode = 0;
5466 if (dmaChannelSpec) {
5467 dmaCode = dmaChannelSpec->code;
5469 printValue(dumpMask, equalsDefault,
5470 "# %s %d: " DMASPEC_FORMAT_STRING,
5471 entry->device, uiIndex, DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5472 } else if (!(dumpMask & HIDE_UNUSED)) {
5473 printValue(dumpMask, equalsDefault,
5474 "dma %s %d NONE",
5475 entry->device, uiIndex);
5479 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5481 const pgRegistry_t* pg = pgFind(entry->pgn);
5482 const void *currentConfig;
5483 const void *defaultConfig;
5485 if (isReadingConfigFromCopy()) {
5486 currentConfig = pg->copy;
5487 defaultConfig = pg->address;
5488 } else {
5489 currentConfig = pg->address;
5490 defaultConfig = NULL;
5493 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5494 dmaoptValue_t defaultOpt;
5496 if (defaultConfig) {
5497 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5498 } else {
5499 defaultOpt = DMA_OPT_UNUSED;
5502 bool equalsDefault = currentOpt == defaultOpt;
5503 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5505 if (defaultConfig) {
5506 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5509 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5510 return headingStr;
5513 #if defined(USE_TIMER_MGMT)
5514 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5516 const char *format = "dma pin %c%02d %d";
5518 if (dmaopt != DMA_OPT_UNUSED) {
5519 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5520 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5521 dmaopt
5524 if (printDetails) {
5525 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5526 dmaCode_t dmaCode = 0;
5527 if (dmaChannelSpec) {
5528 dmaCode = dmaChannelSpec->code;
5529 printValue(dumpMask, false,
5530 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5531 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5532 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5536 } else if (!(dumpMask & HIDE_UNUSED)) {
5537 printValue(dumpMask, equalsDefault,
5538 "dma pin %c%02d NONE",
5539 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5544 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5546 const ioTag_t ioTag = currentConfig[index].ioTag;
5548 if (!ioTag) {
5549 return headingStr;
5552 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5553 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5555 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5556 bool equalsDefault = defaultDmaopt == dmaopt;
5557 if (defaultConfig) {
5558 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5559 if (defaultConfig[i].ioTag == ioTag) {
5560 defaultDmaopt = defaultConfig[i].dmaopt;
5562 // 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.
5563 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5565 tagsInUse[index] = true;
5567 break;
5572 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5574 if (defaultConfig) {
5575 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5578 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5579 return headingStr;
5581 #endif
5583 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5585 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5586 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5587 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5588 for (int index = 0; index < entry->maxIndex; index++) {
5589 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5593 #if defined(USE_TIMER_MGMT)
5594 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5595 const timerIOConfig_t *currentConfig;
5596 const timerIOConfig_t *defaultConfig;
5598 if (isReadingConfigFromCopy()) {
5599 currentConfig = (timerIOConfig_t *)pg->copy;
5600 defaultConfig = (timerIOConfig_t *)pg->address;
5601 } else {
5602 currentConfig = (timerIOConfig_t *)pg->address;
5603 defaultConfig = NULL;
5606 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5607 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5608 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5611 if (defaultConfig) {
5612 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5613 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5614 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5615 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5616 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5618 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5622 #endif
5625 static void cliDmaopt(const char *cmdName, char *cmdline)
5627 char *pch = NULL;
5628 char *saveptr;
5630 // Peripheral name or command option
5631 pch = strtok_r(cmdline, " ", &saveptr);
5632 if (!pch) {
5633 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5635 return;
5636 } else if (strcasecmp(pch, "list") == 0) {
5637 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5639 return;
5642 dmaoptEntry_t *entry = NULL;
5643 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5644 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5645 entry = &dmaoptEntryTable[i];
5649 if (!entry && strcasecmp(pch, "pin") != 0) {
5650 cliPrintErrorLinef(cmdName, "BAD DEVICE: %s", pch);
5651 return;
5654 // Index
5655 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5657 int index = 0;
5658 dmaoptValue_t *optaddr = NULL;
5660 ioTag_t ioTag = IO_TAG_NONE;
5661 #if defined(USE_TIMER_MGMT)
5662 timerIOConfig_t *timerIoConfig = NULL;
5663 #endif
5664 const timerHardware_t *timer = NULL;
5665 pch = strtok_r(NULL, " ", &saveptr);
5666 if (entry) {
5667 index = atoi(pch) - 1;
5668 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5669 cliPrintErrorLinef(cmdName, "BAD INDEX: '%s'", pch ? pch : "");
5670 return;
5673 const pgRegistry_t* pg = pgFind(entry->pgn);
5674 const void *currentConfig;
5675 if (isWritingConfigToCopy()) {
5676 currentConfig = pg->copy;
5677 } else {
5678 currentConfig = pg->address;
5680 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5681 orgval = *optaddr;
5682 } else {
5683 // It's a pin
5684 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5685 cliPrintErrorLinef(cmdName, "INVALID PIN: '%s'", pch ? pch : "");
5687 return;
5690 orgval = dmaoptByTag(ioTag);
5691 #if defined(USE_TIMER_MGMT)
5692 timerIoConfig = timerIoConfigByTag(ioTag);
5693 #endif
5694 timer = timerGetConfiguredByTag(ioTag);
5697 // opt or list
5698 pch = strtok_r(NULL, " ", &saveptr);
5699 if (!pch) {
5700 if (entry) {
5701 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5703 #if defined(USE_TIMER_MGMT)
5704 else {
5705 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5707 #endif
5709 return;
5710 } else if (strcasecmp(pch, "list") == 0) {
5711 // Show possible opts
5712 const dmaChannelSpec_t *dmaChannelSpec;
5713 if (entry) {
5714 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5715 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5717 } else {
5718 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5719 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5723 return;
5724 } else if (pch) {
5725 int optval;
5726 if (strcasecmp(pch, "none") == 0) {
5727 optval = DMA_OPT_UNUSED;
5728 } else {
5729 optval = atoi(pch);
5731 if (entry) {
5732 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5733 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5735 return;
5737 } else {
5738 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5739 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5741 return;
5746 char optvalString[DMA_OPT_STRING_BUFSIZE];
5747 optToString(optval, optvalString);
5749 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5750 optToString(orgval, orgvalString);
5752 if (optval != orgval) {
5753 if (entry) {
5754 *optaddr = optval;
5756 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5757 } else {
5758 #if defined(USE_TIMER_MGMT)
5759 timerIoConfig->dmaopt = optval;
5760 #endif
5762 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5764 } else {
5765 if (entry) {
5766 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5767 } else {
5768 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5773 #endif // USE_DMA_SPEC
5775 #ifdef USE_DMA
5776 static void cliDma(const char *cmdName, char* cmdline)
5778 int len = strlen(cmdline);
5779 if (len && strncasecmp(cmdline, "show", len) == 0) {
5780 showDma();
5782 return;
5785 #if defined(USE_DMA_SPEC)
5786 cliDmaopt(cmdName, cmdline);
5787 #else
5788 cliShowParseError(cmdName);
5789 #endif
5791 #endif
5792 #endif // USE_RESOURCE_MGMT
5794 #ifdef USE_TIMER_MGMT
5795 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5797 const char *format = "timer %c%02d AF%d";
5798 const char *emptyFormat = "timer %c%02d NONE";
5800 if (timerIndex > 0) {
5801 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5802 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5803 IO_GPIOPortIdxByTag(ioTag) + 'A',
5804 IO_GPIOPinIdxByTag(ioTag),
5805 timer->alternateFunction
5807 if (printDetails) {
5808 printValue(dumpMask, false,
5809 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5810 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5811 timerGetTIMNumber(timer->tim),
5812 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5813 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5814 timer->alternateFunction
5817 } else {
5818 printValue(dumpMask, equalsDefault, emptyFormat,
5819 IO_GPIOPortIdxByTag(ioTag) + 'A',
5820 IO_GPIOPinIdxByTag(ioTag)
5825 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5827 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5828 const timerIOConfig_t *currentConfig;
5829 const timerIOConfig_t *defaultConfig;
5831 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5832 if (isReadingConfigFromCopy()) {
5833 currentConfig = (timerIOConfig_t *)pg->copy;
5834 defaultConfig = (timerIOConfig_t *)pg->address;
5835 } else {
5836 currentConfig = (timerIOConfig_t *)pg->address;
5837 defaultConfig = NULL;
5840 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5841 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5842 const ioTag_t ioTag = currentConfig[i].ioTag;
5844 if (!ioTag) {
5845 continue;
5848 const uint8_t timerIndex = currentConfig[i].index;
5850 uint8_t defaultTimerIndex = 0;
5851 if (defaultConfig) {
5852 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5853 if (defaultConfig[i].ioTag == ioTag) {
5854 defaultTimerIndex = defaultConfig[i].index;
5855 tagsInUse[i] = true;
5857 break;
5862 const bool equalsDefault = defaultTimerIndex == timerIndex;
5863 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5864 if (defaultConfig && defaultTimerIndex) {
5865 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5868 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5871 if (defaultConfig) {
5872 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5873 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5874 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5875 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5877 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5883 #define TIMER_INDEX_UNDEFINED -1
5884 #define TIMER_AF_STRING_BUFSIZE 5
5886 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5888 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5889 if (!timer) {
5890 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5891 } else {
5892 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5896 static void showTimers(void)
5898 cliPrintLinefeed();
5900 #ifdef MINIMAL_CLI
5901 cliPrintLine("Timers:");
5902 #else
5903 cliPrintLine("Currently active Timers:");
5904 cliRepeat('-', 23);
5905 #endif
5907 int8_t timerNumber;
5908 for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
5909 cliPrintf("TIM%d:", timerNumber);
5910 bool timerUsed = false;
5911 for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
5912 const timerHardware_t *timer = timerGetAllocatedByNumberAndChannel(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
5913 const resourceOwner_t *timerOwner = timerGetOwner(timer);
5914 if (timerOwner->owner) {
5915 if (!timerUsed) {
5916 timerUsed = true;
5918 cliPrintLinefeed();
5921 if (timerOwner->resourceIndex > 0) {
5922 cliPrintLinef(" CH%d%s: %s %d", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner], timerOwner->resourceIndex);
5923 } else {
5924 cliPrintLinef(" CH%d%s: %s", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner]);
5929 if (!timerUsed) {
5930 cliPrintLine(" FREE");
5935 static void cliTimer(const char *cmdName, char *cmdline)
5937 int len = strlen(cmdline);
5939 if (len == 0) {
5940 printTimer(DUMP_MASTER, NULL);
5942 return;
5943 } else if (strncasecmp(cmdline, "list", len) == 0) {
5944 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5946 return;
5947 } else if (strncasecmp(cmdline, "show", len) == 0) {
5948 showTimers();
5950 return;
5953 char *pch = NULL;
5954 char *saveptr;
5956 ioTag_t ioTag = IO_TAG_NONE;
5957 pch = strtok_r(cmdline, " ", &saveptr);
5958 if (!pch || !strToPin(pch, &ioTag)) {
5959 cliShowParseError(cmdName);
5961 return;
5962 } else if (!IOGetByTag(ioTag)) {
5963 cliPrintErrorLinef(cmdName, "PIN NOT USED ON BOARD.");
5965 return;
5968 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5969 bool isExistingTimerOpt = false;
5970 /* find existing entry, or go for next available */
5971 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5972 if (timerIOConfig(i)->ioTag == ioTag) {
5973 timerIOIndex = i;
5974 isExistingTimerOpt = true;
5976 break;
5979 /* first available empty slot */
5980 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5981 timerIOIndex = i;
5985 if (timerIOIndex < 0) {
5986 cliPrintErrorLinef(cmdName, "PIN TIMER MAP FULL.");
5988 return;
5991 pch = strtok_r(NULL, " ", &saveptr);
5992 if (pch) {
5993 int timerIndex = TIMER_INDEX_UNDEFINED;
5994 if (strcasecmp(pch, "list") == 0) {
5995 /* output the list of available options */
5996 const timerHardware_t *timer;
5997 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5998 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5999 timer->alternateFunction,
6000 timerGetTIMNumber(timer->tim),
6001 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
6002 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
6006 return;
6007 } else if (strncasecmp(pch, "af", 2) == 0) {
6008 unsigned alternateFunction = atoi(&pch[2]);
6010 const timerHardware_t *timer;
6011 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
6012 if (timer->alternateFunction == alternateFunction) {
6013 timerIndex = index;
6015 break;
6019 if (!timer) {
6020 cliPrintErrorLinef(cmdName, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
6022 return;
6024 } else if (strcasecmp(pch, "none") != 0) {
6025 cliPrintErrorLinef(cmdName, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
6027 return;
6030 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
6031 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
6032 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
6033 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
6035 char optvalString[DMA_OPT_STRING_BUFSIZE];
6036 alternateFunctionToString(ioTag, timerIndex, optvalString);
6038 char orgvalString[DMA_OPT_STRING_BUFSIZE];
6039 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
6041 if (timerIndex == oldTimerIndex) {
6042 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
6043 } else {
6044 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
6047 return;
6048 } else {
6049 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
6051 return;
6054 #endif
6056 #if defined(USE_RESOURCE_MGMT)
6057 static void cliResource(const char *cmdName, char *cmdline)
6059 char *pch = NULL;
6060 char *saveptr;
6062 pch = strtok_r(cmdline, " ", &saveptr);
6063 if (!pch) {
6064 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
6066 return;
6067 } else if (strcasecmp(pch, "show") == 0) {
6068 #ifdef MINIMAL_CLI
6069 cliPrintLine("IO");
6070 #else
6071 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
6072 cliRepeat('-', 20);
6073 #endif
6074 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
6075 const char* owner;
6076 owner = ownerNames[ioRecs[i].owner];
6078 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
6079 if (ioRecs[i].index > 0) {
6080 cliPrintf(" %d", ioRecs[i].index);
6082 cliPrintLinefeed();
6085 pch = strtok_r(NULL, " ", &saveptr);
6086 if (strcasecmp(pch, "all") == 0) {
6087 #if defined(USE_TIMER_MGMT)
6088 cliTimer(cmdName, "show");
6089 #endif
6090 #if defined(USE_DMA)
6091 cliDma(cmdName, "show");
6092 #endif
6095 return;
6098 unsigned resourceIndex = 0;
6099 for (; ; resourceIndex++) {
6100 if (resourceIndex >= ARRAYLEN(resourceTable)) {
6101 cliPrintErrorLinef(cmdName, "INVALID RESOURCE NAME: '%s'", pch);
6102 return;
6105 const char *resourceName = ownerNames[resourceTable[resourceIndex].owner];
6106 if (strncasecmp(pch, resourceName, strlen(resourceName)) == 0) {
6107 break;
6111 pch = strtok_r(NULL, " ", &saveptr);
6112 int index = atoi(pch);
6114 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
6115 if (index <= 0 || index > RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex)) {
6116 cliShowArgumentRangeError(cmdName, "INDEX", 1, RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex));
6117 return;
6119 index -= 1;
6121 pch = strtok_r(NULL, " ", &saveptr);
6124 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
6126 if (strlen(pch) > 0) {
6127 if (strToPin(pch, tag)) {
6128 if (*tag == IO_TAG_NONE) {
6129 #ifdef MINIMAL_CLI
6130 cliPrintLine("Freed");
6131 #else
6132 cliPrintLine("Resource is freed");
6133 #endif
6134 return;
6135 } else {
6136 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
6137 if (rec) {
6138 resourceCheck(resourceIndex, index, *tag);
6139 #ifdef MINIMAL_CLI
6140 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6141 #else
6142 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6143 #endif
6144 } else {
6145 cliShowParseError(cmdName);
6147 return;
6152 cliShowParseError(cmdName);
6154 #endif
6156 #ifdef USE_DSHOT_TELEMETRY
6157 static void cliDshotTelemetryInfo(const char *cmdName, char *cmdline)
6159 UNUSED(cmdName);
6160 UNUSED(cmdline);
6162 if (useDshotTelemetry) {
6163 cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
6164 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
6165 uint32_t directionChangeCycles = dshotDMAHandlerCycleCounters.changeDirectionCompletedAt - dshotDMAHandlerCycleCounters.irqAt;
6166 uint32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
6167 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
6168 cliPrintLinefeed();
6170 #ifdef USE_DSHOT_TELEMETRY_STATS
6171 cliPrintLine("Motor eRPM RPM Hz Invalid");
6172 cliPrintLine("===== ======= ====== ===== =======");
6173 #else
6174 cliPrintLine("Motor eRPM RPM Hz");
6175 cliPrintLine("===== ======= ====== =====");
6176 #endif
6177 for (uint8_t i = 0; i < getMotorCount(); i++) {
6178 cliPrintf("%5d %7d %6d %5d ", i,
6179 (int)getDshotTelemetry(i) * 100,
6180 (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount,
6181 (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount / 60);
6182 #ifdef USE_DSHOT_TELEMETRY_STATS
6183 if (isDshotMotorTelemetryActive(i)) {
6184 const int calcPercent = getDshotTelemetryMotorInvalidPercent(i);
6185 cliPrintLinef("%3d.%02d%%", calcPercent / 100, calcPercent % 100);
6186 } else {
6187 cliPrintLine("NO DATA");
6189 #else
6190 cliPrintLinefeed();
6191 #endif
6193 cliPrintLinefeed();
6195 const int len = MAX_GCR_EDGES;
6196 #ifdef DEBUG_BBDECODE
6197 extern uint16_t bbBuffer[134];
6198 for (int i = 0; i < 134; i++) {
6199 cliPrintf("%u ", (int)bbBuffer[i]);
6201 cliPrintLinefeed();
6202 #endif
6203 for (int i = 0; i < len; i++) {
6204 cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
6206 cliPrintLinefeed();
6207 for (int i = 1; i < len; i++) {
6208 cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
6210 cliPrintLinefeed();
6211 } else {
6212 cliPrintLine("Dshot telemetry not enabled");
6215 #endif
6217 static void printConfig(const char *cmdName, char *cmdline, bool doDiff)
6219 dumpFlags_t dumpMask = DUMP_MASTER;
6220 char *options;
6221 if ((options = checkCommand(cmdline, "master"))) {
6222 dumpMask = DUMP_MASTER; // only
6223 } else if ((options = checkCommand(cmdline, "profile"))) {
6224 dumpMask = DUMP_PROFILE; // only
6225 } else if ((options = checkCommand(cmdline, "rates"))) {
6226 dumpMask = DUMP_RATES; // only
6227 } else if ((options = checkCommand(cmdline, "hardware"))) {
6228 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
6229 } else if ((options = checkCommand(cmdline, "all"))) {
6230 dumpMask = DUMP_ALL; // all profiles and rates
6231 } else {
6232 options = cmdline;
6235 if (doDiff) {
6236 dumpMask = dumpMask | DO_DIFF;
6239 if (checkCommand(options, "defaults")) {
6240 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
6241 } else if (checkCommand(options, "bare")) {
6242 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
6245 backupAndResetConfigs((dumpMask & BARE) == 0);
6247 #ifdef USE_CLI_BATCH
6248 bool batchModeEnabled = false;
6249 #endif
6250 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
6251 cliPrintHashLine("version");
6252 printVersion(cmdName, false);
6254 if (!(dumpMask & BARE)) {
6255 #ifdef USE_CLI_BATCH
6256 cliPrintHashLine("start the command batch");
6257 cliPrintLine("batch start");
6258 batchModeEnabled = true;
6259 #endif
6261 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
6262 cliPrintHashLine("reset configuration to default settings");
6263 cliPrintLine("defaults nosave");
6267 #if defined(USE_BOARD_INFO)
6268 cliPrintLinefeed();
6269 printBoardName(dumpMask);
6270 printManufacturerId(dumpMask);
6271 #endif
6273 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
6274 cliMcuId(cmdName, "");
6275 #if defined(USE_SIGNATURE)
6276 cliSignature(cmdName, "");
6277 #endif
6280 if (!(dumpMask & HARDWARE_ONLY)) {
6281 printName(dumpMask, &pilotConfig_Copy);
6284 #ifdef USE_RESOURCE_MGMT
6285 printResource(dumpMask, "resources");
6286 #if defined(USE_TIMER_MGMT)
6287 printTimer(dumpMask, "timer");
6288 #endif
6289 #ifdef USE_DMA_SPEC
6290 printDmaopt(dumpMask, "dma");
6291 #endif
6292 #endif
6294 printFeature(dumpMask, featureConfig_Copy.enabledFeatures, featureConfig()->enabledFeatures, "feature");
6296 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
6298 if (!(dumpMask & HARDWARE_ONLY)) {
6299 #ifndef USE_QUAD_MIXER_ONLY
6300 const char *mixerHeadingStr = "mixer";
6301 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
6302 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
6303 const char *formatMixer = "mixer %s";
6304 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
6305 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
6307 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
6309 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
6311 #ifdef USE_SERVOS
6312 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
6314 const char *servoMixHeadingStr = "servo mixer";
6315 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
6316 cliPrintHashLine(servoMixHeadingStr);
6317 cliPrintLine("smix reset\r\n");
6318 servoMixHeadingStr = NULL;
6320 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
6321 #endif
6322 #endif
6324 #if defined(USE_BEEPER)
6325 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
6327 #if defined(USE_DSHOT)
6328 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
6329 #endif
6330 #endif // USE_BEEPER
6332 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
6334 #ifdef USE_LED_STRIP_STATUS_MODE
6335 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
6337 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
6339 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
6340 #endif
6342 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
6344 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
6346 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
6348 #ifdef USE_VTX_TABLE
6349 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
6350 #endif
6352 #ifdef USE_VTX_CONTROL
6353 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
6354 #endif
6356 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
6359 if (dumpMask & HARDWARE_ONLY) {
6360 dumpAllValues(cmdName, HARDWARE_VALUE, dumpMask, "master");
6361 } else {
6362 dumpAllValues(cmdName, MASTER_VALUE, dumpMask, "master");
6364 if (dumpMask & DUMP_ALL) {
6365 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
6366 cliDumpPidProfile(cmdName, pidProfileIndex, dumpMask);
6369 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
6371 if (!(dumpMask & BARE)) {
6372 cliPrintHashLine("restore original profile selection");
6374 cliProfile(cmdName, "");
6377 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
6379 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
6380 cliDumpRateProfile(cmdName, rateIndex, dumpMask);
6383 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
6385 if (!(dumpMask & BARE)) {
6386 cliPrintHashLine("restore original rateprofile selection");
6388 cliRateProfile(cmdName, "");
6390 cliPrintHashLine("save configuration");
6391 cliPrint("save");
6392 #ifdef USE_CLI_BATCH
6393 batchModeEnabled = false;
6394 #endif
6397 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
6398 } else {
6399 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6401 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6404 } else if (dumpMask & DUMP_PROFILE) {
6405 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6406 } else if (dumpMask & DUMP_RATES) {
6407 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6410 #ifdef USE_CLI_BATCH
6411 if (batchModeEnabled) {
6412 cliPrintHashLine("end the command batch");
6413 cliPrintLine("batch end");
6415 #endif
6417 // restore configs from copies
6418 restoreConfigs(0);
6421 static void cliDump(const char *cmdName, char *cmdline)
6423 printConfig(cmdName, cmdline, false);
6426 static void cliDiff(const char *cmdName, char *cmdline)
6428 printConfig(cmdName, cmdline, true);
6431 #if defined(USE_USB_MSC)
6432 static void cliMsc(const char *cmdName, char *cmdline)
6434 if (mscCheckFilesystemReady()) {
6435 #ifdef USE_RTC_TIME
6436 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
6437 if (!isEmpty(cmdline)) {
6438 timezoneOffsetMinutes = atoi(cmdline);
6439 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
6440 cliPrintErrorLinef(cmdName, "INVALID TIMEZONE OFFSET");
6441 return;
6444 #else
6445 int timezoneOffsetMinutes = 0;
6446 UNUSED(cmdline);
6447 #endif
6448 cliPrintHashLine("Restarting in mass storage mode");
6449 cliPrint("\r\nRebooting");
6450 cliWriterFlush();
6451 waitForSerialPortToFinishTransmitting(cliPort);
6452 motorShutdown();
6454 systemResetToMsc(timezoneOffsetMinutes);
6455 } else {
6456 cliPrintHashLine("Storage not present or failed to initialize!");
6459 #endif
6461 typedef void cliCommandFn(const char* name, char *cmdline);
6463 typedef struct {
6464 const char *name;
6465 #ifndef MINIMAL_CLI
6466 const char *description;
6467 const char *args;
6468 #endif
6469 cliCommandFn *cliCommand;
6470 } clicmd_t;
6472 #ifndef MINIMAL_CLI
6473 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6475 name , \
6476 description , \
6477 args , \
6478 cliCommand \
6480 #else
6481 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6483 name, \
6484 cliCommand \
6486 #endif
6488 static void cliHelp(const char *cmdName, char *cmdline);
6490 // should be sorted a..z for bsearch()
6491 const clicmd_t cmdTable[] = {
6492 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
6493 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
6494 #ifdef USE_CLI_BATCH
6495 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
6496 #endif
6497 #if defined(USE_BEEPER)
6498 #if defined(USE_DSHOT)
6499 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6500 "\t<->[name]", cliBeacon),
6501 #endif
6502 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6503 "\t<->[name]", cliBeeper),
6504 #endif // USE_BEEPER
6505 #if defined(USE_RX_BIND)
6506 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
6507 #endif
6508 #if defined(USE_FLASH_BOOT_LOADER)
6509 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6510 #else
6511 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6512 #endif
6513 #if defined(USE_BOARD_INFO)
6514 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6515 #endif
6516 #ifdef USE_LED_STRIP_STATUS_MODE
6517 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6518 #endif
6519 #if defined(USE_CUSTOM_DEFAULTS)
6520 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{show} {nosave} {bare} {group_id <id>}", cliDefaults),
6521 #else
6522 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{nosave}", cliDefaults),
6523 #endif
6524 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6525 #ifdef USE_RESOURCE_MGMT
6527 #ifdef USE_DMA
6528 #ifdef USE_DMA_SPEC
6529 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6530 #else
6531 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6532 #endif
6533 #endif
6535 #endif
6536 #ifdef USE_DSHOT_TELEMETRY
6537 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6538 #endif
6539 #ifdef USE_DSHOT
6540 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6541 #endif
6542 CLI_COMMAND_DEF("dump", "dump configuration",
6543 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6544 #ifdef USE_ESCSERIAL
6545 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6546 #endif
6547 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
6548 CLI_COMMAND_DEF("feature", "configure features",
6549 "list\r\n"
6550 "\t<->[name]", cliFeature),
6551 #ifdef USE_FLASHFS
6552 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6553 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6554 #ifdef USE_FLASH_TOOLS
6555 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6556 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6557 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6558 #endif
6559 #endif
6560 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6561 #ifdef USE_GPS
6562 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6563 #endif
6564 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6565 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6566 #endif
6567 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp),
6568 #ifdef USE_LED_STRIP_STATUS_MODE
6569 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6570 #endif
6571 #if defined(USE_BOARD_INFO)
6572 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6573 #endif
6574 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6575 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6576 #ifndef USE_QUAD_MIXER_ONLY
6577 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6578 #endif
6579 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6580 #ifdef USE_LED_STRIP_STATUS_MODE
6581 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6582 #endif
6583 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6584 #ifdef USE_USB_MSC
6585 #ifdef USE_RTC_TIME
6586 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6587 #else
6588 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6589 #endif
6590 #endif
6591 #ifndef MINIMAL_CLI
6592 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6593 #endif
6594 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6595 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6596 #ifdef USE_RC_SMOOTHING_FILTER
6597 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6598 #endif // USE_RC_SMOOTHING_FILTER
6599 #ifdef USE_RESOURCE_MGMT
6600 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6601 #endif
6602 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6603 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6604 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
6605 #ifdef USE_SDCARD
6606 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6607 #endif
6608 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6609 #if defined(USE_SERIAL_PASSTHROUGH)
6610 #if defined(USE_PINIO)
6611 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),
6612 #else
6613 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6614 #endif
6615 #endif
6616 #ifdef USE_SERVOS
6617 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6618 #endif
6619 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6620 #if defined(USE_SIGNATURE)
6621 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6622 #endif
6623 #if defined(USE_SIMPLIFIED_TUNING)
6624 CLI_COMMAND_DEF("simplified_tuning", "applies or disables simplified tuning", "apply | disable", cliSimplifiedTuning),
6625 #endif
6626 #ifdef USE_SERVOS
6627 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6628 "\treset\r\n"
6629 "\tload <mixer>\r\n"
6630 "\treverse <servo> <source> r|n", cliServoMix),
6631 #endif
6632 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6633 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6634 #ifdef USE_TIMER_MGMT
6635 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6636 #endif
6637 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6638 #ifdef USE_VTX_CONTROL
6639 #ifdef MINIMAL_CLI
6640 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6641 #else
6642 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6643 #endif
6644 #endif
6645 #ifdef USE_VTX_TABLE
6646 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL, cliVtxInfo),
6647 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6648 #endif
6651 static void cliHelp(const char *cmdName, char *cmdline)
6653 bool anyMatches = false;
6655 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6656 bool printEntry = false;
6657 if (isEmpty(cmdline)) {
6658 printEntry = true;
6659 } else {
6660 if (strcasestr(cmdTable[i].name, cmdline)
6661 #ifndef MINIMAL_CLI
6662 || strcasestr(cmdTable[i].description, cmdline)
6663 #endif
6665 printEntry = true;
6669 if (printEntry) {
6670 anyMatches = true;
6671 cliPrint(cmdTable[i].name);
6672 #ifndef MINIMAL_CLI
6673 if (cmdTable[i].description) {
6674 cliPrintf(" - %s", cmdTable[i].description);
6676 if (cmdTable[i].args) {
6677 cliPrintf("\r\n\t%s", cmdTable[i].args);
6679 #endif
6680 cliPrintLinefeed();
6683 if (!isEmpty(cmdline) && !anyMatches) {
6684 cliPrintErrorLinef(cmdName, "NO MATCHES FOR '%s'", cmdline);
6688 static void processCharacter(const char c)
6690 if (bufferIndex && (c == '\n' || c == '\r')) {
6691 // enter pressed
6692 cliPrintLinefeed();
6694 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
6695 if (processingCustomDefaults) {
6696 cliPrint("d: ");
6698 #endif
6700 // Strip comment starting with # from line
6701 char *p = cliBuffer;
6702 p = strchr(p, '#');
6703 if (NULL != p) {
6704 bufferIndex = (uint32_t)(p - cliBuffer);
6707 // Strip trailing whitespace
6708 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6709 bufferIndex--;
6712 // Process non-empty lines
6713 if (bufferIndex > 0) {
6714 cliBuffer[bufferIndex] = 0; // null terminate
6716 const clicmd_t *cmd;
6717 char *options;
6718 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6719 if ((options = checkCommand(cliBuffer, cmd->name))) {
6720 break;
6723 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6724 cmd->cliCommand(cmd->name, options);
6725 } else {
6726 cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
6728 bufferIndex = 0;
6731 memset(cliBuffer, 0, sizeof(cliBuffer));
6733 // 'exit' will reset this flag, so we don't need to print prompt again
6734 if (!cliMode) {
6735 return;
6738 cliPrompt();
6739 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6740 if (!bufferIndex && c == ' ')
6741 return; // Ignore leading spaces
6742 cliBuffer[bufferIndex++] = c;
6743 cliWrite(c);
6747 static void processCharacterInteractive(const char c)
6749 if (c == '\t' || c == '?') {
6750 // do tab completion
6751 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6752 uint32_t i = bufferIndex;
6753 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6754 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) {
6755 continue;
6757 if (!pstart) {
6758 pstart = cmd;
6760 pend = cmd;
6762 if (pstart) { /* Buffer matches one or more commands */
6763 for (; ; bufferIndex++) {
6764 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6765 break;
6766 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6767 /* Unambiguous -- append a space */
6768 cliBuffer[bufferIndex++] = ' ';
6769 cliBuffer[bufferIndex] = '\0';
6770 break;
6772 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6775 if (!bufferIndex || pstart != pend) {
6776 /* Print list of ambiguous matches */
6777 cliPrint("\r\n\033[K");
6778 for (cmd = pstart; cmd <= pend; cmd++) {
6779 cliPrint(cmd->name);
6780 cliWrite('\t');
6782 cliPrompt();
6783 i = 0; /* Redraw prompt */
6785 for (; i < bufferIndex; i++)
6786 cliWrite(cliBuffer[i]);
6787 } else if (!bufferIndex && c == 4) { // CTRL-D
6788 cliExit("", cliBuffer);
6789 return;
6790 } else if (c == 12) { // NewPage / CTRL-L
6791 // clear screen
6792 cliPrint("\033[2J\033[1;1H");
6793 cliPrompt();
6794 } else if (c == 127) {
6795 // backspace
6796 if (bufferIndex) {
6797 cliBuffer[--bufferIndex] = 0;
6798 cliPrint("\010 \010");
6800 } else {
6801 processCharacter(c);
6805 void cliProcess(void)
6807 if (!cliWriter) {
6808 return;
6811 // Flush the buffer to get rid of any MSP data polls sent by configurator after CLI was invoked
6812 cliWriterFlush();
6814 while (serialRxBytesWaiting(cliPort)) {
6815 uint8_t c = serialRead(cliPort);
6817 processCharacterInteractive(c);
6821 #if defined(USE_CUSTOM_DEFAULTS)
6822 static bool cliProcessCustomDefaults(bool quiet)
6824 if (processingCustomDefaults || !hasCustomDefaults()) {
6825 return false;
6828 bufWriter_t *cliWriterTemp = NULL;
6829 if (quiet
6830 #if !defined(DEBUG_CUSTOM_DEFAULTS)
6831 || true
6832 #endif
6834 cliWriterTemp = cliWriter;
6835 cliWriter = NULL;
6837 if (quiet) {
6838 cliErrorWriter = NULL;
6841 memcpy(cliBufferTemp, cliBuffer, sizeof(cliBuffer));
6842 uint32_t bufferIndexTemp = bufferIndex;
6843 bufferIndex = 0;
6844 processingCustomDefaults = true;
6846 char *customDefaultsPtr = customDefaultsStart;
6847 while (customDefaultsHasNext(customDefaultsPtr)) {
6848 processCharacter(*customDefaultsPtr++);
6851 // Process a newline at the very end so that the last command gets executed,
6852 // even when the file did not contain a trailing newline
6853 processCharacter('\r');
6855 processingCustomDefaults = false;
6857 if (cliWriterTemp) {
6858 cliWriter = cliWriterTemp;
6859 cliErrorWriter = cliWriter;
6862 memcpy(cliBuffer, cliBufferTemp, sizeof(cliBuffer));
6863 bufferIndex = bufferIndexTemp;
6865 systemConfigMutable()->configurationState = CONFIGURATION_STATE_DEFAULTS_CUSTOM;
6867 return true;
6869 #endif
6871 void cliEnter(serialPort_t *serialPort)
6873 cliMode = true;
6874 cliPort = serialPort;
6875 setPrintfSerialPort(cliPort);
6876 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6877 cliErrorWriter = cliWriter;
6879 #ifndef MINIMAL_CLI
6880 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6881 #else
6882 cliPrintLine("\r\nCLI");
6883 #endif
6884 setArmingDisabled(ARMING_DISABLED_CLI);
6886 cliPrompt();
6888 #ifdef USE_CLI_BATCH
6889 resetCommandBatch();
6890 #endif
6893 #endif // USE_CLI