Merge pull request #10113 from knoopx/improve-custom-builds
[betaflight.git] / src / main / cli / cli.c
blob0614608715bd20565ff36f83eb9702dbe06fb829
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"
61 #include "drivers/accgyro/accgyro.h"
62 #include "drivers/adc.h"
63 #include "drivers/buf_writer.h"
64 #include "drivers/bus_spi.h"
65 #include "drivers/dma.h"
66 #include "drivers/dma_reqmap.h"
67 #include "drivers/dshot.h"
68 #include "drivers/dshot_command.h"
69 #include "drivers/dshot_dpwm.h"
70 #include "drivers/pwm_output_dshot_shared.h"
71 #include "drivers/camera_control.h"
72 #include "drivers/compass/compass.h"
73 #include "drivers/display.h"
74 #include "drivers/dma.h"
75 #include "drivers/flash.h"
76 #include "drivers/inverter.h"
77 #include "drivers/io.h"
78 #include "drivers/io_impl.h"
79 #include "drivers/light_led.h"
80 #include "drivers/motor.h"
81 #include "drivers/rangefinder/rangefinder_hcsr04.h"
82 #include "drivers/resource.h"
83 #include "drivers/sdcard.h"
84 #include "drivers/sensor.h"
85 #include "drivers/serial.h"
86 #include "drivers/serial_escserial.h"
87 #include "drivers/sound_beeper.h"
88 #include "drivers/stack_check.h"
89 #include "drivers/system.h"
90 #include "drivers/time.h"
91 #include "drivers/timer.h"
92 #include "drivers/transponder_ir.h"
93 #include "drivers/usb_msc.h"
94 #include "drivers/vtx_common.h"
95 #include "drivers/vtx_table.h"
97 #include "fc/board_info.h"
98 #include "fc/controlrate_profile.h"
99 #include "fc/core.h"
100 #include "fc/rc.h"
101 #include "fc/rc_adjustments.h"
102 #include "fc/rc_controls.h"
103 #include "fc/runtime_config.h"
105 #include "flight/failsafe.h"
106 #include "flight/imu.h"
107 #include "flight/mixer.h"
108 #include "flight/pid.h"
109 #include "flight/position.h"
110 #include "flight/servos.h"
112 #include "io/asyncfatfs/asyncfatfs.h"
113 #include "io/beeper.h"
114 #include "io/flashfs.h"
115 #include "io/gimbal.h"
116 #include "io/gps.h"
117 #include "io/ledstrip.h"
118 #include "io/serial.h"
119 #include "io/transponder_ir.h"
120 #include "io/usb_msc.h"
121 #include "io/vtx_control.h"
122 #include "io/vtx.h"
124 #include "msp/msp.h"
125 #include "msp/msp_box.h"
126 #include "msp/msp_protocol.h"
128 #include "osd/osd.h"
130 #include "pg/adc.h"
131 #include "pg/beeper.h"
132 #include "pg/beeper_dev.h"
133 #include "pg/board.h"
134 #include "pg/bus_i2c.h"
135 #include "pg/bus_spi.h"
136 #include "pg/gyrodev.h"
137 #include "pg/max7456.h"
138 #include "pg/mco.h"
139 #include "pg/motor.h"
140 #include "pg/pinio.h"
141 #include "pg/pin_pull_up_down.h"
142 #include "pg/pg.h"
143 #include "pg/pg_ids.h"
144 #include "pg/rx.h"
145 #include "pg/rx_pwm.h"
146 #include "pg/rx_spi_cc2500.h"
147 #include "pg/serial_uart.h"
148 #include "pg/sdio.h"
149 #include "pg/timerio.h"
150 #include "pg/timerup.h"
151 #include "pg/usb.h"
152 #include "pg/vtx_table.h"
154 #include "rx/rx_bind.h"
155 #include "rx/rx_spi.h"
157 #include "scheduler/scheduler.h"
159 #include "sensors/acceleration.h"
160 #include "sensors/adcinternal.h"
161 #include "sensors/barometer.h"
162 #include "sensors/battery.h"
163 #include "sensors/boardalignment.h"
164 #include "sensors/compass.h"
165 #include "sensors/esc_sensor.h"
166 #include "sensors/gyro.h"
167 #include "sensors/gyro_init.h"
168 #include "sensors/sensors.h"
170 #include "telemetry/frsky_hub.h"
171 #include "telemetry/telemetry.h"
173 #include "cli.h"
175 static serialPort_t *cliPort = NULL;
177 #ifdef STM32F1
178 #define CLI_IN_BUFFER_SIZE 128
179 #else
180 // Space required to set array parameters
181 #define CLI_IN_BUFFER_SIZE 256
182 #endif
183 #define CLI_OUT_BUFFER_SIZE 64
185 static bufWriter_t *cliWriter = NULL;
186 static bufWriter_t *cliErrorWriter = NULL;
187 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
189 static char cliBuffer[CLI_IN_BUFFER_SIZE];
190 static uint32_t bufferIndex = 0;
192 static bool configIsInCopy = false;
194 #define CURRENT_PROFILE_INDEX -1
195 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
196 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
198 #ifdef USE_CLI_BATCH
199 static bool commandBatchActive = false;
200 static bool commandBatchError = false;
201 #endif
203 #if defined(USE_BOARD_INFO)
204 static bool boardInformationUpdated = false;
205 #if defined(USE_SIGNATURE)
206 static bool signatureUpdated = false;
207 #endif
208 #endif // USE_BOARD_INFO
210 static const char* const emptyName = "-";
211 static const char* const emptyString = "";
213 #if !defined(USE_CUSTOM_DEFAULTS)
214 #define CUSTOM_DEFAULTS_START ((char*)0)
215 #define CUSTOM_DEFAULTS_END ((char *)0)
216 #else
217 extern char __custom_defaults_start;
218 extern char __custom_defaults_end;
219 #define CUSTOM_DEFAULTS_START (&__custom_defaults_start)
220 #define CUSTOM_DEFAULTS_END (&__custom_defaults_end)
222 static bool processingCustomDefaults = false;
223 static char cliBufferTemp[CLI_IN_BUFFER_SIZE];
225 #define CUSTOM_DEFAULTS_START_PREFIX ("# " FC_FIRMWARE_NAME)
226 #define CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX "# config: manufacturer_id: "
227 #define CUSTOM_DEFAULTS_BOARD_NAME_PREFIX ", board_name: "
228 #define CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX ", version: "
229 #define CUSTOM_DEFAULTS_DATE_PREFIX ", date: "
231 #define MAX_CHANGESET_ID_LENGTH 8
232 #define MAX_DATE_LENGTH 20
234 static bool customDefaultsHeaderParsed = false;
235 static bool customDefaultsFound = false;
236 static char customDefaultsManufacturerId[MAX_MANUFACTURER_ID_LENGTH + 1] = { 0 };
237 static char customDefaultsBoardName[MAX_BOARD_NAME_LENGTH + 1] = { 0 };
238 static char customDefaultsChangesetId[MAX_CHANGESET_ID_LENGTH + 1] = { 0 };
239 static char customDefaultsDate[MAX_DATE_LENGTH + 1] = { 0 };
240 #endif
242 #if defined(USE_CUSTOM_DEFAULTS_ADDRESS)
243 static char __attribute__ ((section(".custom_defaults_start_address"))) *customDefaultsStart = CUSTOM_DEFAULTS_START;
244 static char __attribute__ ((section(".custom_defaults_end_address"))) *customDefaultsEnd = CUSTOM_DEFAULTS_END;
245 #endif
247 #ifndef USE_QUAD_MIXER_ONLY
248 // sync this with mixerMode_e
249 static const char * const mixerNames[] = {
250 "TRI", "QUADP", "QUADX", "BI",
251 "GIMBAL", "Y6", "HEX6",
252 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
253 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
254 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
255 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
257 #endif
259 // sync this with features_e
260 static const char * const featureNames[] = {
261 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
262 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
263 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
264 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
265 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
266 "", "", "RX_SPI", "", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
269 // sync this with rxFailsafeChannelMode_e
270 static const char rxFailsafeModeCharacters[] = "ahs";
272 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
273 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
274 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
277 #if defined(USE_SENSOR_NAMES)
278 // sync this with sensors_e
279 static const char *const sensorTypeNames[] = {
280 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
283 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
285 static const char * const *sensorHardwareNames[] = {
286 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
288 #endif // USE_SENSOR_NAMES
290 // Needs to be aligned with mcuTypeId_e
291 static const char *mcuTypeNames[] = {
292 "SIMULATOR",
293 "F103",
294 "F303",
295 "F40X",
296 "F411",
297 "F446",
298 "F722",
299 "F745",
300 "F746",
301 "F765",
302 "H750",
303 "H743 (Rev Unknown)",
304 "H743 (Rev.Y)",
305 "H743 (Rev.X)",
306 "H743 (Rev.V)",
309 typedef enum dumpFlags_e {
310 DUMP_MASTER = (1 << 0),
311 DUMP_PROFILE = (1 << 1),
312 DUMP_RATES = (1 << 2),
313 DUMP_ALL = (1 << 3),
314 DO_DIFF = (1 << 4),
315 SHOW_DEFAULTS = (1 << 5),
316 HIDE_UNUSED = (1 << 6),
317 HARDWARE_ONLY = (1 << 7),
318 BARE = (1 << 8),
319 } dumpFlags_t;
321 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
323 typedef enum {
324 REBOOT_TARGET_FIRMWARE,
325 REBOOT_TARGET_BOOTLOADER_ROM,
326 REBOOT_TARGET_BOOTLOADER_FLASH,
327 } rebootTarget_e;
329 typedef struct serialPassthroughPort_s {
330 int id;
331 uint32_t baud;
332 unsigned mode;
333 serialPort_t *port;
334 } serialPassthroughPort_t;
336 static void cliWriterFlushInternal(bufWriter_t *writer)
338 if (writer) {
339 bufWriterFlush(writer);
343 static void cliPrintInternal(bufWriter_t *writer, const char *str)
345 if (writer) {
346 while (*str) {
347 bufWriterAppend(writer, *str++);
349 cliWriterFlushInternal(writer);
353 static void cliWriterFlush()
355 cliWriterFlushInternal(cliWriter);
358 void cliPrint(const char *str)
360 cliPrintInternal(cliWriter, str);
363 void cliPrintLinefeed(void)
365 cliPrint("\r\n");
368 void cliPrintLine(const char *str)
370 cliPrint(str);
371 cliPrintLinefeed();
374 #ifdef MINIMAL_CLI
375 #define cliPrintHashLine(str)
376 #else
377 static void cliPrintHashLine(const char *str)
379 cliPrint("\r\n# ");
380 cliPrintLine(str);
382 #endif
384 static void cliPutp(void *p, char ch)
386 bufWriterAppend(p, ch);
389 static void cliPrintfva(const char *format, va_list va)
391 if (cliWriter) {
392 tfp_format(cliWriter, cliPutp, format, va);
393 cliWriterFlush();
397 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
399 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
400 va_list va;
401 va_start(va, format);
402 cliPrintfva(format, va);
403 va_end(va);
404 cliPrintLinefeed();
405 return true;
406 } else {
407 return false;
411 static void cliWrite(uint8_t ch)
413 if (cliWriter) {
414 bufWriterAppend(cliWriter, ch);
418 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
420 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
421 cliWrite('#');
423 va_list va;
424 va_start(va, format);
425 cliPrintfva(format, va);
426 va_end(va);
427 cliPrintLinefeed();
428 return true;
429 } else {
430 return false;
434 void cliPrintf(const char *format, ...)
436 va_list va;
437 va_start(va, format);
438 cliPrintfva(format, va);
439 va_end(va);
443 void cliPrintLinef(const char *format, ...)
445 va_list va;
446 va_start(va, format);
447 cliPrintfva(format, va);
448 va_end(va);
449 cliPrintLinefeed();
452 static void cliPrintErrorVa(const char *cmdName, const char *format, va_list va)
454 if (cliErrorWriter) {
455 cliPrintInternal(cliErrorWriter, "###ERROR: ");
456 cliPrintInternal(cliErrorWriter, cmdName);
457 cliPrintInternal(cliErrorWriter, ": ");
459 tfp_format(cliErrorWriter, cliPutp, format, va);
460 va_end(va);
462 cliPrintInternal(cliErrorWriter, "###");
465 #ifdef USE_CLI_BATCH
466 if (commandBatchActive) {
467 commandBatchError = true;
469 #endif
472 static void cliPrintError(const char *cmdName, const char *format, ...)
474 va_list va;
475 va_start(va, format);
476 cliPrintErrorVa(cmdName, format, va);
478 if (!cliWriter) {
479 // Supply our own linefeed in case we are printing inside a custom defaults operation
480 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
481 // instead of expecting the directly following command to supply the line feed.
482 cliPrintInternal(cliErrorWriter, "\r\n");
486 static void cliPrintErrorLinef(const char *cmdName, const char *format, ...)
488 va_list va;
489 va_start(va, format);
490 cliPrintErrorVa(cmdName, format, va);
491 cliPrintInternal(cliErrorWriter, "\r\n");
494 static void getMinMax(const clivalue_t *var, int *min, int *max)
496 switch (var->type & VALUE_TYPE_MASK) {
497 case VAR_UINT8:
498 case VAR_UINT16:
499 *min = var->config.minmaxUnsigned.min;
500 *max = var->config.minmaxUnsigned.max;
502 break;
503 default:
504 *min = var->config.minmax.min;
505 *max = var->config.minmax.max;
507 break;
511 static void printValuePointer(const char *cmdName, const clivalue_t *var, const void *valuePointer, bool full)
513 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
514 for (int i = 0; i < var->config.array.length; i++) {
515 switch (var->type & VALUE_TYPE_MASK) {
516 default:
517 case VAR_UINT8:
518 // uint8_t array
519 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
520 break;
522 case VAR_INT8:
523 // int8_t array
524 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
525 break;
527 case VAR_UINT16:
528 // uin16_t array
529 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
530 break;
532 case VAR_INT16:
533 // int16_t array
534 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
535 break;
537 case VAR_UINT32:
538 // uin32_t array
539 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
540 break;
543 if (i < var->config.array.length - 1) {
544 cliPrint(",");
547 } else {
548 int value = 0;
550 switch (var->type & VALUE_TYPE_MASK) {
551 case VAR_UINT8:
552 value = *(uint8_t *)valuePointer;
554 break;
555 case VAR_INT8:
556 value = *(int8_t *)valuePointer;
558 break;
559 case VAR_UINT16:
560 value = *(uint16_t *)valuePointer;
562 break;
563 case VAR_INT16:
564 value = *(int16_t *)valuePointer;
566 break;
567 case VAR_UINT32:
568 value = *(uint32_t *)valuePointer;
570 break;
573 bool valueIsCorrupted = false;
574 switch (var->type & VALUE_MODE_MASK) {
575 case MODE_DIRECT:
576 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
577 cliPrintf("%u", (uint32_t)value);
578 if ((uint32_t)value > var->config.u32Max) {
579 valueIsCorrupted = true;
580 } else if (full) {
581 cliPrintf(" 0 %u", var->config.u32Max);
583 } else {
584 int min;
585 int max;
586 getMinMax(var, &min, &max);
588 cliPrintf("%d", value);
589 if ((value < min) || (value > max)) {
590 valueIsCorrupted = true;
591 } else if (full) {
592 cliPrintf(" %d %d", min, max);
595 break;
596 case MODE_LOOKUP:
597 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
598 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
599 } else {
600 valueIsCorrupted = true;
602 break;
603 case MODE_BITSET:
604 if (value & 1 << var->config.bitpos) {
605 cliPrintf("ON");
606 } else {
607 cliPrintf("OFF");
609 break;
610 case MODE_STRING:
611 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
612 break;
615 if (valueIsCorrupted) {
616 cliPrintLinefeed();
617 cliPrintError(cmdName, "CORRUPTED CONFIG: %s = %d", var->name, value);
623 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
625 bool result = true;
626 int elementCount = 1;
627 uint32_t mask = 0xffffffff;
629 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
630 elementCount = var->config.array.length;
632 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
633 mask = 1 << var->config.bitpos;
635 for (int i = 0; i < elementCount; i++) {
636 switch (var->type & VALUE_TYPE_MASK) {
637 case VAR_UINT8:
638 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
639 break;
641 case VAR_INT8:
642 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
643 break;
645 case VAR_UINT16:
646 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
647 break;
648 case VAR_INT16:
649 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
650 break;
651 case VAR_UINT32:
652 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
653 break;
657 return result;
660 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
662 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
663 cliPrintHashLine(headingStr);
664 return NULL;
665 } else {
666 return headingStr;
670 static void backupPgConfig(const pgRegistry_t *pg)
672 memcpy(pg->copy, pg->address, pg->size);
675 static void restorePgConfig(const pgRegistry_t *pg)
677 memcpy(pg->address, pg->copy, pg->size);
680 static void backupConfigs(void)
682 if (configIsInCopy) {
683 return;
686 // make copies of configs to do differencing
687 PG_FOREACH(pg) {
688 backupPgConfig(pg);
691 configIsInCopy = true;
694 static void restoreConfigs(void)
696 if (!configIsInCopy) {
697 return;
700 PG_FOREACH(pg) {
701 restorePgConfig(pg);
704 configIsInCopy = false;
707 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
708 static bool isReadingConfigFromCopy()
710 return configIsInCopy;
712 #endif
714 static bool isWritingConfigToCopy()
716 return configIsInCopy
717 #if defined(USE_CUSTOM_DEFAULTS)
718 && !processingCustomDefaults
719 #endif
723 #if defined(USE_CUSTOM_DEFAULTS)
724 static bool cliProcessCustomDefaults(bool quiet);
725 #endif
727 static void backupAndResetConfigs(const bool useCustomDefaults)
729 backupConfigs();
731 // reset all configs to defaults to do differencing
732 resetConfig();
734 #if defined(USE_CUSTOM_DEFAULTS)
735 if (useCustomDefaults) {
736 if (!cliProcessCustomDefaults(true)) {
737 cliPrintLine("###WARNING: NO CUSTOM DEFAULTS FOUND###");
740 #else
741 UNUSED(useCustomDefaults);
742 #endif
745 static uint8_t getPidProfileIndexToUse()
747 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
750 static uint8_t getRateProfileIndexToUse()
752 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
756 static uint16_t getValueOffset(const clivalue_t *value)
758 switch (value->type & VALUE_SECTION_MASK) {
759 case MASTER_VALUE:
760 case HARDWARE_VALUE:
761 return value->offset;
762 case PROFILE_VALUE:
763 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
764 case PROFILE_RATE_VALUE:
765 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
767 return 0;
770 STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
772 const pgRegistry_t* rec = pgFind(value->pgn);
773 if (isWritingConfigToCopy()) {
774 return CONST_CAST(void *, rec->copy + getValueOffset(value));
775 } else {
776 return CONST_CAST(void *, rec->address + getValueOffset(value));
780 static const char *dumpPgValue(const char *cmdName, const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
782 const pgRegistry_t *pg = pgFind(value->pgn);
783 #ifdef DEBUG
784 if (!pg) {
785 cliPrintLinef("VALUE %s ERROR", value->name);
786 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
788 #endif
790 const char *format = "set %s = ";
791 const char *defaultFormat = "#set %s = ";
792 const int valueOffset = getValueOffset(value);
793 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
795 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
796 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
797 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
798 cliPrintf(defaultFormat, value->name);
799 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
800 cliPrintLinefeed();
802 cliPrintf(format, value->name);
803 printValuePointer(cmdName, value, pg->copy + valueOffset, false);
804 cliPrintLinefeed();
806 return headingStr;
809 static void dumpAllValues(const char *cmdName, uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
811 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
813 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
814 const clivalue_t *value = &valueTable[i];
815 cliWriterFlush();
816 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
817 headingStr = dumpPgValue(cmdName, value, dumpMask, headingStr);
822 static void cliPrintVar(const char *cmdName, const clivalue_t *var, bool full)
824 const void *ptr = cliGetValuePointer(var);
826 printValuePointer(cmdName, var, ptr, full);
829 static void cliPrintVarRange(const clivalue_t *var)
831 switch (var->type & VALUE_MODE_MASK) {
832 case (MODE_DIRECT): {
833 switch (var->type & VALUE_TYPE_MASK) {
834 case VAR_UINT32:
835 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
837 break;
838 case VAR_UINT8:
839 case VAR_UINT16:
840 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
842 break;
843 default:
844 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
846 break;
849 break;
850 case (MODE_LOOKUP): {
851 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
852 cliPrint("Allowed values: ");
853 bool firstEntry = true;
854 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
855 if (tableEntry->values[i]) {
856 if (!firstEntry) {
857 cliPrint(", ");
859 cliPrintf("%s", tableEntry->values[i]);
860 firstEntry = false;
863 cliPrintLinefeed();
865 break;
866 case (MODE_ARRAY): {
867 cliPrintLinef("Array length: %d", var->config.array.length);
869 break;
870 case (MODE_STRING): {
871 cliPrintLinef("String length: %d - %d", var->config.string.minlength, var->config.string.maxlength);
873 break;
874 case (MODE_BITSET): {
875 cliPrintLinef("Allowed values: OFF, ON");
877 break;
881 static void cliSetVar(const clivalue_t *var, const uint32_t value)
883 void *ptr = cliGetValuePointer(var);
884 uint32_t workValue;
885 uint32_t mask;
887 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
888 switch (var->type & VALUE_TYPE_MASK) {
889 case VAR_UINT8:
890 mask = (1 << var->config.bitpos) & 0xff;
891 if (value) {
892 workValue = *(uint8_t *)ptr | mask;
893 } else {
894 workValue = *(uint8_t *)ptr & ~mask;
896 *(uint8_t *)ptr = workValue;
897 break;
899 case VAR_UINT16:
900 mask = (1 << var->config.bitpos) & 0xffff;
901 if (value) {
902 workValue = *(uint16_t *)ptr | mask;
903 } else {
904 workValue = *(uint16_t *)ptr & ~mask;
906 *(uint16_t *)ptr = workValue;
907 break;
909 case VAR_UINT32:
910 mask = 1 << var->config.bitpos;
911 if (value) {
912 workValue = *(uint32_t *)ptr | mask;
913 } else {
914 workValue = *(uint32_t *)ptr & ~mask;
916 *(uint32_t *)ptr = workValue;
917 break;
919 } else {
920 switch (var->type & VALUE_TYPE_MASK) {
921 case VAR_UINT8:
922 *(uint8_t *)ptr = value;
923 break;
925 case VAR_INT8:
926 *(int8_t *)ptr = value;
927 break;
929 case VAR_UINT16:
930 *(uint16_t *)ptr = value;
931 break;
933 case VAR_INT16:
934 *(int16_t *)ptr = value;
935 break;
937 case VAR_UINT32:
938 *(uint32_t *)ptr = value;
939 break;
944 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
945 static void cliRepeat(char ch, uint8_t len)
947 if (cliWriter) {
948 for (int i = 0; i < len; i++) {
949 bufWriterAppend(cliWriter, ch);
951 cliPrintLinefeed();
954 #endif
956 static void cliPrompt(void)
958 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
959 if (processingCustomDefaults) {
960 cliPrint("\r\nd: #");
961 } else
962 #endif
964 cliPrint("\r\n# ");
968 static void cliShowParseError(const char *cmdName)
970 cliPrintErrorLinef(cmdName, "PARSING FAILED");
973 static void cliShowInvalidArgumentCountError(const char *cmdName)
975 cliPrintErrorLinef(cmdName, "INVALID ARGUMENT COUNT", cmdName);
978 static void cliShowArgumentRangeError(const char *cmdName, char *name, int min, int max)
980 if (name) {
981 cliPrintErrorLinef(cmdName, "%s NOT BETWEEN %d AND %d", name, min, max);
982 } else {
983 cliPrintErrorLinef(cmdName, "ARGUMENT OUT OF RANGE");
987 static const char *nextArg(const char *currentArg)
989 const char *ptr = strchr(currentArg, ' ');
990 while (ptr && *ptr == ' ') {
991 ptr++;
994 return ptr;
997 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
999 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
1000 ptr = nextArg(ptr);
1001 if (ptr) {
1002 int val = atoi(ptr);
1003 val = CHANNEL_VALUE_TO_STEP(val);
1004 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
1005 if (argIndex == 0) {
1006 range->startStep = val;
1007 } else {
1008 range->endStep = val;
1010 (*validArgumentCount)++;
1015 return ptr;
1018 // Check if a string's length is zero
1019 static bool isEmpty(const char *string)
1021 return (string == NULL || *string == '\0') ? true : false;
1024 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
1026 // print out rxConfig failsafe settings
1027 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1028 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1029 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
1030 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
1031 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
1032 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1033 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1034 if (requireValue) {
1035 const char *format = "rxfail %u %c %d";
1036 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1037 channel,
1038 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
1039 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
1041 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1042 channel,
1043 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
1044 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1046 } else {
1047 const char *format = "rxfail %u %c";
1048 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1049 channel,
1050 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
1052 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1053 channel,
1054 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
1060 static void cliRxFailsafe(const char *cmdName, char *cmdline)
1062 uint8_t channel;
1063 char buf[3];
1065 if (isEmpty(cmdline)) {
1066 // print out rxConfig failsafe settings
1067 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1068 cliRxFailsafe(cmdName, itoa(channel, buf, 10));
1070 } else {
1071 const char *ptr = cmdline;
1072 channel = atoi(ptr++);
1073 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
1075 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
1077 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
1078 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
1079 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1081 ptr = nextArg(ptr);
1082 if (ptr) {
1083 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
1084 if (p) {
1085 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
1086 mode = rxFailsafeModesTable[type][requestedMode];
1087 } else {
1088 mode = RX_FAILSAFE_MODE_INVALID;
1090 if (mode == RX_FAILSAFE_MODE_INVALID) {
1091 cliShowParseError(cmdName);
1092 return;
1095 requireValue = mode == RX_FAILSAFE_MODE_SET;
1097 ptr = nextArg(ptr);
1098 if (ptr) {
1099 if (!requireValue) {
1100 cliShowParseError(cmdName);
1101 return;
1103 uint16_t value = atoi(ptr);
1104 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
1105 if (value > MAX_RXFAIL_RANGE_STEP) {
1106 cliPrintErrorLinef(cmdName, "value out of range: %d", value);
1107 return;
1110 channelFailsafeConfig->step = value;
1111 } else if (requireValue) {
1112 cliShowInvalidArgumentCountError(cmdName);
1113 return;
1115 channelFailsafeConfig->mode = mode;
1118 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
1120 // double use of cliPrintf below
1121 // 1. acknowledge interpretation on command,
1122 // 2. query current setting on single item,
1124 if (requireValue) {
1125 cliPrintLinef("rxfail %u %c %d",
1126 channel,
1127 modeCharacter,
1128 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1130 } else {
1131 cliPrintLinef("rxfail %u %c",
1132 channel,
1133 modeCharacter
1136 } else {
1137 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1142 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1144 const char *format = "aux %u %u %u %u %u %u %u";
1145 // print out aux channel settings
1146 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1147 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1148 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1149 bool equalsDefault = false;
1150 if (defaultModeActivationConditions) {
1151 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1152 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1153 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1154 const box_t *box = findBoxByBoxId(macDefault->modeId);
1155 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1156 if (box) {
1157 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1159 box->permanentId,
1160 macDefault->auxChannelIndex,
1161 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1162 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1163 macDefault->modeLogic,
1164 linkedTo ? linkedTo->permanentId : 0
1168 const box_t *box = findBoxByBoxId(mac->modeId);
1169 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1170 if (box) {
1171 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1173 box->permanentId,
1174 mac->auxChannelIndex,
1175 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1176 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1177 mac->modeLogic,
1178 linkedTo ? linkedTo->permanentId : 0
1184 static void cliAux(const char *cmdName, char *cmdline)
1186 int i, val = 0;
1187 const char *ptr;
1189 if (isEmpty(cmdline)) {
1190 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1191 } else {
1192 ptr = cmdline;
1193 i = atoi(ptr++);
1194 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1195 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1196 uint8_t validArgumentCount = 0;
1197 ptr = nextArg(ptr);
1198 if (ptr) {
1199 val = atoi(ptr);
1200 const box_t *box = findBoxByPermanentId(val);
1201 if (box) {
1202 mac->modeId = box->boxId;
1203 validArgumentCount++;
1206 ptr = nextArg(ptr);
1207 if (ptr) {
1208 val = atoi(ptr);
1209 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1210 mac->auxChannelIndex = val;
1211 validArgumentCount++;
1214 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1215 ptr = nextArg(ptr);
1216 if (ptr) {
1217 val = atoi(ptr);
1218 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1219 mac->modeLogic = val;
1220 validArgumentCount++;
1223 ptr = nextArg(ptr);
1224 if (ptr) {
1225 val = atoi(ptr);
1226 const box_t *box = findBoxByPermanentId(val);
1227 if (box) {
1228 mac->linkedTo = box->boxId;
1229 validArgumentCount++;
1232 if (validArgumentCount == 4) { // for backwards compatibility
1233 mac->modeLogic = MODELOGIC_OR;
1234 mac->linkedTo = 0;
1235 } else if (validArgumentCount == 5) { // for backwards compatibility
1236 mac->linkedTo = 0;
1237 } else if (validArgumentCount != 6) {
1238 memset(mac, 0, sizeof(modeActivationCondition_t));
1240 analyzeModeActivationConditions();
1241 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1243 findBoxByBoxId(mac->modeId)->permanentId,
1244 mac->auxChannelIndex,
1245 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1246 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1247 mac->modeLogic,
1248 findBoxByBoxId(mac->linkedTo)->permanentId
1250 } else {
1251 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1256 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1258 const char *format = "serial %d %d %ld %ld %ld %ld";
1259 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1260 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
1261 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1262 continue;
1264 bool equalsDefault = false;
1265 if (serialConfigDefault) {
1266 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1267 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1268 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1269 serialConfigDefault->portConfigs[i].identifier,
1270 serialConfigDefault->portConfigs[i].functionMask,
1271 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1272 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1273 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1274 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1277 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1278 serialConfig->portConfigs[i].identifier,
1279 serialConfig->portConfigs[i].functionMask,
1280 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1281 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1282 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1283 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1288 static void cliSerial(const char *cmdName, char *cmdline)
1290 const char *format = "serial %d %d %ld %ld %ld %ld";
1291 if (isEmpty(cmdline)) {
1292 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1293 return;
1295 serialPortConfig_t portConfig;
1296 memset(&portConfig, 0 , sizeof(portConfig));
1299 uint8_t validArgumentCount = 0;
1301 const char *ptr = cmdline;
1303 int val = atoi(ptr++);
1304 serialPortConfig_t *currentConfig = serialFindPortConfigurationMutable(val);
1306 if (currentConfig) {
1307 portConfig.identifier = val;
1308 validArgumentCount++;
1311 ptr = nextArg(ptr);
1312 if (ptr) {
1313 val = strtoul(ptr, NULL, 10);
1314 portConfig.functionMask = val;
1315 validArgumentCount++;
1318 for (int i = 0; i < 4; i ++) {
1319 ptr = nextArg(ptr);
1320 if (!ptr) {
1321 break;
1324 val = atoi(ptr);
1326 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1327 if (baudRates[baudRateIndex] != (uint32_t) val) {
1328 break;
1331 switch (i) {
1332 case 0:
1333 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1334 continue;
1336 portConfig.msp_baudrateIndex = baudRateIndex;
1337 break;
1338 case 1:
1339 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1340 continue;
1342 portConfig.gps_baudrateIndex = baudRateIndex;
1343 break;
1344 case 2:
1345 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1346 continue;
1348 portConfig.telemetry_baudrateIndex = baudRateIndex;
1349 break;
1350 case 3:
1351 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1352 continue;
1354 portConfig.blackbox_baudrateIndex = baudRateIndex;
1355 break;
1358 validArgumentCount++;
1361 if (validArgumentCount < 6) {
1362 cliShowInvalidArgumentCountError(cmdName);
1363 return;
1366 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1368 cliDumpPrintLinef(0, false, format,
1369 portConfig.identifier,
1370 portConfig.functionMask,
1371 baudRates[portConfig.msp_baudrateIndex],
1372 baudRates[portConfig.gps_baudrateIndex],
1373 baudRates[portConfig.telemetry_baudrateIndex],
1374 baudRates[portConfig.blackbox_baudrateIndex]
1379 #if defined(USE_SERIAL_PASSTHROUGH)
1380 static void cbCtrlLine(void *context, uint16_t ctrl)
1382 #ifdef USE_PINIO
1383 int contextValue = (int)(long)context;
1384 if (contextValue) {
1385 pinioSet(contextValue - 1, !(ctrl & CTRL_LINE_STATE_DTR));
1386 } else
1387 #endif /* USE_PINIO */
1388 UNUSED(context);
1390 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1391 systemReset();
1395 static int cliParseSerialMode(const char *tok)
1397 int mode = 0;
1399 if (strcasestr(tok, "rx")) {
1400 mode |= MODE_RX;
1402 if (strcasestr(tok, "tx")) {
1403 mode |= MODE_TX;
1406 return mode;
1409 static void cliSerialPassthrough(const char *cmdName, char *cmdline)
1411 if (isEmpty(cmdline)) {
1412 cliShowInvalidArgumentCountError(cmdName);
1413 return;
1416 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, NULL}, {cliPort->identifier, 0, 0, cliPort} };
1417 bool enableBaudCb = false;
1418 int port1PinioDtr = 0;
1419 bool port1ResetOnDtr = false;
1420 bool escSensorPassthrough = false;
1421 char *saveptr;
1422 char* tok = strtok_r(cmdline, " ", &saveptr);
1423 int index = 0;
1425 while (tok != NULL) {
1426 switch (index) {
1427 case 0:
1428 if (strcasestr(tok, "esc_sensor")) {
1429 escSensorPassthrough = true;
1430 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1431 ports[0].id = portConfig->identifier;
1432 } else {
1433 ports[0].id = atoi(tok);
1435 break;
1436 case 1:
1437 ports[0].baud = atoi(tok);
1438 break;
1439 case 2:
1440 ports[0].mode = cliParseSerialMode(tok);
1441 break;
1442 case 3:
1443 if (strncasecmp(tok, "reset", strlen(tok)) == 0) {
1444 port1ResetOnDtr = true;
1445 #ifdef USE_PINIO
1446 } else if (strncasecmp(tok, "none", strlen(tok)) == 0) {
1447 port1PinioDtr = 0;
1448 } else {
1449 port1PinioDtr = atoi(tok);
1450 if (port1PinioDtr < 0 || port1PinioDtr > PINIO_COUNT) {
1451 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1452 return ;
1454 #endif /* USE_PINIO */
1456 break;
1457 case 4:
1458 ports[1].id = atoi(tok);
1459 ports[1].port = NULL;
1460 break;
1461 case 5:
1462 ports[1].baud = atoi(tok);
1463 break;
1464 case 6:
1465 ports[1].mode = cliParseSerialMode(tok);
1466 break;
1468 index++;
1469 tok = strtok_r(NULL, " ", &saveptr);
1472 // Port checks
1473 if (ports[0].id == ports[1].id) {
1474 cliPrintLinef("Port1 and port2 are same");
1475 return ;
1478 for (int i = 0; i < 2; i++) {
1479 if (findSerialPortIndexByIdentifier(ports[i].id) == -1) {
1480 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1481 return ;
1482 } else {
1483 cliPrintLinef("Port%d: %d ", i + 1, ports[i].id);
1487 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1488 enableBaudCb = true;
1491 for (int i = 0; i < 2; i++) {
1492 serialPort_t **port = &(ports[i].port);
1493 if (*port != NULL) {
1494 continue;
1497 int portIndex = i + 1;
1498 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(ports[i].id);
1499 if (!portUsage || portUsage->serialPort == NULL) {
1500 bool isUseDefaultBaud = false;
1501 if (ports[i].baud == 0) {
1502 // Set default baud
1503 ports[i].baud = 57600;
1504 isUseDefaultBaud = true;
1507 if (!ports[i].mode) {
1508 ports[i].mode = MODE_RXTX;
1511 *port = openSerialPort(ports[i].id, FUNCTION_NONE, NULL, NULL,
1512 ports[i].baud, ports[i].mode,
1513 SERIAL_NOT_INVERTED);
1514 if (!*port) {
1515 cliPrintLinef("Port%d could not be opened.", portIndex);
1516 return;
1519 if (isUseDefaultBaud) {
1520 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex, ports[i].baud);
1521 } else {
1522 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex, ports[i].baud);
1524 } else {
1525 *port = portUsage->serialPort;
1526 // If the user supplied a mode, override the port's mode, otherwise
1527 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1528 // Set the baud rate if specified
1529 if (ports[i].baud) {
1530 cliPrintf("Port%d is already open, setting baud = %d.\r\n", portIndex, ports[i].baud);
1531 serialSetBaudRate(*port, ports[i].baud);
1532 } else {
1533 cliPrintf("Port%d is already open, baud = %d.\r\n", portIndex, (*port)->baudRate);
1536 if (ports[i].mode && (*port)->mode != ports[i].mode) {
1537 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1538 portIndex, (*port)->mode, ports[i].mode);
1539 serialSetMode(*port, ports[i].mode);
1542 // If this port has a rx callback associated we need to remove it now.
1543 // Otherwise no data will be pushed in the serial port buffer!
1544 if ((*port)->rxCallback) {
1545 (*port)->rxCallback = NULL;
1550 // If no baud rate is specified allow to be set via USB
1551 if (enableBaudCb) {
1552 cliPrintLine("Port1 baud rate change over USB enabled.");
1553 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1554 // baud rate over USB without setting it using the serialpassthrough command
1555 serialSetBaudRateCb(ports[0].port, serialSetBaudRate, ports[1].port);
1558 char *resetMessage = "";
1559 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1560 resetMessage = "or drop DTR ";
1563 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1565 if ((ports[1].id == SERIAL_PORT_USB_VCP) && (port1ResetOnDtr
1566 #ifdef USE_PINIO
1567 || port1PinioDtr
1568 #endif /* USE_PINIO */
1569 )) {
1570 // Register control line state callback
1571 serialSetCtrlLineStateCb(ports[0].port, cbCtrlLine, (void *)(intptr_t)(port1PinioDtr));
1574 // XXX Review ESC pass through under refactored motor handling
1575 #ifdef USE_PWM_OUTPUT
1576 if (escSensorPassthrough) {
1577 // pwmDisableMotors();
1578 motorDisable();
1579 delay(5);
1580 unsigned motorsCount = getMotorCount();
1581 for (unsigned i = 0; i < motorsCount; i++) {
1582 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1583 if (tag) {
1584 const timerHardware_t *timerHardware = timerGetByTag(tag);
1585 if (timerHardware) {
1586 IO_t io = IOGetByTag(tag);
1587 IOInit(io, OWNER_MOTOR, 0);
1588 IOConfigGPIO(io, IOCFG_OUT_PP);
1589 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1590 IOLo(io);
1591 } else {
1592 IOHi(io);
1598 #endif
1600 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1602 #endif
1604 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1606 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1607 // print out adjustment ranges channel settings
1608 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1609 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1610 const adjustmentRange_t *ar = &adjustmentRanges[i];
1611 bool equalsDefault = false;
1612 if (defaultAdjustmentRanges) {
1613 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1614 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1615 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1616 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1618 arDefault->auxChannelIndex,
1619 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1620 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1621 arDefault->adjustmentConfig,
1622 arDefault->auxSwitchChannelIndex,
1623 arDefault->adjustmentCenter,
1624 arDefault->adjustmentScale
1627 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1629 ar->auxChannelIndex,
1630 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1631 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1632 ar->adjustmentConfig,
1633 ar->auxSwitchChannelIndex,
1634 ar->adjustmentCenter,
1635 ar->adjustmentScale
1640 static void cliAdjustmentRange(const char *cmdName, char *cmdline)
1642 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1643 int i, val = 0;
1644 const char *ptr;
1646 if (isEmpty(cmdline)) {
1647 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1648 } else {
1649 ptr = cmdline;
1650 i = atoi(ptr++);
1651 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1652 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1653 uint8_t validArgumentCount = 0;
1655 ptr = nextArg(ptr);
1656 if (ptr) {
1657 val = atoi(ptr);
1658 // Was: slot
1659 // Keeping the parameter to retain backwards compatibility for the command format.
1660 validArgumentCount++;
1662 ptr = nextArg(ptr);
1663 if (ptr) {
1664 val = atoi(ptr);
1665 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1666 ar->auxChannelIndex = val;
1667 validArgumentCount++;
1671 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1673 ptr = nextArg(ptr);
1674 if (ptr) {
1675 val = atoi(ptr);
1676 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1677 ar->adjustmentConfig = val;
1678 validArgumentCount++;
1681 ptr = nextArg(ptr);
1682 if (ptr) {
1683 val = atoi(ptr);
1684 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1685 ar->auxSwitchChannelIndex = val;
1686 validArgumentCount++;
1690 if (validArgumentCount != 6) {
1691 memset(ar, 0, sizeof(adjustmentRange_t));
1692 cliShowInvalidArgumentCountError(cmdName);
1693 return;
1696 // Optional arguments
1697 ar->adjustmentCenter = 0;
1698 ar->adjustmentScale = 0;
1700 ptr = nextArg(ptr);
1701 if (ptr) {
1702 val = atoi(ptr);
1703 ar->adjustmentCenter = val;
1704 validArgumentCount++;
1706 ptr = nextArg(ptr);
1707 if (ptr) {
1708 val = atoi(ptr);
1709 ar->adjustmentScale = val;
1710 validArgumentCount++;
1713 activeAdjustmentRangeReset();
1715 cliDumpPrintLinef(0, false, format,
1717 ar->auxChannelIndex,
1718 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1719 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1720 ar->adjustmentConfig,
1721 ar->auxSwitchChannelIndex,
1722 ar->adjustmentCenter,
1723 ar->adjustmentScale
1726 } else {
1727 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1732 #ifndef USE_QUAD_MIXER_ONLY
1733 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1735 const char *format = "mmix %d %s %s %s %s";
1736 char buf0[FTOA_BUFFER_LENGTH];
1737 char buf1[FTOA_BUFFER_LENGTH];
1738 char buf2[FTOA_BUFFER_LENGTH];
1739 char buf3[FTOA_BUFFER_LENGTH];
1740 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1741 if (customMotorMixer[i].throttle == 0.0f)
1742 break;
1743 const float thr = customMotorMixer[i].throttle;
1744 const float roll = customMotorMixer[i].roll;
1745 const float pitch = customMotorMixer[i].pitch;
1746 const float yaw = customMotorMixer[i].yaw;
1747 bool equalsDefault = false;
1748 if (defaultCustomMotorMixer) {
1749 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1750 const float rollDefault = defaultCustomMotorMixer[i].roll;
1751 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1752 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1753 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1755 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1756 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1758 ftoa(thrDefault, buf0),
1759 ftoa(rollDefault, buf1),
1760 ftoa(pitchDefault, buf2),
1761 ftoa(yawDefault, buf3));
1763 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1765 ftoa(thr, buf0),
1766 ftoa(roll, buf1),
1767 ftoa(pitch, buf2),
1768 ftoa(yaw, buf3));
1771 #endif // USE_QUAD_MIXER_ONLY
1773 static void cliMotorMix(const char *cmdName, char *cmdline)
1775 #ifdef USE_QUAD_MIXER_ONLY
1776 UNUSED(cmdName);
1777 UNUSED(cmdline);
1778 #else
1779 int check = 0;
1780 uint8_t len;
1781 const char *ptr;
1783 if (isEmpty(cmdline)) {
1784 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1785 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1786 // erase custom mixer
1787 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1788 customMotorMixerMutable(i)->throttle = 0.0f;
1790 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1791 ptr = nextArg(cmdline);
1792 if (ptr) {
1793 len = strlen(ptr);
1794 for (uint32_t i = 0; ; i++) {
1795 if (mixerNames[i] == NULL) {
1796 cliPrintErrorLinef(cmdName, "INVALID NAME");
1797 break;
1799 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1800 mixerLoadMix(i, customMotorMixerMutable(0));
1801 cliPrintLinef("Loaded %s", mixerNames[i]);
1802 cliMotorMix(cmdName, "");
1803 break;
1807 } else {
1808 ptr = cmdline;
1809 uint32_t i = atoi(ptr); // get motor number
1810 if (i < MAX_SUPPORTED_MOTORS) {
1811 ptr = nextArg(ptr);
1812 if (ptr) {
1813 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1814 check++;
1816 ptr = nextArg(ptr);
1817 if (ptr) {
1818 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1819 check++;
1821 ptr = nextArg(ptr);
1822 if (ptr) {
1823 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1824 check++;
1826 ptr = nextArg(ptr);
1827 if (ptr) {
1828 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1829 check++;
1831 if (check != 4) {
1832 cliShowInvalidArgumentCountError(cmdName);
1833 } else {
1834 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1836 } else {
1837 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1840 #endif
1843 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1845 const char *format = "rxrange %u %u %u";
1846 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1847 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1848 bool equalsDefault = false;
1849 if (defaultChannelRangeConfigs) {
1850 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1851 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1852 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1854 defaultChannelRangeConfigs[i].min,
1855 defaultChannelRangeConfigs[i].max
1858 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1860 channelRangeConfigs[i].min,
1861 channelRangeConfigs[i].max
1866 static void cliRxRange(const char *cmdName, char *cmdline)
1868 const char *format = "rxrange %u %u %u";
1869 int i, validArgumentCount = 0;
1870 const char *ptr;
1872 if (isEmpty(cmdline)) {
1873 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1874 } else if (strcasecmp(cmdline, "reset") == 0) {
1875 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1876 } else {
1877 ptr = cmdline;
1878 i = atoi(ptr);
1879 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1880 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1882 ptr = nextArg(ptr);
1883 if (ptr) {
1884 rangeMin = atoi(ptr);
1885 validArgumentCount++;
1888 ptr = nextArg(ptr);
1889 if (ptr) {
1890 rangeMax = atoi(ptr);
1891 validArgumentCount++;
1894 if (validArgumentCount != 2) {
1895 cliShowInvalidArgumentCountError(cmdName);
1896 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1897 cliShowArgumentRangeError(cmdName, "range min/max", PWM_PULSE_MIN, PWM_PULSE_MAX);
1898 } else {
1899 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1900 channelRangeConfig->min = rangeMin;
1901 channelRangeConfig->max = rangeMax;
1902 cliDumpPrintLinef(0, false, format,
1904 channelRangeConfig->min,
1905 channelRangeConfig->max
1909 } else {
1910 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1915 #ifdef USE_LED_STRIP_STATUS_MODE
1916 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1918 const char *format = "led %u %s";
1919 char ledConfigBuffer[20];
1920 char ledConfigDefaultBuffer[20];
1921 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1922 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1923 ledConfig_t ledConfig = ledConfigs[i];
1924 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1925 bool equalsDefault = false;
1926 if (defaultLedConfigs) {
1927 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1928 equalsDefault = ledConfig == ledConfigDefault;
1929 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1930 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1931 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1933 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1937 static void cliLed(const char *cmdName, char *cmdline)
1939 const char *format = "led %u %s";
1940 char ledConfigBuffer[20];
1941 int i;
1942 const char *ptr;
1944 if (isEmpty(cmdline)) {
1945 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
1946 } else {
1947 ptr = cmdline;
1948 i = atoi(ptr);
1949 if (i >= 0 && i < LED_MAX_STRIP_LENGTH) {
1950 ptr = nextArg(cmdline);
1951 if (parseLedStripConfig(i, ptr)) {
1952 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1953 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1954 } else {
1955 cliShowParseError(cmdName);
1957 } else {
1958 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_MAX_STRIP_LENGTH - 1);
1963 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
1965 const char *format = "color %u %d,%u,%u";
1966 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1967 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1968 const hsvColor_t *color = &colors[i];
1969 bool equalsDefault = false;
1970 if (defaultColors) {
1971 const hsvColor_t *colorDefault = &defaultColors[i];
1972 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1973 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1974 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1976 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1980 static void cliColor(const char *cmdName, char *cmdline)
1982 const char *format = "color %u %d,%u,%u";
1983 if (isEmpty(cmdline)) {
1984 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
1985 } else {
1986 const char *ptr = cmdline;
1987 const int i = atoi(ptr);
1988 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1989 ptr = nextArg(cmdline);
1990 if (parseColor(i, ptr)) {
1991 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
1992 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1993 } else {
1994 cliShowParseError(cmdName);
1996 } else {
1997 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
2002 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
2004 const char *format = "mode_color %u %u %u";
2005 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2006 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
2007 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
2008 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
2009 bool equalsDefault = false;
2010 if (defaultLedStripConfig) {
2011 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
2012 equalsDefault = colorIndex == colorIndexDefault;
2013 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2014 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
2016 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
2020 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
2021 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
2022 bool equalsDefault = false;
2023 if (defaultLedStripConfig) {
2024 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
2025 equalsDefault = colorIndex == colorIndexDefault;
2026 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2027 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
2029 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
2032 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
2033 bool equalsDefault = false;
2034 if (defaultLedStripConfig) {
2035 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
2036 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
2037 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2038 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
2040 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
2043 static void cliModeColor(const char *cmdName, char *cmdline)
2045 if (isEmpty(cmdline)) {
2046 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
2047 } else {
2048 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
2049 int args[ARGS_COUNT];
2050 int argNo = 0;
2051 char *saveptr;
2052 const char* ptr = strtok_r(cmdline, " ", &saveptr);
2053 while (ptr && argNo < ARGS_COUNT) {
2054 args[argNo++] = atoi(ptr);
2055 ptr = strtok_r(NULL, " ", &saveptr);
2058 if (ptr != NULL || argNo != ARGS_COUNT) {
2059 cliShowInvalidArgumentCountError(cmdName);
2060 return;
2063 int modeIdx = args[MODE];
2064 int funIdx = args[FUNCTION];
2065 int color = args[COLOR];
2066 if (!setModeColor(modeIdx, funIdx, color)) {
2067 cliShowParseError(cmdName);
2068 return;
2070 // values are validated
2071 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
2074 #endif
2076 #ifdef USE_SERVOS
2077 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
2079 // print out servo settings
2080 const char *format = "servo %u %d %d %d %d %d";
2081 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2082 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2083 const servoParam_t *servoConf = &servoParams[i];
2084 bool equalsDefault = false;
2085 if (defaultServoParams) {
2086 const servoParam_t *defaultServoConf = &defaultServoParams[i];
2087 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
2088 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2089 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2091 defaultServoConf->min,
2092 defaultServoConf->max,
2093 defaultServoConf->middle,
2094 defaultServoConf->rate,
2095 defaultServoConf->forwardFromChannel
2098 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2100 servoConf->min,
2101 servoConf->max,
2102 servoConf->middle,
2103 servoConf->rate,
2104 servoConf->forwardFromChannel
2107 // print servo directions
2108 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2109 const char *format = "smix reverse %d %d r";
2110 const servoParam_t *servoConf = &servoParams[i];
2111 const servoParam_t *servoConfDefault = &defaultServoParams[i];
2112 if (defaultServoParams) {
2113 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
2114 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2115 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
2116 if (servoConfDefault->reversedSources & (1 << channel)) {
2117 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
2119 if (servoConf->reversedSources & (1 << channel)) {
2120 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
2123 } else {
2124 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2125 if (servoConf->reversedSources & (1 << channel)) {
2126 cliDumpPrintLinef(dumpMask, true, format, i , channel);
2133 static void cliServo(const char *cmdName, char *cmdline)
2135 const char *format = "servo %u %d %d %d %d %d";
2136 enum { SERVO_ARGUMENT_COUNT = 6 };
2137 int16_t arguments[SERVO_ARGUMENT_COUNT];
2139 servoParam_t *servo;
2141 int i;
2142 char *ptr;
2144 if (isEmpty(cmdline)) {
2145 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2146 } else {
2147 int validArgumentCount = 0;
2149 ptr = cmdline;
2151 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2153 // If command line doesn't fit the format, don't modify the config
2154 while (*ptr) {
2155 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2156 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2157 cliShowInvalidArgumentCountError(cmdName);
2158 return;
2161 arguments[validArgumentCount++] = atoi(ptr);
2163 do {
2164 ptr++;
2165 } while (*ptr >= '0' && *ptr <= '9');
2166 } else if (*ptr == ' ') {
2167 ptr++;
2168 } else {
2169 cliShowParseError(cmdName);
2170 return;
2174 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2176 i = arguments[INDEX];
2178 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2179 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2180 cliShowInvalidArgumentCountError(cmdName);
2181 return;
2184 servo = servoParamsMutable(i);
2186 if (
2187 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
2188 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
2189 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2190 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
2191 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2192 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2194 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2195 return;
2198 servo->min = arguments[MIN];
2199 servo->max = arguments[MAX];
2200 servo->middle = arguments[MIDDLE];
2201 servo->rate = arguments[RATE];
2202 servo->forwardFromChannel = arguments[FORWARD];
2204 cliDumpPrintLinef(0, false, format,
2206 servo->min,
2207 servo->max,
2208 servo->middle,
2209 servo->rate,
2210 servo->forwardFromChannel
2215 #endif
2217 #ifdef USE_SERVOS
2218 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2220 const char *format = "smix %d %d %d %d %d %d %d %d";
2221 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2222 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2223 const servoMixer_t customServoMixer = customServoMixers[i];
2224 if (customServoMixer.rate == 0) {
2225 break;
2228 bool equalsDefault = false;
2229 if (defaultCustomServoMixers) {
2230 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2231 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2233 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2234 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2236 customServoMixerDefault.targetChannel,
2237 customServoMixerDefault.inputSource,
2238 customServoMixerDefault.rate,
2239 customServoMixerDefault.speed,
2240 customServoMixerDefault.min,
2241 customServoMixerDefault.max,
2242 customServoMixerDefault.box
2245 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2247 customServoMixer.targetChannel,
2248 customServoMixer.inputSource,
2249 customServoMixer.rate,
2250 customServoMixer.speed,
2251 customServoMixer.min,
2252 customServoMixer.max,
2253 customServoMixer.box
2258 static void cliServoMix(const char *cmdName, char *cmdline)
2260 int args[8], check = 0;
2261 int len = strlen(cmdline);
2263 if (len == 0) {
2264 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2265 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2266 // erase custom mixer
2267 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2268 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2269 servoParamsMutable(i)->reversedSources = 0;
2271 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2272 const char *ptr = nextArg(cmdline);
2273 if (ptr) {
2274 len = strlen(ptr);
2275 for (uint32_t i = 0; ; i++) {
2276 if (mixerNames[i] == NULL) {
2277 cliPrintErrorLinef(cmdName, "INVALID NAME");
2278 break;
2280 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2281 servoMixerLoadMix(i);
2282 cliPrintLinef("Loaded %s", mixerNames[i]);
2283 cliServoMix(cmdName, "");
2284 break;
2288 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2289 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2290 char *ptr = strchr(cmdline, ' ');
2292 if (ptr == NULL) {
2293 cliPrintf("s");
2294 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2295 cliPrintf("\ti%d", inputSource);
2296 cliPrintLinefeed();
2298 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2299 cliPrintf("%d", servoIndex);
2300 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2301 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2303 cliPrintLinefeed();
2305 return;
2308 char *saveptr;
2309 ptr = strtok_r(ptr, " ", &saveptr);
2310 while (ptr != NULL && check < ARGS_COUNT - 1) {
2311 args[check++] = atoi(ptr);
2312 ptr = strtok_r(NULL, " ", &saveptr);
2315 if (ptr == NULL || check != ARGS_COUNT - 1) {
2316 cliShowInvalidArgumentCountError(cmdName);
2317 return;
2320 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2321 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2322 && (*ptr == 'r' || *ptr == 'n')) {
2323 if (*ptr == 'r') {
2324 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2325 } else {
2326 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2328 } else {
2329 cliShowArgumentRangeError(cmdName, "servo", 0, MAX_SUPPORTED_SERVOS);
2330 return;
2333 cliServoMix(cmdName, "reverse");
2334 } else {
2335 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2336 char *saveptr;
2337 char *ptr = strtok_r(cmdline, " ", &saveptr);
2338 while (ptr != NULL && check < ARGS_COUNT) {
2339 args[check++] = atoi(ptr);
2340 ptr = strtok_r(NULL, " ", &saveptr);
2343 if (ptr != NULL || check != ARGS_COUNT) {
2344 cliShowInvalidArgumentCountError(cmdName);
2345 return;
2348 int32_t i = args[RULE];
2349 if (i >= 0 && i < MAX_SERVO_RULES &&
2350 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2351 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2352 args[RATE] >= -100 && args[RATE] <= 100 &&
2353 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2354 args[MIN] >= 0 && args[MIN] <= 100 &&
2355 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2356 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2357 customServoMixersMutable(i)->targetChannel = args[TARGET];
2358 customServoMixersMutable(i)->inputSource = args[INPUT];
2359 customServoMixersMutable(i)->rate = args[RATE];
2360 customServoMixersMutable(i)->speed = args[SPEED];
2361 customServoMixersMutable(i)->min = args[MIN];
2362 customServoMixersMutable(i)->max = args[MAX];
2363 customServoMixersMutable(i)->box = args[BOX];
2364 cliServoMix(cmdName, "");
2365 } else {
2366 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2370 #endif
2372 #ifdef USE_SDCARD
2374 static void cliWriteBytes(const uint8_t *buffer, int count)
2376 while (count > 0) {
2377 cliWrite(*buffer);
2378 buffer++;
2379 count--;
2383 static void cliSdInfo(const char *cmdName, char *cmdline)
2385 UNUSED(cmdName);
2386 UNUSED(cmdline);
2388 cliPrint("SD card: ");
2390 if (sdcardConfig()->mode == SDCARD_MODE_NONE) {
2391 cliPrintLine("Not configured");
2393 return;
2396 if (!sdcard_isInserted()) {
2397 cliPrintLine("None inserted");
2398 return;
2401 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2402 cliPrintLine("Startup failed");
2403 return;
2406 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2408 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2409 metadata->manufacturerID,
2410 metadata->numBlocks / 2, /* One block is half a kB */
2411 metadata->productionMonth,
2412 metadata->productionYear,
2413 metadata->productRevisionMajor,
2414 metadata->productRevisionMinor
2417 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2419 cliPrint("'\r\n" "Filesystem: ");
2421 switch (afatfs_getFilesystemState()) {
2422 case AFATFS_FILESYSTEM_STATE_READY:
2423 cliPrint("Ready");
2424 break;
2425 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2426 cliPrint("Initializing");
2427 break;
2428 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2429 case AFATFS_FILESYSTEM_STATE_FATAL:
2430 cliPrint("Fatal");
2432 switch (afatfs_getLastError()) {
2433 case AFATFS_ERROR_BAD_MBR:
2434 cliPrint(" - no FAT MBR partitions");
2435 break;
2436 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2437 cliPrint(" - bad FAT header");
2438 break;
2439 case AFATFS_ERROR_GENERIC:
2440 case AFATFS_ERROR_NONE:
2441 ; // Nothing more detailed to print
2442 break;
2444 break;
2446 cliPrintLinefeed();
2449 #endif
2451 #ifdef USE_FLASH_CHIP
2453 static void cliFlashInfo(const char *cmdName, char *cmdline)
2455 UNUSED(cmdName);
2456 UNUSED(cmdline);
2458 const flashGeometry_t *layout = flashGetGeometry();
2460 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2461 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2463 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2464 const flashPartition_t *partition;
2465 if (index == 0) {
2466 cliPrintLine("Paritions:");
2468 partition = flashPartitionFindByIndex(index);
2469 if (!partition) {
2470 break;
2472 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2474 #ifdef USE_FLASHFS
2475 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2477 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2478 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2479 flashfsGetOffset()
2481 #endif
2485 static void cliFlashErase(const char *cmdName, char *cmdline)
2487 UNUSED(cmdName);
2488 UNUSED(cmdline);
2490 if (!flashfsIsSupported()) {
2491 return;
2494 #ifndef MINIMAL_CLI
2495 uint32_t i = 0;
2496 cliPrintLine("Erasing, please wait ... ");
2497 #else
2498 cliPrintLine("Erasing,");
2499 #endif
2501 cliWriterFlush();
2502 flashfsEraseCompletely();
2504 while (!flashfsIsReady()) {
2505 #ifndef MINIMAL_CLI
2506 cliPrintf(".");
2507 if (i++ > 120) {
2508 i=0;
2509 cliPrintLinefeed();
2512 cliWriterFlush();
2513 #endif
2514 delay(100);
2516 beeper(BEEPER_BLACKBOX_ERASE);
2517 cliPrintLinefeed();
2518 cliPrintLine("Done.");
2521 #ifdef USE_FLASH_TOOLS
2523 static void cliFlashVerify(const char *cmdName, char *cmdline)
2525 UNUSED(cmdline);
2527 cliPrintLine("Verifying");
2528 if (flashfsVerifyEntireFlash()) {
2529 cliPrintLine("Success");
2530 } else {
2531 cliPrintErrorLinef(cmdName, "Failed");
2535 static void cliFlashWrite(const char *cmdName, char *cmdline)
2537 const uint32_t address = atoi(cmdline);
2538 const char *text = strchr(cmdline, ' ');
2540 if (!text) {
2541 cliShowInvalidArgumentCountError(cmdName);
2542 } else {
2543 flashfsSeekAbs(address);
2544 flashfsWrite((uint8_t*)text, strlen(text), true);
2545 flashfsFlushSync();
2547 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2551 static void cliFlashRead(const char *cmdName, char *cmdline)
2553 uint32_t address = atoi(cmdline);
2555 const char *nextArg = strchr(cmdline, ' ');
2557 if (!nextArg) {
2558 cliShowInvalidArgumentCountError(cmdName);
2559 } else {
2560 uint32_t length = atoi(nextArg);
2562 cliPrintLinef("Reading %u bytes at %u:", length, address);
2564 uint8_t buffer[32];
2565 while (length > 0) {
2566 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2568 for (int i = 0; i < bytesRead; i++) {
2569 cliWrite(buffer[i]);
2572 length -= bytesRead;
2573 address += bytesRead;
2575 if (bytesRead == 0) {
2576 //Assume we reached the end of the volume or something fatal happened
2577 break;
2580 cliPrintLinefeed();
2584 #endif
2585 #endif
2587 #ifdef USE_VTX_CONTROL
2588 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2590 // print out vtx channel settings
2591 const char *format = "vtx %u %u %u %u %u %u %u";
2592 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2593 bool equalsDefault = false;
2594 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2595 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2596 if (vtxConfigDefault) {
2597 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2598 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2599 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2600 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2602 cacDefault->auxChannelIndex,
2603 cacDefault->band,
2604 cacDefault->channel,
2605 cacDefault->power,
2606 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2607 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2610 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2612 cac->auxChannelIndex,
2613 cac->band,
2614 cac->channel,
2615 cac->power,
2616 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2617 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2622 static void cliVtx(const char *cmdName, char *cmdline)
2624 const char *format = "vtx %u %u %u %u %u %u %u";
2625 int i, val = 0;
2626 const char *ptr;
2628 if (isEmpty(cmdline)) {
2629 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2630 } else {
2631 #ifdef USE_VTX_TABLE
2632 const uint8_t maxBandIndex = vtxTableConfig()->bands;
2633 const uint8_t maxChannelIndex = vtxTableConfig()->channels;
2634 const uint8_t maxPowerIndex = vtxTableConfig()->powerLevels;
2635 #else
2636 const uint8_t maxBandIndex = VTX_TABLE_MAX_BANDS;
2637 const uint8_t maxChannelIndex = VTX_TABLE_MAX_CHANNELS;
2638 const uint8_t maxPowerIndex = VTX_TABLE_MAX_POWER_LEVELS;
2639 #endif
2640 ptr = cmdline;
2641 i = atoi(ptr++);
2642 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2643 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2644 uint8_t validArgumentCount = 0;
2645 ptr = nextArg(ptr);
2646 if (ptr) {
2647 val = atoi(ptr);
2648 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2649 cac->auxChannelIndex = val;
2650 validArgumentCount++;
2653 ptr = nextArg(ptr);
2654 if (ptr) {
2655 val = atoi(ptr);
2656 if (val >= 0 && val <= maxBandIndex) {
2657 cac->band = val;
2658 validArgumentCount++;
2661 ptr = nextArg(ptr);
2662 if (ptr) {
2663 val = atoi(ptr);
2664 if (val >= 0 && val <= maxChannelIndex) {
2665 cac->channel = val;
2666 validArgumentCount++;
2669 ptr = nextArg(ptr);
2670 if (ptr) {
2671 val = atoi(ptr);
2672 if (val >= 0 && val <= maxPowerIndex) {
2673 cac->power= val;
2674 validArgumentCount++;
2677 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2679 if (validArgumentCount != 6) {
2680 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2681 cliShowInvalidArgumentCountError(cmdName);
2682 } else {
2683 cliDumpPrintLinef(0, false, format,
2685 cac->auxChannelIndex,
2686 cac->band,
2687 cac->channel,
2688 cac->power,
2689 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2690 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2693 } else {
2694 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2699 #endif // VTX_CONTROL
2701 #ifdef USE_VTX_TABLE
2703 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2705 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2706 char freqtmp[5 + 1];
2707 freqbuf[0] = 0;
2708 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2709 for (int channel = 0; channel < channels; channel++) {
2710 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2711 strcat(freqbuf, freqtmp);
2713 return freqbuf;
2716 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2718 char *fmt = "vtxtable band %d %s %c%s";
2719 bool equalsDefault = false;
2721 if (defaultConfig) {
2722 equalsDefault = true;
2723 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2724 equalsDefault = false;
2726 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2727 equalsDefault = false;
2729 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2730 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2731 equalsDefault = false;
2734 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2735 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2736 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2739 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2740 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2741 return headingStr;
2744 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2746 // (max 4 digit + 1 space) per level
2747 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2748 char pwrtmp[5 + 1];
2749 pwrbuf[0] = 0;
2750 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2751 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2752 strcat(pwrbuf, pwrtmp);
2754 return pwrbuf;
2757 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2759 char *fmt = "vtxtable powervalues%s";
2760 bool equalsDefault = false;
2761 if (defaultConfig) {
2762 equalsDefault = true;
2763 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2764 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2765 equalsDefault = false;
2768 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2769 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2770 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2773 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2774 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2775 return headingStr;
2778 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2780 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2781 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2782 pwrbuf[0] = 0;
2783 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2784 strcat(pwrbuf, " ");
2785 strcpy(pwrtmp, labels[pwrindex]);
2786 // trim trailing space
2787 char *sp;
2788 while ((sp = strchr(pwrtmp, ' '))) {
2789 *sp = 0;
2791 strcat(pwrbuf, pwrtmp);
2793 return pwrbuf;
2796 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2798 char *fmt = "vtxtable powerlabels%s";
2799 bool equalsDefault = false;
2800 if (defaultConfig) {
2801 equalsDefault = true;
2802 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2803 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2804 equalsDefault = false;
2807 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2808 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2809 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2812 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2813 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2814 return headingStr;
2817 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2819 bool equalsDefault;
2820 char *fmt;
2822 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2824 // bands
2825 equalsDefault = false;
2826 fmt = "vtxtable bands %d";
2827 if (defaultConfig) {
2828 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2829 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2830 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2832 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2834 // channels
2835 equalsDefault = false;
2836 fmt = "vtxtable channels %d";
2837 if (defaultConfig) {
2838 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2839 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2840 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2842 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2844 // band
2846 for (int band = 0; band < currentConfig->bands; band++) {
2847 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2850 // powerlevels
2852 equalsDefault = false;
2853 fmt = "vtxtable powerlevels %d";
2854 if (defaultConfig) {
2855 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2856 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2857 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2859 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2861 // powervalues
2863 // powerlabels
2864 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2865 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2868 static void cliVtxTable(const char *cmdName, char *cmdline)
2870 char *tok;
2871 char *saveptr;
2873 // Band number or nothing
2874 tok = strtok_r(cmdline, " ", &saveptr);
2876 if (!tok) {
2877 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2878 return;
2881 if (strcasecmp(tok, "bands") == 0) {
2882 tok = strtok_r(NULL, " ", &saveptr);
2883 int bands = atoi(tok);
2884 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2885 cliShowArgumentRangeError(cmdName, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS);
2886 return;
2888 if (bands < vtxTableConfigMutable()->bands) {
2889 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2890 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2893 vtxTableConfigMutable()->bands = bands;
2895 } else if (strcasecmp(tok, "channels") == 0) {
2896 tok = strtok_r(NULL, " ", &saveptr);
2898 int channels = atoi(tok);
2899 if (channels < 0 || channels > VTX_TABLE_MAX_CHANNELS) {
2900 cliShowArgumentRangeError(cmdName, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS);
2901 return;
2903 if (channels < vtxTableConfigMutable()->channels) {
2904 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2905 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2908 vtxTableConfigMutable()->channels = channels;
2910 } else if (strcasecmp(tok, "powerlevels") == 0) {
2911 // Number of power levels
2912 tok = strtok_r(NULL, " ", &saveptr);
2913 if (tok) {
2914 int levels = atoi(tok);
2915 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2916 cliShowArgumentRangeError(cmdName, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS);
2917 } else {
2918 if (levels < vtxTableConfigMutable()->powerLevels) {
2919 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2920 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2922 vtxTableConfigMutable()->powerLevels = levels;
2924 } else {
2925 // XXX Show current level count?
2927 return;
2929 } else if (strcasecmp(tok, "powervalues") == 0) {
2930 // Power values
2931 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2932 int count;
2933 int levels = vtxTableConfigMutable()->powerLevels;
2935 memset(power, 0, sizeof(power));
2937 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2938 int value = atoi(tok);
2939 power[count] = value;
2942 // Check remaining tokens
2944 if (count < levels) {
2945 cliPrintErrorLinef(cmdName, "NOT ENOUGH VALUES (EXPECTED %d)", levels);
2946 return;
2947 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2948 cliPrintErrorLinef(cmdName, "TOO MANY VALUES (EXPECTED %d)", levels);
2949 return;
2952 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
2953 vtxTableConfigMutable()->powerValues[i] = power[i];
2956 } else if (strcasecmp(tok, "powerlabels") == 0) {
2957 // Power labels
2958 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
2959 int levels = vtxTableConfigMutable()->powerLevels;
2960 int count;
2961 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2962 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
2963 for (unsigned i = 0; i < strlen(label[count]); i++) {
2964 label[count][i] = toupper(label[count][i]);
2968 // Check remaining tokens
2970 if (count < levels) {
2971 cliPrintErrorLinef(cmdName, "NOT ENOUGH LABELS (EXPECTED %d)", levels);
2972 return;
2973 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2974 cliPrintErrorLinef(cmdName, "TOO MANY LABELS (EXPECTED %d)", levels);
2975 return;
2978 for (int i = 0; i < count; i++) {
2979 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
2981 } else if (strcasecmp(tok, "band") == 0) {
2983 int bands = vtxTableConfigMutable()->bands;
2985 tok = strtok_r(NULL, " ", &saveptr);
2986 if (!tok) {
2987 return;
2990 int band = atoi(tok);
2991 --band;
2993 if (band < 0 || band >= bands) {
2994 cliShowArgumentRangeError(cmdName, "BAND NUMBER", 1, bands);
2995 return;
2998 // Band name
2999 tok = strtok_r(NULL, " ", &saveptr);
3001 if (!tok) {
3002 return;
3005 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
3006 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
3007 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
3008 for (unsigned i = 0; i < strlen(bandname); i++) {
3009 bandname[i] = toupper(bandname[i]);
3012 // Band letter
3013 tok = strtok_r(NULL, " ", &saveptr);
3015 if (!tok) {
3016 return;
3019 char bandletter = toupper(tok[0]);
3021 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
3022 int channel = 0;
3023 int channels = vtxTableConfigMutable()->channels;
3024 bool isFactory = false;
3026 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
3027 if (channel == 0 && !isdigit(tok[0])) {
3028 channel -= 1;
3029 if (strcasecmp(tok, "FACTORY") == 0) {
3030 isFactory = true;
3031 } else if (strcasecmp(tok, "CUSTOM") == 0) {
3032 isFactory = false;
3033 } else {
3034 cliPrintErrorLinef(cmdName, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
3035 return;
3038 int freq = atoi(tok);
3039 if (freq < 0) {
3040 cliPrintErrorLinef(cmdName, "INVALID FREQUENCY %s", tok);
3041 return;
3043 bandfreq[channel] = freq;
3046 if (channel < channels) {
3047 cliPrintErrorLinef(cmdName, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
3048 return;
3049 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3050 cliPrintErrorLinef(cmdName, "TOO MANY FREQUENCIES (EXPECTED %d)", channels);
3051 return;
3054 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
3055 vtxTableConfigMutable()->bandLetters[band] = bandletter;
3057 for (int i = 0; i < channel; i++) {
3058 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
3060 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
3061 } else {
3062 // Bad subcommand
3063 cliPrintErrorLinef(cmdName, "INVALID SUBCOMMAND %s", tok);
3067 static void cliVtxInfo(const char *cmdName, char *cmdline)
3069 UNUSED(cmdline);
3071 // Display the available power levels
3072 uint16_t levels[VTX_TABLE_MAX_POWER_LEVELS];
3073 uint16_t powers[VTX_TABLE_MAX_POWER_LEVELS];
3074 vtxDevice_t *vtxDevice = vtxCommonDevice();
3075 if (vtxDevice) {
3076 uint8_t level_count = vtxCommonGetVTXPowerLevels(vtxDevice, levels, powers);
3078 if (level_count) {
3079 for (int i = 0; i < level_count; i++) {
3080 cliPrintLinef("level %d dBm, power %d mW", levels[i], powers[i]);
3082 } else {
3083 cliPrintErrorLinef(cmdName, "NO POWER VALUES DEFINED");
3085 } else {
3086 cliPrintErrorLinef(cmdName, "NO VTX");
3089 #endif // USE_VTX_TABLE
3091 static void printName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
3093 const bool equalsDefault = strlen(pilotConfig->name) == 0;
3094 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->name);
3097 #if defined(USE_BOARD_INFO)
3099 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
3101 static void printBoardName(dumpFlags_t dumpMask)
3103 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
3104 cliPrintLinef("board_name %s", getBoardName());
3108 static void cliBoardName(const char *cmdName, char *cmdline)
3110 const unsigned int len = strlen(cmdline);
3111 const char *boardName = getBoardName();
3112 if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
3113 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "BOARD_NAME", boardName);
3114 } else {
3115 if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
3116 boardInformationUpdated = true;
3118 cliPrintHashLine("Set board_name.");
3120 printBoardName(DUMP_ALL);
3124 static void printManufacturerId(dumpFlags_t dumpMask)
3126 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
3127 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3131 static void cliManufacturerId(const char *cmdName, char *cmdline)
3133 const unsigned int len = strlen(cmdline);
3134 const char *manufacturerId = getManufacturerId();
3135 if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
3136 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
3137 } else {
3138 if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
3139 boardInformationUpdated = true;
3141 cliPrintHashLine("Set manufacturer_id.");
3143 printManufacturerId(DUMP_ALL);
3147 #if defined(USE_SIGNATURE)
3148 static void writeSignature(char *signatureStr, uint8_t *signature)
3150 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3151 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
3155 static void cliSignature(const char *cmdName, char *cmdline)
3157 const int len = strlen(cmdline);
3159 uint8_t signature[SIGNATURE_LENGTH] = {0};
3160 if (len > 0) {
3161 if (len != 2 * SIGNATURE_LENGTH) {
3162 cliPrintErrorLinef(cmdName, "INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
3164 return;
3167 #define BLOCK_SIZE 2
3168 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3169 char temp[BLOCK_SIZE + 1];
3170 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3171 temp[BLOCK_SIZE] = '\0';
3172 char *end;
3173 unsigned result = strtoul(temp, &end, 16);
3174 if (end == &temp[BLOCK_SIZE]) {
3175 signature[i] = result;
3176 } else {
3177 cliPrintErrorLinef(cmdName, "INVALID CHARACTER FOUND: %c", end[0]);
3179 return;
3182 #undef BLOCK_SIZE
3185 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3186 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3187 writeSignature(signatureStr, getSignature());
3188 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "SIGNATURE", signatureStr);
3189 } else {
3190 if (len > 0 && !configIsInCopy && setSignature(signature)) {
3191 signatureUpdated = true;
3193 writeSignature(signatureStr, getSignature());
3195 cliPrintHashLine("Set signature.");
3196 } else if (signatureUpdated || signatureIsSet()) {
3197 writeSignature(signatureStr, getSignature());
3200 cliPrintLinef("signature %s", signatureStr);
3203 #endif
3205 #undef ERROR_MESSAGE
3207 #endif // USE_BOARD_INFO
3209 static void cliMcuId(const char *cmdName, char *cmdline)
3211 UNUSED(cmdName);
3212 UNUSED(cmdline);
3214 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3217 static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
3219 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3220 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
3221 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3222 const char *format = "feature -%s";
3223 const bool equalsDefault = (~defaultMask | mask) & (1 << i);
3224 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3225 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
3226 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3229 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
3230 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3231 const char *format = "feature %s";
3232 if (defaultMask & (1 << i)) {
3233 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
3235 if (mask & (1 << i)) {
3236 const bool equalsDefault = (defaultMask | ~mask) & (1 << i);
3237 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3238 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3244 static void cliFeature(const char *cmdName, char *cmdline)
3246 uint32_t len = strlen(cmdline);
3247 const uint32_t mask = featureConfig()->enabledFeatures;
3248 if (len == 0) {
3249 cliPrint("Enabled: ");
3250 for (uint32_t i = 0; ; i++) {
3251 if (featureNames[i] == NULL) {
3252 break;
3254 if (mask & (1 << i)) {
3255 cliPrintf("%s ", featureNames[i]);
3258 cliPrintLinefeed();
3259 } else if (strncasecmp(cmdline, "list", len) == 0) {
3260 cliPrint("Available:");
3261 for (uint32_t i = 0; ; i++) {
3262 if (featureNames[i] == NULL)
3263 break;
3264 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
3265 cliPrintf(" %s", featureNames[i]);
3267 cliPrintLinefeed();
3268 return;
3269 } else {
3270 uint32_t feature;
3272 bool remove = false;
3273 if (cmdline[0] == '-') {
3274 // remove feature
3275 remove = true;
3276 cmdline++; // skip over -
3277 len--;
3280 for (uint32_t i = 0; ; i++) {
3281 if (featureNames[i] == NULL) {
3282 cliPrintErrorLinef(cmdName, "INVALID NAME");
3283 break;
3286 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
3287 feature = 1 << i;
3288 #ifndef USE_GPS
3289 if (feature & FEATURE_GPS) {
3290 cliPrintLine("unavailable");
3291 break;
3293 #endif
3294 #ifndef USE_RANGEFINDER
3295 if (feature & FEATURE_RANGEFINDER) {
3296 cliPrintLine("unavailable");
3297 break;
3299 #endif
3300 if (remove) {
3301 featureConfigClear(feature);
3302 cliPrint("Disabled");
3303 } else {
3304 featureConfigSet(feature);
3305 cliPrint("Enabled");
3307 cliPrintLinef(" %s", featureNames[i]);
3308 break;
3314 #if defined(USE_BEEPER)
3315 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3317 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3318 const uint8_t beeperCount = beeperTableEntryCount();
3319 for (int32_t i = 0; i < beeperCount - 1; i++) {
3320 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3321 const char *formatOff = "%s -%s";
3322 const char *formatOn = "%s %s";
3323 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3324 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3325 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3326 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3327 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3332 static void processBeeperCommand(const char *cmdName, char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3334 uint32_t len = strlen(cmdline);
3335 uint8_t beeperCount = beeperTableEntryCount();
3337 if (len == 0) {
3338 cliPrintf("Disabled:");
3339 for (int32_t i = 0; ; i++) {
3340 if (i == beeperCount - 1) {
3341 if (*offFlags == 0)
3342 cliPrint(" none");
3343 break;
3346 if (beeperModeMaskForTableIndex(i) & *offFlags)
3347 cliPrintf(" %s", beeperNameForTableIndex(i));
3349 cliPrintLinefeed();
3350 } else if (strncasecmp(cmdline, "list", len) == 0) {
3351 cliPrint("Available:");
3352 for (uint32_t i = 0; i < beeperCount; i++) {
3353 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3354 cliPrintf(" %s", beeperNameForTableIndex(i));
3357 cliPrintLinefeed();
3358 } else {
3359 bool remove = false;
3360 if (cmdline[0] == '-') {
3361 remove = true; // this is for beeper OFF condition
3362 cmdline++;
3363 len--;
3366 for (uint32_t i = 0; ; i++) {
3367 if (i == beeperCount) {
3368 cliPrintErrorLinef(cmdName, "INVALID NAME");
3369 break;
3371 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3372 if (remove) { // beeper off
3373 if (i == BEEPER_ALL - 1) {
3374 *offFlags = allowedFlags;
3375 } else {
3376 *offFlags |= beeperModeMaskForTableIndex(i);
3378 cliPrint("Disabled");
3380 else { // beeper on
3381 if (i == BEEPER_ALL - 1) {
3382 *offFlags = 0;
3383 } else {
3384 *offFlags &= ~beeperModeMaskForTableIndex(i);
3386 cliPrint("Enabled");
3388 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3389 break;
3395 #if defined(USE_DSHOT)
3396 static void cliBeacon(const char *cmdName, char *cmdline)
3398 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3400 #endif
3402 static void cliBeeper(const char *cmdName, char *cmdline)
3404 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3406 #endif
3408 #if defined(USE_RX_BIND)
3409 static void cliRxBind(const char *cmdName, char *cmdline)
3411 UNUSED(cmdline);
3412 if (!startRxBind()) {
3413 cliPrintErrorLinef(cmdName, "Not supported.");
3414 } else {
3415 cliPrintLinef("Binding...");
3418 #endif
3420 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3422 bool equalsDefault = true;
3423 char buf[16];
3424 char bufDefault[16];
3425 uint32_t i;
3427 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3428 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3429 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3430 if (defaultRxConfig) {
3431 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3432 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3435 buf[i] = '\0';
3437 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3438 const char *formatMap = "map %s";
3439 if (defaultRxConfig) {
3440 bufDefault[i] = '\0';
3441 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3443 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3447 static void cliMap(const char *cmdName, char *cmdline)
3449 uint32_t i;
3450 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3452 uint32_t len = strlen(cmdline);
3453 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3455 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3456 buf[i] = toupper((unsigned char)cmdline[i]);
3458 buf[i] = '\0';
3460 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3461 buf[i] = toupper((unsigned char)cmdline[i]);
3463 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3464 continue;
3466 cliShowParseError(cmdName);
3467 return;
3469 parseRcChannels(buf, rxConfigMutable());
3470 } else if (len > 0) {
3471 cliShowInvalidArgumentCountError(cmdName);
3472 return;
3475 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3476 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3479 buf[i] = '\0';
3480 cliPrintLinef("map %s", buf);
3483 static char *skipSpace(char *buffer)
3485 while (*(buffer) == ' ') {
3486 buffer++;
3489 return buffer;
3492 static char *checkCommand(char *cmdline, const char *command)
3494 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3495 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3496 return skipSpace(cmdline + strlen(command) + 1);
3497 } else {
3498 return 0;
3502 static void cliRebootEx(rebootTarget_e rebootTarget)
3504 cliPrint("\r\nRebooting");
3505 cliWriterFlush();
3506 waitForSerialPortToFinishTransmitting(cliPort);
3507 motorShutdown();
3509 switch (rebootTarget) {
3510 case REBOOT_TARGET_BOOTLOADER_ROM:
3511 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3513 break;
3514 #if defined(USE_FLASH_BOOT_LOADER)
3515 case REBOOT_TARGET_BOOTLOADER_FLASH:
3516 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3518 break;
3519 #endif
3520 case REBOOT_TARGET_FIRMWARE:
3521 default:
3522 systemReset();
3524 break;
3528 static void cliReboot(void)
3530 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3533 static void cliBootloader(const char *cmdName, char *cmdline)
3535 rebootTarget_e rebootTarget;
3536 if (
3537 #if !defined(USE_FLASH_BOOT_LOADER)
3538 isEmpty(cmdline) ||
3539 #endif
3540 strncasecmp(cmdline, "rom", 3) == 0) {
3541 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3543 cliPrintHashLine("restarting in ROM bootloader mode");
3544 #if defined(USE_FLASH_BOOT_LOADER)
3545 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3546 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3548 cliPrintHashLine("restarting in flash bootloader mode");
3549 #endif
3550 } else {
3551 cliPrintErrorLinef(cmdName, "Invalid option");
3553 return;
3556 cliRebootEx(rebootTarget);
3559 static void cliExit(const char *cmdName, char *cmdline)
3561 UNUSED(cmdName);
3562 UNUSED(cmdline);
3564 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3565 cliWriterFlush();
3567 *cliBuffer = '\0';
3568 bufferIndex = 0;
3569 cliMode = false;
3570 // incase a motor was left running during motortest, clear it here
3571 mixerResetDisarmedMotors();
3572 cliReboot();
3575 #ifdef USE_GPS
3576 static void cliGpsPassthrough(const char *cmdName, char *cmdline)
3578 UNUSED(cmdName);
3579 UNUSED(cmdline);
3581 gpsEnablePassthrough(cliPort);
3583 #endif
3585 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3586 static void cliPrintGyroRegisters(uint8_t whichSensor)
3588 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3589 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3590 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3593 static void cliDumpGyroRegisters(const char *cmdName, char *cmdline)
3595 UNUSED(cmdName);
3596 UNUSED(cmdline);
3598 #ifdef USE_MULTI_GYRO
3599 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3600 cliPrintLinef("\r\n# Gyro 1");
3601 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3603 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3604 cliPrintLinef("\r\n# Gyro 2");
3605 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3607 #else
3608 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3609 #endif
3611 #endif
3614 static int parseOutputIndex(const char *cmdName, char *pch, bool allowAllEscs) {
3615 int outputIndex = atoi(pch);
3616 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3617 cliPrintLinef("Using output %d.", outputIndex);
3618 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3619 cliPrintLinef("Using all outputs.");
3620 } else {
3621 cliPrintErrorLinef(cmdName, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3623 return -1;
3626 return outputIndex;
3629 #if defined(USE_DSHOT)
3630 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3632 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3633 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3634 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3636 enum {
3637 ESC_INFO_KISS_V1,
3638 ESC_INFO_KISS_V2,
3639 ESC_INFO_BLHELI32
3642 #define ESC_INFO_VERSION_POSITION 12
3644 static void printEscInfo(const char *cmdName, const uint8_t *escInfoBuffer, uint8_t bytesRead)
3646 bool escInfoReceived = false;
3647 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3648 uint8_t escInfoVersion;
3649 uint8_t frameLength;
3650 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3651 escInfoVersion = ESC_INFO_BLHELI32;
3652 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3653 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3654 escInfoVersion = ESC_INFO_KISS_V2;
3655 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3656 } else {
3657 escInfoVersion = ESC_INFO_KISS_V1;
3658 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3661 if (bytesRead == frameLength) {
3662 escInfoReceived = true;
3664 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3665 uint8_t firmwareVersion = 0;
3666 uint8_t firmwareSubVersion = 0;
3667 uint8_t escType = 0;
3668 switch (escInfoVersion) {
3669 case ESC_INFO_KISS_V1:
3670 firmwareVersion = escInfoBuffer[12];
3671 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3672 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3674 break;
3675 case ESC_INFO_KISS_V2:
3676 firmwareVersion = escInfoBuffer[13];
3677 firmwareSubVersion = escInfoBuffer[14];
3678 escType = escInfoBuffer[15];
3680 break;
3681 case ESC_INFO_BLHELI32:
3682 firmwareVersion = escInfoBuffer[13];
3683 firmwareSubVersion = escInfoBuffer[14];
3684 escType = escInfoBuffer[15];
3686 break;
3689 cliPrint("ESC Type: ");
3690 switch (escInfoVersion) {
3691 case ESC_INFO_KISS_V1:
3692 case ESC_INFO_KISS_V2:
3693 switch (escType) {
3694 case 1:
3695 cliPrintLine("KISS8A");
3697 break;
3698 case 2:
3699 cliPrintLine("KISS16A");
3701 break;
3702 case 3:
3703 cliPrintLine("KISS24A");
3705 break;
3706 case 5:
3707 cliPrintLine("KISS Ultralite");
3709 break;
3710 default:
3711 cliPrintLine("unknown");
3713 break;
3716 break;
3717 case ESC_INFO_BLHELI32:
3719 char *escType = (char *)(escInfoBuffer + 31);
3720 escType[32] = 0;
3721 cliPrintLine(escType);
3724 break;
3727 cliPrint("MCU Serial No: 0x");
3728 for (int i = 0; i < 12; i++) {
3729 if (i && (i % 3 == 0)) {
3730 cliPrint("-");
3732 cliPrintf("%02x", escInfoBuffer[i]);
3734 cliPrintLinefeed();
3736 switch (escInfoVersion) {
3737 case ESC_INFO_KISS_V1:
3738 case ESC_INFO_KISS_V2:
3739 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3741 break;
3742 case ESC_INFO_BLHELI32:
3743 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3745 break;
3747 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3748 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3749 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3750 if (escInfoVersion == ESC_INFO_BLHELI32) {
3751 uint8_t setting = escInfoBuffer[18];
3752 cliPrint("Low voltage Limit: ");
3753 switch (setting) {
3754 case 0:
3755 cliPrintLine("off");
3757 break;
3758 case 255:
3759 cliPrintLine("unsupported");
3761 break;
3762 default:
3763 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3765 break;
3768 setting = escInfoBuffer[19];
3769 cliPrint("Current Limit: ");
3770 switch (setting) {
3771 case 0:
3772 cliPrintLine("off");
3774 break;
3775 case 255:
3776 cliPrintLine("unsupported");
3778 break;
3779 default:
3780 cliPrintLinef("%d", setting);
3782 break;
3785 for (int i = 0; i < 4; i++) {
3786 setting = escInfoBuffer[i + 20];
3787 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3791 } else {
3792 cliPrintErrorLinef(cmdName, "CHECKSUM ERROR.");
3797 if (!escInfoReceived) {
3798 cliPrintLine("No Info.");
3802 static void executeEscInfoCommand(const char *cmdName, uint8_t escIndex)
3804 cliPrintLinef("Info for ESC %d:", escIndex);
3806 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3808 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3810 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, DSHOT_CMD_TYPE_BLOCKING);
3812 delay(10);
3814 printEscInfo(cmdName, escInfoBuffer, getNumberEscBytesRead());
3816 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3818 static void cliDshotProg(const char *cmdName, char *cmdline)
3820 if (isEmpty(cmdline) || !isMotorProtocolDshot()) {
3821 cliShowParseError(cmdName);
3823 return;
3826 char *saveptr;
3827 char *pch = strtok_r(cmdline, " ", &saveptr);
3828 int pos = 0;
3829 int escIndex = 0;
3830 bool firstCommand = true;
3831 while (pch != NULL) {
3832 switch (pos) {
3833 case 0:
3834 escIndex = parseOutputIndex(cmdName, pch, true);
3835 if (escIndex == -1) {
3836 return;
3839 break;
3840 default:
3842 int command = atoi(pch);
3843 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3844 if (firstCommand) {
3845 // pwmDisableMotors();
3846 motorDisable();
3848 if (command == DSHOT_CMD_ESC_INFO) {
3849 delay(5); // Wait for potential ESC telemetry transmission to finish
3850 } else {
3851 delay(1);
3854 firstCommand = false;
3857 if (command != DSHOT_CMD_ESC_INFO) {
3858 dshotCommandWrite(escIndex, getMotorCount(), command, DSHOT_CMD_TYPE_BLOCKING);
3859 } else {
3860 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3861 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3862 if (escIndex != ALL_MOTORS) {
3863 executeEscInfoCommand(cmdName, escIndex);
3864 } else {
3865 for (uint8_t i = 0; i < getMotorCount(); i++) {
3866 executeEscInfoCommand(cmdName, i);
3869 } else
3870 #endif
3872 cliPrintLine("Not supported.");
3876 cliPrintLinef("Command Sent: %d", command);
3878 } else {
3879 cliPrintErrorLinef(cmdName, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3883 break;
3886 pos++;
3887 pch = strtok_r(NULL, " ", &saveptr);
3890 motorEnable();
3892 #endif // USE_DSHOT
3894 #ifdef USE_ESCSERIAL
3895 static void cliEscPassthrough(const char *cmdName, char *cmdline)
3897 if (isEmpty(cmdline)) {
3898 cliShowInvalidArgumentCountError(cmdName);
3900 return;
3903 char *saveptr;
3904 char *pch = strtok_r(cmdline, " ", &saveptr);
3905 int pos = 0;
3906 uint8_t mode = 0;
3907 int escIndex = 0;
3908 while (pch != NULL) {
3909 switch (pos) {
3910 case 0:
3911 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3912 mode = PROTOCOL_SIMONK;
3913 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3914 mode = PROTOCOL_BLHELI;
3915 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3916 mode = PROTOCOL_KISS;
3917 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3918 mode = PROTOCOL_KISSALL;
3919 } else {
3920 cliShowParseError(cmdName);
3922 return;
3924 break;
3925 case 1:
3926 escIndex = parseOutputIndex(cmdName, pch, mode == PROTOCOL_KISS);
3927 if (escIndex == -1) {
3928 return;
3931 break;
3932 default:
3933 cliShowInvalidArgumentCountError(cmdName);
3935 return;
3937 break;
3940 pos++;
3941 pch = strtok_r(NULL, " ", &saveptr);
3944 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
3945 cliPrintErrorLinef(cmdName, "Error starting ESC connection");
3948 #endif
3950 #ifndef USE_QUAD_MIXER_ONLY
3951 static void cliMixer(const char *cmdName, char *cmdline)
3953 int len;
3955 len = strlen(cmdline);
3957 if (len == 0) {
3958 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3959 return;
3960 } else if (strncasecmp(cmdline, "list", len) == 0) {
3961 cliPrint("Available:");
3962 for (uint32_t i = 0; ; i++) {
3963 if (mixerNames[i] == NULL)
3964 break;
3965 cliPrintf(" %s", mixerNames[i]);
3967 cliPrintLinefeed();
3968 return;
3971 for (uint32_t i = 0; ; i++) {
3972 if (mixerNames[i] == NULL) {
3973 cliPrintErrorLinef(cmdName, "INVALID NAME");
3974 return;
3976 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
3977 mixerConfigMutable()->mixerMode = i + 1;
3978 break;
3982 cliMixer(cmdName, "");
3984 #endif
3986 static void cliMotor(const char *cmdName, char *cmdline)
3988 if (isEmpty(cmdline)) {
3989 cliShowInvalidArgumentCountError(cmdName);
3991 return;
3994 int motorIndex = 0;
3995 int motorValue = 0;
3997 char *saveptr;
3998 char *pch = strtok_r(cmdline, " ", &saveptr);
3999 int index = 0;
4000 while (pch != NULL) {
4001 switch (index) {
4002 case 0:
4003 motorIndex = parseOutputIndex(cmdName, pch, true);
4004 if (motorIndex == -1) {
4005 return;
4008 break;
4009 case 1:
4010 motorValue = atoi(pch);
4012 break;
4014 index++;
4015 pch = strtok_r(NULL, " ", &saveptr);
4018 if (index == 2) {
4019 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
4020 cliShowArgumentRangeError(cmdName, "VALUE", 1000, 2000);
4021 } else {
4022 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
4024 if (motorIndex != ALL_MOTORS) {
4025 motor_disarmed[motorIndex] = motorOutputValue;
4027 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
4028 } else {
4029 for (int i = 0; i < getMotorCount(); i++) {
4030 motor_disarmed[i] = motorOutputValue;
4033 cliPrintLinef("all motors: %d", motorOutputValue);
4036 } else {
4037 cliShowInvalidArgumentCountError(cmdName);
4041 #ifndef MINIMAL_CLI
4042 static void cliPlaySound(const char *cmdName, char *cmdline)
4044 int i;
4045 const char *name;
4046 static int lastSoundIdx = -1;
4048 if (isEmpty(cmdline)) {
4049 i = lastSoundIdx + 1; //next sound index
4050 if ((name=beeperNameForTableIndex(i)) == NULL) {
4051 while (true) { //no name for index; try next one
4052 if (++i >= beeperTableEntryCount())
4053 i = 0; //if end then wrap around to first entry
4054 if ((name=beeperNameForTableIndex(i)) != NULL)
4055 break; //if name OK then play sound below
4056 if (i == lastSoundIdx + 1) { //prevent infinite loop
4057 cliPrintErrorLinef(cmdName, "ERROR PLAYING SOUND");
4058 return;
4062 } else { //index value was given
4063 i = atoi(cmdline);
4064 if ((name=beeperNameForTableIndex(i)) == NULL) {
4065 cliPrintLinef("No sound for index %d", i);
4066 return;
4069 lastSoundIdx = i;
4070 beeperSilence();
4071 cliPrintLinef("Playing sound %d: %s", i, name);
4072 beeper(beeperModeForTableIndex(i));
4074 #endif
4076 static void cliProfile(const char *cmdName, char *cmdline)
4078 if (isEmpty(cmdline)) {
4079 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4080 return;
4081 } else {
4082 const int i = atoi(cmdline);
4083 if (i >= 0 && i < PID_PROFILE_COUNT) {
4084 changePidProfile(i);
4085 cliProfile(cmdName, "");
4086 } else {
4087 cliPrintErrorLinef(cmdName, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
4092 static void cliRateProfile(const char *cmdName, char *cmdline)
4094 if (isEmpty(cmdline)) {
4095 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4096 return;
4097 } else {
4098 const int i = atoi(cmdline);
4099 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
4100 changeControlRateProfile(i);
4101 cliRateProfile(cmdName, "");
4102 } else {
4103 cliPrintErrorLinef(cmdName, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
4108 static void cliDumpPidProfile(const char *cmdName, uint8_t pidProfileIndex, dumpFlags_t dumpMask)
4110 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4111 // Faulty values
4112 return;
4115 pidProfileIndexToUse = pidProfileIndex;
4117 cliPrintLinefeed();
4118 cliProfile(cmdName, "");
4120 char profileStr[10];
4121 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4122 dumpAllValues(cmdName, PROFILE_VALUE, dumpMask, profileStr);
4124 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4127 static void cliDumpRateProfile(const char *cmdName, uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4129 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4130 // Faulty values
4131 return;
4134 rateProfileIndexToUse = rateProfileIndex;
4136 cliPrintLinefeed();
4137 cliRateProfile(cmdName, "");
4139 char rateProfileStr[14];
4140 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4141 dumpAllValues(cmdName, PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4143 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4146 #ifdef USE_CLI_BATCH
4147 static void cliPrintCommandBatchWarning(const char *cmdName, const char *warning)
4149 cliPrintErrorLinef(cmdName, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4150 if (warning) {
4151 cliPrintErrorLinef(cmdName, warning);
4155 static void resetCommandBatch(void)
4157 commandBatchActive = false;
4158 commandBatchError = false;
4161 static void cliBatch(const char *cmdName, char *cmdline)
4163 if (strncasecmp(cmdline, "start", 5) == 0) {
4164 if (!commandBatchActive) {
4165 commandBatchActive = true;
4166 commandBatchError = false;
4168 cliPrintLine("Command batch started");
4169 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4170 if (commandBatchActive && commandBatchError) {
4171 cliPrintCommandBatchWarning(cmdName, NULL);
4172 } else {
4173 cliPrintLine("Command batch ended");
4175 resetCommandBatch();
4176 } else {
4177 cliPrintErrorLinef(cmdName, "Invalid option");
4180 #endif
4182 static bool prepareSave(void)
4184 #if defined(USE_CUSTOM_DEFAULTS)
4185 if (processingCustomDefaults) {
4186 return true;
4188 #endif
4190 #ifdef USE_CLI_BATCH
4191 if (commandBatchActive && commandBatchError) {
4192 return false;
4194 #endif
4196 #if defined(USE_BOARD_INFO)
4197 if (boardInformationUpdated) {
4198 persistBoardInformation();
4200 #if defined(USE_SIGNATURE)
4201 if (signatureUpdated) {
4202 persistSignature();
4204 #endif
4205 #endif // USE_BOARD_INFO
4207 return true;
4210 bool tryPrepareSave(const char *cmdName)
4212 bool success = prepareSave();
4213 #if defined(USE_CLI_BATCH)
4214 if (!success) {
4215 cliPrintCommandBatchWarning(cmdName, "PLEASE FIX ERRORS THEN 'SAVE'");
4216 resetCommandBatch();
4218 return false;
4220 #else
4221 UNUSED(cmdName);
4222 UNUSED(success);
4223 #endif
4225 return true;
4228 static void cliSave(const char *cmdName, char *cmdline)
4230 UNUSED(cmdline);
4232 if (tryPrepareSave(cmdName)) {
4233 writeEEPROM();
4234 cliPrintHashLine("saving");
4236 cliReboot();
4240 #if defined(USE_CUSTOM_DEFAULTS)
4241 bool resetConfigToCustomDefaults(void)
4243 resetConfig();
4245 #ifdef USE_CLI_BATCH
4246 commandBatchError = false;
4247 #endif
4249 cliProcessCustomDefaults(true);
4251 return prepareSave();
4254 static bool customDefaultsHasNext(const char *customDefaultsPtr)
4256 return *customDefaultsPtr && *customDefaultsPtr != 0xFF && customDefaultsPtr < customDefaultsEnd;
4259 static const char *parseCustomDefaultsHeaderElement(char *dest, const char *customDefaultsPtr, const char *prefix, const char terminator, const unsigned maxLength)
4261 char *endPtr = NULL;
4262 unsigned len = strlen(prefix);
4263 if (customDefaultsPtr && customDefaultsHasNext(customDefaultsPtr) && strncmp(customDefaultsPtr, prefix, len) == 0) {
4264 customDefaultsPtr += len;
4265 endPtr = strchr(customDefaultsPtr, terminator);
4268 if (endPtr && customDefaultsHasNext(endPtr)) {
4269 len = endPtr - customDefaultsPtr;
4270 memcpy(dest, customDefaultsPtr, MIN(len, maxLength));
4272 customDefaultsPtr += len;
4274 return customDefaultsPtr;
4277 return NULL;
4280 static void parseCustomDefaultsHeader(void)
4282 const char *customDefaultsPtr = customDefaultsStart;
4283 if (strncmp(customDefaultsPtr, CUSTOM_DEFAULTS_START_PREFIX, strlen(CUSTOM_DEFAULTS_START_PREFIX)) == 0) {
4284 customDefaultsFound = true;
4286 customDefaultsPtr = strchr(customDefaultsPtr, '\n');
4287 if (customDefaultsPtr && customDefaultsHasNext(customDefaultsPtr)) {
4288 customDefaultsPtr++;
4291 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsManufacturerId, customDefaultsPtr, CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX, CUSTOM_DEFAULTS_BOARD_NAME_PREFIX[0], MAX_MANUFACTURER_ID_LENGTH);
4293 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsBoardName, customDefaultsPtr, CUSTOM_DEFAULTS_BOARD_NAME_PREFIX, CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX[0], MAX_BOARD_NAME_LENGTH);
4295 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsChangesetId, customDefaultsPtr, CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX, CUSTOM_DEFAULTS_DATE_PREFIX[0], MAX_CHANGESET_ID_LENGTH);
4297 customDefaultsPtr = parseCustomDefaultsHeaderElement(customDefaultsDate, customDefaultsPtr, CUSTOM_DEFAULTS_DATE_PREFIX, '\n', MAX_DATE_LENGTH);
4300 customDefaultsHeaderParsed = true;
4303 bool hasCustomDefaults(void)
4305 if (!customDefaultsHeaderParsed) {
4306 parseCustomDefaultsHeader();
4309 return customDefaultsFound;
4311 #endif
4313 static void cliDefaults(const char *cmdName, char *cmdline)
4315 bool saveConfigs = true;
4316 #if defined(USE_CUSTOM_DEFAULTS)
4317 bool useCustomDefaults = true;
4318 #elif defined(USE_CUSTOM_DEFAULTS_ADDRESS)
4319 // Required to keep the linker from eliminating these
4320 if (customDefaultsStart != customDefaultsEnd) {
4321 delay(0);
4323 #endif
4325 if (isEmpty(cmdline)) {
4326 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
4327 saveConfigs = false;
4328 #if defined(USE_CUSTOM_DEFAULTS)
4329 } else if (strncasecmp(cmdline, "bare", 4) == 0) {
4330 useCustomDefaults = false;
4331 } else if (strncasecmp(cmdline, "show", 4) == 0) {
4332 if (hasCustomDefaults()) {
4333 char *customDefaultsPtr = customDefaultsStart;
4334 while (customDefaultsHasNext(customDefaultsPtr)) {
4335 if (*customDefaultsPtr != '\n') {
4336 cliPrintf("%c", *customDefaultsPtr++);
4337 } else {
4338 cliPrintLinefeed();
4339 customDefaultsPtr++;
4342 } else {
4343 cliPrintError(cmdName, "NO CUSTOM DEFAULTS FOUND");
4346 return;
4347 #endif
4348 } else {
4349 cliPrintError(cmdName, "INVALID OPTION");
4351 return;
4354 cliPrintHashLine("resetting to defaults");
4356 resetConfig();
4358 #ifdef USE_CLI_BATCH
4359 // Reset only the error state and allow the batch active state to remain.
4360 // This way if a "defaults nosave" was issued after the "batch on" we'll
4361 // only reset the current error state but the batch will still be active
4362 // for subsequent commands.
4363 commandBatchError = false;
4364 #endif
4366 #if defined(USE_CUSTOM_DEFAULTS)
4367 if (useCustomDefaults) {
4368 cliProcessCustomDefaults(false);
4370 #endif
4372 if (saveConfigs && tryPrepareSave(cmdName)) {
4373 writeUnmodifiedConfigToEEPROM();
4375 cliReboot();
4379 static void cliPrintVarDefault(const char *cmdName, const clivalue_t *value)
4381 const pgRegistry_t *pg = pgFind(value->pgn);
4382 if (pg) {
4383 const char *defaultFormat = "Default value: ";
4384 const int valueOffset = getValueOffset(value);
4385 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
4386 if (!equalsDefault) {
4387 cliPrintf(defaultFormat, value->name);
4388 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
4389 cliPrintLinefeed();
4394 STATIC_UNIT_TESTED void cliGet(const char *cmdName, char *cmdline)
4396 const clivalue_t *val;
4397 int matchedCommands = 0;
4399 pidProfileIndexToUse = getCurrentPidProfileIndex();
4400 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4402 backupAndResetConfigs(true);
4404 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4405 if (strcasestr(valueTable[i].name, cmdline)) {
4406 val = &valueTable[i];
4407 if (matchedCommands > 0) {
4408 cliPrintLinefeed();
4410 cliPrintf("%s = ", valueTable[i].name);
4411 cliPrintVar(cmdName, val, 0);
4412 cliPrintLinefeed();
4413 switch (val->type & VALUE_SECTION_MASK) {
4414 case PROFILE_VALUE:
4415 cliProfile(cmdName, "");
4417 break;
4418 case PROFILE_RATE_VALUE:
4419 cliRateProfile(cmdName, "");
4421 break;
4422 default:
4424 break;
4426 cliPrintVarRange(val);
4427 cliPrintVarDefault(cmdName, val);
4429 matchedCommands++;
4433 restoreConfigs();
4435 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4436 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4438 if (!matchedCommands) {
4439 cliPrintErrorLinef(cmdName, "INVALID NAME");
4443 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
4445 while (*(bufEnd - 1) == ' ') {
4446 bufEnd--;
4449 return bufEnd - bufBegin;
4452 uint16_t cliGetSettingIndex(char *name, uint8_t length)
4454 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4455 const char *settingName = valueTable[i].name;
4457 // ensure exact match when setting to prevent setting variables with shorter names
4458 if (strncasecmp(name, settingName, strlen(settingName)) == 0 && length == strlen(settingName)) {
4459 return i;
4462 return valueTableEntryCount;
4465 STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
4467 const uint32_t len = strlen(cmdline);
4468 char *eqptr;
4470 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4471 cliPrintLine("Current settings: ");
4473 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4474 const clivalue_t *val = &valueTable[i];
4475 cliPrintf("%s = ", valueTable[i].name);
4476 cliPrintVar(cmdName, val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4477 cliPrintLinefeed();
4479 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4480 // has equals
4482 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4484 // skip the '=' and any ' ' characters
4485 eqptr++;
4486 eqptr = skipSpace(eqptr);
4488 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4489 if (index >= valueTableEntryCount) {
4490 cliPrintErrorLinef(cmdName, "INVALID NAME");
4491 return;
4493 const clivalue_t *val = &valueTable[index];
4495 bool valueChanged = false;
4496 int16_t value = 0;
4497 switch (val->type & VALUE_MODE_MASK) {
4498 case MODE_DIRECT: {
4499 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4500 uint32_t value = strtoul(eqptr, NULL, 10);
4502 if (value <= val->config.u32Max) {
4503 cliSetVar(val, value);
4504 valueChanged = true;
4506 } else {
4507 int value = atoi(eqptr);
4509 int min;
4510 int max;
4511 getMinMax(val, &min, &max);
4513 if (value >= min && value <= max) {
4514 cliSetVar(val, value);
4515 valueChanged = true;
4520 break;
4521 case MODE_LOOKUP:
4522 case MODE_BITSET: {
4523 int tableIndex;
4524 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4525 tableIndex = TABLE_OFF_ON;
4526 } else {
4527 tableIndex = val->config.lookup.tableIndex;
4529 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4530 bool matched = false;
4531 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4532 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4534 if (matched) {
4535 value = tableValueIndex;
4537 cliSetVar(val, value);
4538 valueChanged = true;
4543 break;
4545 case MODE_ARRAY: {
4546 const uint8_t arrayLength = val->config.array.length;
4547 char *valPtr = eqptr;
4549 int i = 0;
4550 while (i < arrayLength && valPtr != NULL) {
4551 // skip spaces
4552 valPtr = skipSpace(valPtr);
4554 // process substring starting at valPtr
4555 // note: no need to copy substrings for atoi()
4556 // it stops at the first character that cannot be converted...
4557 switch (val->type & VALUE_TYPE_MASK) {
4558 default:
4559 case VAR_UINT8:
4561 // fetch data pointer
4562 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4563 // store value
4564 *data = (uint8_t)atoi((const char*) valPtr);
4567 break;
4568 case VAR_INT8:
4570 // fetch data pointer
4571 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4572 // store value
4573 *data = (int8_t)atoi((const char*) valPtr);
4576 break;
4577 case VAR_UINT16:
4579 // fetch data pointer
4580 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4581 // store value
4582 *data = (uint16_t)atoi((const char*) valPtr);
4585 break;
4586 case VAR_INT16:
4588 // fetch data pointer
4589 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4590 // store value
4591 *data = (int16_t)atoi((const char*) valPtr);
4594 break;
4595 case VAR_UINT32:
4597 // fetch data pointer
4598 uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
4599 // store value
4600 *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
4603 break;
4606 // find next comma (or end of string)
4607 valPtr = strchr(valPtr, ',') + 1;
4609 i++;
4613 // mark as changed
4614 valueChanged = true;
4616 break;
4617 case MODE_STRING: {
4618 char *valPtr = eqptr;
4619 valPtr = skipSpace(valPtr);
4621 const unsigned int len = strlen(valPtr);
4622 const uint8_t min = val->config.string.minlength;
4623 const uint8_t max = val->config.string.maxlength;
4624 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4625 strlen((char *)cliGetValuePointer(val)) == 0 ||
4626 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4628 if (updatable && len > 0 && len <= max) {
4629 memset((char *)cliGetValuePointer(val), 0, max);
4630 if (len >= min && strncmp(valPtr, emptyName, len)) {
4631 strncpy((char *)cliGetValuePointer(val), valPtr, len);
4633 valueChanged = true;
4634 } else {
4635 cliPrintErrorLinef(cmdName, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4638 break;
4641 if (valueChanged) {
4642 cliPrintf("%s set to ", val->name);
4643 cliPrintVar(cmdName, val, 0);
4644 } else {
4645 cliPrintErrorLinef(cmdName, "INVALID VALUE");
4646 cliPrintVarRange(val);
4649 return;
4650 } else {
4651 // no equals, check for matching variables.
4652 cliGet(cmdName, cmdline);
4656 const char *getMcuTypeById(mcuTypeId_e id)
4658 if (id < MCU_TYPE_UNKNOWN) {
4659 return mcuTypeNames[id];
4660 } else {
4661 return "UNKNOWN";
4665 static void cliStatus(const char *cmdName, char *cmdline)
4667 UNUSED(cmdName);
4668 UNUSED(cmdline);
4670 // MCU type, clock, vrefint, core temperature
4672 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock / 1000000));
4674 #ifdef STM32F4
4675 // Only F4 is capable of switching between HSE/HSI (for now)
4676 int sysclkSource = SystemSYSCLKSource();
4678 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4679 const char *PLLSource[] = { "-HSI", "-HSE" };
4681 int pllSource;
4683 if (sysclkSource >= 2) {
4684 pllSource = SystemPLLSource();
4687 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4688 #endif
4690 #ifdef USE_ADC_INTERNAL
4691 uint16_t vrefintMv = getVrefMv();
4692 int16_t coretemp = getCoreTemperatureCelsius();
4693 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4694 #else
4695 cliPrintLinefeed();
4696 #endif
4698 // Stack and config sizes and usages
4700 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4701 #ifdef STACK_CHECK
4702 cliPrintf(", Stack used: %d", stackUsedSize());
4703 #endif
4704 cliPrintLinefeed();
4706 cliPrintLinef("Config size: %d, Max available config: %d", getEEPROMConfigSize(), getEEPROMStorageSize());
4708 // Sensors
4709 cliPrint("Gyros detected:");
4710 bool found = false;
4711 for (unsigned pos = 0; pos < 7; pos++) {
4712 if (gyroConfig()->gyrosDetected & BIT(pos)) {
4713 if (found) {
4714 cliPrint(",");
4715 } else {
4716 found = true;
4718 cliPrintf(" gyro %d", pos + 1);
4721 cliPrintLinefeed();
4723 #if defined(USE_SENSOR_NAMES)
4724 const uint32_t detectedSensorsMask = sensorsMask();
4725 for (uint32_t i = 0; ; i++) {
4726 if (sensorTypeNames[i] == NULL) {
4727 break;
4729 const uint32_t mask = (1 << i);
4730 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4731 const uint8_t sensorHardwareIndex = detectedSensors[i];
4732 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4733 if (i) {
4734 cliPrint(", ");
4736 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4737 #if defined(USE_ACC)
4738 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4739 cliPrintf(".%c", acc.dev.revisionCode);
4741 #endif
4744 cliPrintLinefeed();
4745 #endif /* USE_SENSOR_NAMES */
4747 #if defined(USE_OSD)
4748 osdDisplayPortDevice_e displayPortDeviceType;
4749 osdGetDisplayPort(&displayPortDeviceType);
4751 cliPrintLinef("OSD: %s", lookupTableOsdDisplayPortDevice[displayPortDeviceType]);
4752 #endif
4754 // Uptime and wall clock
4756 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4758 #ifdef USE_RTC_TIME
4759 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4760 dateTime_t dt;
4761 if (rtcGetDateTime(&dt)) {
4762 dateTimeFormatLocal(buf, &dt);
4763 cliPrintf(", Current Time: %s", buf);
4765 #endif
4766 cliPrintLinefeed();
4768 // Run status
4770 const int gyroRate = getTaskDeltaTimeUs(TASK_GYRO) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_GYRO)));
4771 int rxRate = getCurrentRxRefreshRate();
4772 if (rxRate != 0) {
4773 rxRate = (int)(1000000.0f / ((float)rxRate));
4775 const int systemRate = getTaskDeltaTimeUs(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_SYSTEM)));
4776 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4777 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE), getTaskDeltaTimeUs(TASK_GYRO), gyroRate, rxRate, systemRate);
4779 // Battery meter
4781 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4783 // Other devices and status
4785 #ifdef USE_I2C
4786 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4787 #else
4788 const uint16_t i2cErrorCounter = 0;
4789 #endif
4790 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4792 #ifdef USE_SDCARD
4793 cliSdInfo(cmdName, "");
4794 #endif
4796 cliPrint("Arming disable flags:");
4797 armingDisableFlags_e flags = getArmingDisableFlags();
4798 while (flags) {
4799 const int bitpos = ffs(flags) - 1;
4800 flags &= ~(1 << bitpos);
4801 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4803 cliPrintLinefeed();
4806 #if defined(USE_TASK_STATISTICS)
4807 static void cliTasks(const char *cmdName, char *cmdline)
4809 UNUSED(cmdName);
4810 UNUSED(cmdline);
4811 int maxLoadSum = 0;
4812 int averageLoadSum = 0;
4814 #ifndef MINIMAL_CLI
4815 if (systemConfig()->task_statistics) {
4816 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4817 } else {
4818 cliPrintLine("Task list");
4820 #endif
4821 for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4822 taskInfo_t taskInfo;
4823 getTaskInfo(taskId, &taskInfo);
4824 if (taskInfo.isEnabled) {
4825 int taskFrequency = taskInfo.averageDeltaTimeUs == 0 ? 0 : lrintf(1e6f / taskInfo.averageDeltaTimeUs);
4826 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4827 const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 :(taskInfo.maxExecutionTimeUs * taskFrequency + 5000) / 1000;
4828 const int averageLoad = taskInfo.averageExecutionTimeUs == 0 ? 0 : (taskInfo.averageExecutionTimeUs * taskFrequency + 5000) / 1000;
4829 if (taskId != TASK_SERIAL) {
4830 maxLoadSum += maxLoad;
4831 averageLoadSum += averageLoad;
4833 if (systemConfig()->task_statistics) {
4834 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4835 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTimeUs,
4836 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTimeUs / 1000);
4837 } else {
4838 cliPrintLinef("%6d", taskFrequency);
4841 schedulerResetTaskMaxExecutionTime(taskId);
4844 if (systemConfig()->task_statistics) {
4845 cfCheckFuncInfo_t checkFuncInfo;
4846 getCheckFuncInfo(&checkFuncInfo);
4847 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTimeUs, checkFuncInfo.averageExecutionTimeUs, checkFuncInfo.totalExecutionTimeUs / 1000);
4848 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
4849 schedulerResetCheckFunctionMaxExecutionTime();
4852 #endif
4854 static void printVersion(const char *cmdName, bool printBoardInfo)
4856 #if !(defined(USE_CUSTOM_DEFAULTS) && defined(USE_UNIFIED_TARGET))
4857 UNUSED(cmdName);
4858 UNUSED(printBoardInfo);
4859 #endif
4861 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4862 FC_FIRMWARE_NAME,
4863 targetName,
4864 systemConfig()->boardIdentifier,
4865 FC_VERSION_STRING,
4866 buildDate,
4867 buildTime,
4868 shortGitRevision,
4869 MSP_API_VERSION_STRING
4872 #ifdef FEATURE_CUT_LEVEL
4873 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL);
4874 #else
4875 cliPrintLinefeed();
4876 #endif
4878 #if defined(USE_CUSTOM_DEFAULTS)
4879 if (hasCustomDefaults()) {
4880 if (strlen(customDefaultsManufacturerId) || strlen(customDefaultsBoardName) || strlen(customDefaultsChangesetId) || strlen(customDefaultsDate)) {
4881 cliPrintLinef("%s%s%s%s%s%s%s%s",
4882 CUSTOM_DEFAULTS_MANUFACTURER_ID_PREFIX, customDefaultsManufacturerId,
4883 CUSTOM_DEFAULTS_BOARD_NAME_PREFIX, customDefaultsBoardName,
4884 CUSTOM_DEFAULTS_CHANGESET_ID_PREFIX, customDefaultsChangesetId,
4885 CUSTOM_DEFAULTS_DATE_PREFIX, customDefaultsDate
4887 } else {
4888 cliPrintHashLine("config: YES");
4890 } else {
4891 #if defined(USE_UNIFIED_TARGET)
4892 cliPrintError(cmdName, "NO CONFIG FOUND");
4893 #else
4894 cliPrintHashLine("NO CUSTOM DEFAULTS FOUND");
4895 #endif // USE_UNIFIED_TARGET
4897 #endif // USE_CUSTOM_DEFAULTS
4899 #if defined(USE_UNIFIED_TARGET) && defined(USE_BOARD_INFO)
4900 if (printBoardInfo && strlen(getManufacturerId()) && strlen(getBoardName())) {
4901 cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
4903 #endif
4906 static void cliVersion(const char *cmdName, char *cmdline)
4908 UNUSED(cmdline);
4910 printVersion(cmdName, true);
4913 #ifdef USE_RC_SMOOTHING_FILTER
4914 static void cliRcSmoothing(const char *cmdName, char *cmdline)
4916 UNUSED(cmdName);
4917 UNUSED(cmdline);
4918 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
4919 cliPrint("# RC Smoothing Type: ");
4920 if (rxConfig()->rc_smoothing_type == RC_SMOOTHING_TYPE_FILTER) {
4921 cliPrintLine("FILTER");
4922 if (rcSmoothingAutoCalculate()) {
4923 const uint16_t avgRxFrameUs = rcSmoothingData->averageFrameTimeUs;
4924 cliPrint("# Detected RX frame rate: ");
4925 if (avgRxFrameUs == 0) {
4926 cliPrintLine("NO SIGNAL");
4927 } else {
4928 cliPrintLinef("%d.%03dms", avgRxFrameUs / 1000, avgRxFrameUs % 1000);
4931 cliPrintLinef("# Input filter type: %s", lookupTables[TABLE_RC_SMOOTHING_INPUT_TYPE].values[rcSmoothingData->inputFilterType]);
4932 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingData->inputCutoffFrequency);
4933 if (rcSmoothingData->inputCutoffSetting == 0) {
4934 cliPrintLine("(auto)");
4935 } else {
4936 cliPrintLine("(manual)");
4938 cliPrintf("# Derivative filter type: %s", lookupTables[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE].values[rcSmoothingData->derivativeFilterType]);
4939 if (rcSmoothingData->derivativeFilterTypeSetting == RC_SMOOTHING_DERIVATIVE_AUTO) {
4940 cliPrintLine(" (auto)");
4941 } else {
4942 cliPrintLinefeed();
4944 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingData->derivativeCutoffFrequency);
4945 if (rcSmoothingData->derivativeFilterType == RC_SMOOTHING_DERIVATIVE_OFF) {
4946 cliPrintLine("off)");
4947 } else {
4948 if (rcSmoothingData->derivativeCutoffSetting == 0) {
4949 cliPrintLine("auto)");
4950 } else {
4951 cliPrintLine("manual)");
4954 } else {
4955 cliPrintLine("INTERPOLATION");
4958 #endif // USE_RC_SMOOTHING_FILTER
4960 #if defined(USE_RESOURCE_MGMT)
4962 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
4964 typedef struct {
4965 const uint8_t owner;
4966 pgn_t pgn;
4967 uint8_t stride;
4968 uint8_t offset;
4969 const uint8_t maxIndex;
4970 } cliResourceValue_t;
4972 // Handy macros for keeping the table tidy.
4973 // DEFS : Single entry
4974 // DEFA : Array of uint8_t (stride = 1)
4975 // DEFW : Wider stride case; array of structs.
4977 #define DEFS(owner, pgn, type, member) \
4978 { owner, pgn, 0, offsetof(type, member), 0 }
4980 #define DEFA(owner, pgn, type, member, max) \
4981 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
4983 #define DEFW(owner, pgn, type, member, max) \
4984 { owner, pgn, sizeof(type), offsetof(type, member), max }
4986 const cliResourceValue_t resourceTable[] = {
4987 #ifdef USE_BEEPER
4988 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
4989 #endif
4990 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
4991 #ifdef USE_SERVOS
4992 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
4993 #endif
4994 #if defined(USE_PPM)
4995 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
4996 #endif
4997 #if defined(USE_PWM)
4998 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
4999 #endif
5000 #ifdef USE_RANGEFINDER_HCSR04
5001 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
5002 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
5003 #endif
5004 #ifdef USE_LED_STRIP
5005 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
5006 #endif
5007 #ifdef USE_UART
5008 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
5009 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
5010 #endif
5011 #ifdef USE_INVERTER
5012 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
5013 #endif
5014 #ifdef USE_I2C
5015 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
5016 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
5017 #endif
5018 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
5019 #ifdef USE_SPEKTRUM_BIND
5020 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
5021 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
5022 #endif
5023 #ifdef USE_TRANSPONDER
5024 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
5025 #endif
5026 #ifdef USE_SPI
5027 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
5028 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
5029 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
5030 #endif
5031 #ifdef USE_ESCSERIAL
5032 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
5033 #endif
5034 #ifdef USE_CAMERA_CONTROL
5035 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
5036 #endif
5037 #ifdef USE_ADC
5038 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
5039 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
5040 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
5041 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
5042 #endif
5043 #ifdef USE_BARO
5044 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
5045 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
5046 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
5047 #endif
5048 #ifdef USE_MAG
5049 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
5050 #ifdef USE_MAG_DATA_READY_SIGNAL
5051 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
5052 #endif
5053 #endif
5054 #ifdef USE_SDCARD_SPI
5055 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
5056 #endif
5057 #ifdef USE_SDCARD
5058 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
5059 #endif
5060 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
5061 DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
5062 DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
5063 DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
5064 DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
5065 DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
5066 DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
5067 #endif
5068 #ifdef USE_PINIO
5069 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
5070 #endif
5071 #if defined(USE_USB_MSC)
5072 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
5073 #endif
5074 #ifdef USE_FLASH_CHIP
5075 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
5076 #endif
5077 #ifdef USE_MAX7456
5078 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
5079 #endif
5080 #ifdef USE_RX_SPI
5081 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
5082 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
5083 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
5084 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
5085 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
5086 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
5087 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
5088 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
5089 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
5090 #endif
5091 #endif
5092 #endif
5093 #ifdef USE_GYRO_EXTI
5094 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
5095 #endif
5096 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
5097 #ifdef USE_USB_DETECT
5098 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
5099 #endif
5100 #ifdef USE_VTX_RTC6705
5101 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
5102 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
5103 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
5104 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
5105 #endif
5106 #ifdef USE_PIN_PULL_UP_DOWN
5107 DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5108 DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5109 #endif
5112 #undef DEFS
5113 #undef DEFA
5114 #undef DEFW
5116 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
5118 const pgRegistry_t* rec = pgFind(value.pgn);
5119 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
5122 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
5124 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5125 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
5126 const char* owner = ownerNames[resourceTable[i].owner];
5127 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
5128 const void *currentConfig;
5129 const void *defaultConfig;
5130 if (isReadingConfigFromCopy()) {
5131 currentConfig = pg->copy;
5132 defaultConfig = pg->address;
5133 } else {
5134 currentConfig = pg->address;
5135 defaultConfig = NULL;
5138 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
5139 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5140 ioTag_t ioTagDefault = NULL;
5141 if (defaultConfig) {
5142 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5145 const bool equalsDefault = ioTag == ioTagDefault;
5146 const char *format = "resource %s %d %c%02d";
5147 const char *formatUnassigned = "resource %s %d NONE";
5148 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5149 if (ioTagDefault) {
5150 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
5151 } else if (defaultConfig) {
5152 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5154 if (ioTag) {
5155 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
5156 } else if (!(dumpMask & HIDE_UNUSED)) {
5157 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5163 static void printResourceOwner(uint8_t owner, uint8_t index)
5165 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
5167 if (resourceTable[owner].maxIndex > 0) {
5168 cliPrintf(" %d", RESOURCE_INDEX(index));
5172 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
5174 if (!newTag) {
5175 return;
5178 const char * format = "\r\nNOTE: %c%02d already assigned to ";
5179 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
5180 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
5181 ioTag_t *tag = getIoTag(resourceTable[r], i);
5182 if (*tag == newTag) {
5183 bool cleared = false;
5184 if (r == resourceIndex) {
5185 if (i == index) {
5186 continue;
5188 *tag = IO_TAG_NONE;
5189 cleared = true;
5192 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
5194 printResourceOwner(r, i);
5196 if (cleared) {
5197 cliPrintf(". ");
5198 printResourceOwner(r, i);
5199 cliPrintf(" disabled");
5202 cliPrintLine(".");
5208 static bool strToPin(char *ptr, ioTag_t *tag)
5210 if (strcasecmp(ptr, "NONE") == 0) {
5211 *tag = IO_TAG_NONE;
5213 return true;
5214 } else {
5215 const unsigned port = (*ptr >= 'a') ? *ptr - 'a' : *ptr - 'A';
5216 if (port < 8) {
5217 ptr++;
5219 char *end;
5220 const long pin = strtol(ptr, &end, 10);
5221 if (end != ptr && pin >= 0 && pin < 16) {
5222 *tag = DEFIO_TAG_MAKE(port, pin);
5224 return true;
5229 return false;
5232 #ifdef USE_DMA
5233 static void showDma(void)
5235 cliPrintLinefeed();
5237 #ifdef MINIMAL_CLI
5238 cliPrintLine("DMA:");
5239 #else
5240 cliPrintLine("Currently active DMA:");
5241 cliRepeat('-', 20);
5242 #endif
5243 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
5244 const resourceOwner_t *owner = dmaGetOwner(i);
5246 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
5247 if (owner->resourceIndex > 0) {
5248 cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
5249 } else {
5250 cliPrintLinef(" %s", ownerNames[owner->owner]);
5254 #endif
5256 #ifdef USE_DMA_SPEC
5258 typedef struct dmaoptEntry_s {
5259 char *device;
5260 dmaPeripheral_e peripheral;
5261 pgn_t pgn;
5262 uint8_t stride;
5263 uint8_t offset;
5264 uint8_t maxIndex;
5265 uint32_t presenceMask;
5266 } dmaoptEntry_t;
5268 #define MASK_IGNORED (0)
5270 // Handy macros for keeping the table tidy.
5271 // DEFS : Single entry
5272 // DEFA : Array of uint8_t (stride = 1)
5273 // DEFW : Wider stride case; array of structs.
5275 #define DEFS(device, peripheral, pgn, type, member) \
5276 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5278 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5279 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5281 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5282 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
5284 dmaoptEntry_t dmaoptEntryTable[] = {
5285 DEFW("SPI_TX", DMA_PERIPH_SPI_TX, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5286 DEFW("SPI_RX", DMA_PERIPH_SPI_RX, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5287 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
5288 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
5289 DEFW("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5290 DEFW("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5291 #if defined(STM32H7) || defined(STM32G4)
5292 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
5293 #endif
5296 #undef DEFS
5297 #undef DEFA
5298 #undef DEFW
5300 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5301 #define DMA_OPT_STRING_BUFSIZE 5
5303 #if defined(STM32H7) || defined(STM32G4)
5304 #define DMA_CHANREQ_STRING "Request"
5305 #else
5306 #define DMA_CHANREQ_STRING "Channel"
5307 #endif
5309 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
5310 #define DMA_STCH_STRING "Stream"
5311 #else
5312 #define DMA_STCH_STRING "Channel"
5313 #endif
5315 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5317 static void optToString(int optval, char *buf)
5319 if (optval == DMA_OPT_UNUSED) {
5320 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
5321 } else {
5322 tfp_sprintf(buf, "%d", optval);
5326 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5328 if (dmaopt != DMA_OPT_UNUSED) {
5329 printValue(dumpMask, equalsDefault,
5330 "dma %s %d %d",
5331 entry->device, DMA_OPT_UI_INDEX(index), dmaopt);
5333 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5334 dmaCode_t dmaCode = 0;
5335 if (dmaChannelSpec) {
5336 dmaCode = dmaChannelSpec->code;
5338 printValue(dumpMask, equalsDefault,
5339 "# %s %d: " DMASPEC_FORMAT_STRING,
5340 entry->device, DMA_OPT_UI_INDEX(index), DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5341 } else if (!(dumpMask & HIDE_UNUSED)) {
5342 printValue(dumpMask, equalsDefault,
5343 "dma %s %d NONE",
5344 entry->device, DMA_OPT_UI_INDEX(index));
5348 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5350 const pgRegistry_t* pg = pgFind(entry->pgn);
5351 const void *currentConfig;
5352 const void *defaultConfig;
5354 if (isReadingConfigFromCopy()) {
5355 currentConfig = pg->copy;
5356 defaultConfig = pg->address;
5357 } else {
5358 currentConfig = pg->address;
5359 defaultConfig = NULL;
5362 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5363 dmaoptValue_t defaultOpt;
5365 if (defaultConfig) {
5366 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5367 } else {
5368 defaultOpt = DMA_OPT_UNUSED;
5371 bool equalsDefault = currentOpt == defaultOpt;
5372 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5374 if (defaultConfig) {
5375 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5378 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5379 return headingStr;
5382 #if defined(USE_TIMER_MGMT)
5383 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5385 const char *format = "dma pin %c%02d %d";
5387 if (dmaopt != DMA_OPT_UNUSED) {
5388 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5389 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5390 dmaopt
5393 if (printDetails) {
5394 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5395 dmaCode_t dmaCode = 0;
5396 if (dmaChannelSpec) {
5397 dmaCode = dmaChannelSpec->code;
5398 printValue(dumpMask, false,
5399 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5400 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5401 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5405 } else if (!(dumpMask & HIDE_UNUSED)) {
5406 printValue(dumpMask, equalsDefault,
5407 "dma pin %c%02d NONE",
5408 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5413 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5415 const ioTag_t ioTag = currentConfig[index].ioTag;
5417 if (!ioTag) {
5418 return headingStr;
5421 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5422 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5424 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5425 bool equalsDefault = defaultDmaopt == dmaopt;
5426 if (defaultConfig) {
5427 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5428 if (defaultConfig[i].ioTag == ioTag) {
5429 defaultDmaopt = defaultConfig[i].dmaopt;
5431 // 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.
5432 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5434 tagsInUse[index] = true;
5436 break;
5441 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5443 if (defaultConfig) {
5444 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5447 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5448 return headingStr;
5450 #endif
5452 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5454 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5455 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5456 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5457 for (int index = 0; index < entry->maxIndex; index++) {
5458 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5462 #if defined(USE_TIMER_MGMT)
5463 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5464 const timerIOConfig_t *currentConfig;
5465 const timerIOConfig_t *defaultConfig;
5467 if (isReadingConfigFromCopy()) {
5468 currentConfig = (timerIOConfig_t *)pg->copy;
5469 defaultConfig = (timerIOConfig_t *)pg->address;
5470 } else {
5471 currentConfig = (timerIOConfig_t *)pg->address;
5472 defaultConfig = NULL;
5475 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5476 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5477 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5480 if (defaultConfig) {
5481 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5482 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5483 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5484 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5485 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5487 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5491 #endif
5494 static void cliDmaopt(const char *cmdName, char *cmdline)
5496 char *pch = NULL;
5497 char *saveptr;
5499 // Peripheral name or command option
5500 pch = strtok_r(cmdline, " ", &saveptr);
5501 if (!pch) {
5502 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5504 return;
5505 } else if (strcasecmp(pch, "list") == 0) {
5506 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5508 return;
5511 dmaoptEntry_t *entry = NULL;
5512 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5513 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5514 entry = &dmaoptEntryTable[i];
5518 if (!entry && strcasecmp(pch, "pin") != 0) {
5519 cliPrintErrorLinef(cmdName, "BAD DEVICE: %s", pch);
5520 return;
5523 // Index
5524 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5526 int index = 0;
5527 dmaoptValue_t *optaddr = NULL;
5529 ioTag_t ioTag = IO_TAG_NONE;
5530 #if defined(USE_TIMER_MGMT)
5531 timerIOConfig_t *timerIoConfig = NULL;
5532 #endif
5533 const timerHardware_t *timer = NULL;
5534 pch = strtok_r(NULL, " ", &saveptr);
5535 if (entry) {
5536 index = atoi(pch) - 1;
5537 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5538 cliPrintErrorLinef(cmdName, "BAD INDEX: '%s'", pch ? pch : "");
5539 return;
5542 const pgRegistry_t* pg = pgFind(entry->pgn);
5543 const void *currentConfig;
5544 if (isWritingConfigToCopy()) {
5545 currentConfig = pg->copy;
5546 } else {
5547 currentConfig = pg->address;
5549 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5550 orgval = *optaddr;
5551 } else {
5552 // It's a pin
5553 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5554 cliPrintErrorLinef(cmdName, "INVALID PIN: '%s'", pch ? pch : "");
5556 return;
5559 orgval = dmaoptByTag(ioTag);
5560 #if defined(USE_TIMER_MGMT)
5561 timerIoConfig = timerIoConfigByTag(ioTag);
5562 #endif
5563 timer = timerGetByTag(ioTag);
5566 // opt or list
5567 pch = strtok_r(NULL, " ", &saveptr);
5568 if (!pch) {
5569 if (entry) {
5570 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5572 #if defined(USE_TIMER_MGMT)
5573 else {
5574 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5576 #endif
5578 return;
5579 } else if (strcasecmp(pch, "list") == 0) {
5580 // Show possible opts
5581 const dmaChannelSpec_t *dmaChannelSpec;
5582 if (entry) {
5583 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5584 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5586 } else {
5587 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5588 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5592 return;
5593 } else if (pch) {
5594 int optval;
5595 if (strcasecmp(pch, "none") == 0) {
5596 optval = DMA_OPT_UNUSED;
5597 } else {
5598 optval = atoi(pch);
5600 if (entry) {
5601 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5602 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5604 return;
5606 } else {
5607 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5608 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5610 return;
5615 char optvalString[DMA_OPT_STRING_BUFSIZE];
5616 optToString(optval, optvalString);
5618 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5619 optToString(orgval, orgvalString);
5621 if (optval != orgval) {
5622 if (entry) {
5623 *optaddr = optval;
5625 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5626 } else {
5627 #if defined(USE_TIMER_MGMT)
5628 timerIoConfig->dmaopt = optval;
5629 #endif
5631 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5633 } else {
5634 if (entry) {
5635 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5636 } else {
5637 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5642 #endif // USE_DMA_SPEC
5644 #ifdef USE_DMA
5645 static void cliDma(const char *cmdName, char* cmdline)
5647 int len = strlen(cmdline);
5648 if (len && strncasecmp(cmdline, "show", len) == 0) {
5649 showDma();
5651 return;
5654 #if defined(USE_DMA_SPEC)
5655 cliDmaopt(cmdName, cmdline);
5656 #else
5657 cliShowParseError(cmdName);
5658 #endif
5660 #endif
5661 #endif // USE_RESOURCE_MGMT
5663 #ifdef USE_TIMER_MGMT
5664 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5666 const char *format = "timer %c%02d AF%d";
5667 const char *emptyFormat = "timer %c%02d NONE";
5669 if (timerIndex > 0) {
5670 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5671 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5672 IO_GPIOPortIdxByTag(ioTag) + 'A',
5673 IO_GPIOPinIdxByTag(ioTag),
5674 timer->alternateFunction
5676 if (printDetails) {
5677 printValue(dumpMask, false,
5678 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5679 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5680 timerGetTIMNumber(timer->tim),
5681 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5682 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5683 timer->alternateFunction
5686 } else {
5687 printValue(dumpMask, equalsDefault, emptyFormat,
5688 IO_GPIOPortIdxByTag(ioTag) + 'A',
5689 IO_GPIOPinIdxByTag(ioTag)
5694 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5696 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5697 const timerIOConfig_t *currentConfig;
5698 const timerIOConfig_t *defaultConfig;
5700 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5701 if (isReadingConfigFromCopy()) {
5702 currentConfig = (timerIOConfig_t *)pg->copy;
5703 defaultConfig = (timerIOConfig_t *)pg->address;
5704 } else {
5705 currentConfig = (timerIOConfig_t *)pg->address;
5706 defaultConfig = NULL;
5709 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5710 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5711 const ioTag_t ioTag = currentConfig[i].ioTag;
5713 if (!ioTag) {
5714 continue;
5717 const uint8_t timerIndex = currentConfig[i].index;
5719 uint8_t defaultTimerIndex = 0;
5720 if (defaultConfig) {
5721 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5722 if (defaultConfig[i].ioTag == ioTag) {
5723 defaultTimerIndex = defaultConfig[i].index;
5724 tagsInUse[i] = true;
5726 break;
5731 const bool equalsDefault = defaultTimerIndex == timerIndex;
5732 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5733 if (defaultConfig && defaultTimerIndex) {
5734 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5737 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5740 if (defaultConfig) {
5741 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5742 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5743 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5744 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5746 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5752 #define TIMER_INDEX_UNDEFINED -1
5753 #define TIMER_AF_STRING_BUFSIZE 5
5755 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5757 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5758 if (!timer) {
5759 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5760 } else {
5761 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5765 static void showTimers(void)
5767 cliPrintLinefeed();
5769 #ifdef MINIMAL_CLI
5770 cliPrintLine("Timers:");
5771 #else
5772 cliPrintLine("Currently active Timers:");
5773 cliRepeat('-', 23);
5774 #endif
5776 int8_t timerNumber;
5777 for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
5778 cliPrintf("TIM%d:", timerNumber);
5779 bool timerUsed = false;
5780 for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
5781 const resourceOwner_t *timerOwner = timerGetOwner(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
5782 if (timerOwner->owner) {
5783 if (!timerUsed) {
5784 timerUsed = true;
5786 cliPrintLinefeed();
5789 if (timerOwner->resourceIndex > 0) {
5790 cliPrintLinef(" CH%d: %s %d", timerIndex + 1, ownerNames[timerOwner->owner], timerOwner->resourceIndex);
5791 } else {
5792 cliPrintLinef(" CH%d: %s", timerIndex + 1, ownerNames[timerOwner->owner]);
5797 if (!timerUsed) {
5798 cliPrintLine(" FREE");
5803 static void cliTimer(const char *cmdName, char *cmdline)
5805 int len = strlen(cmdline);
5807 if (len == 0) {
5808 printTimer(DUMP_MASTER, NULL);
5810 return;
5811 } else if (strncasecmp(cmdline, "list", len) == 0) {
5812 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5814 return;
5815 } else if (strncasecmp(cmdline, "show", len) == 0) {
5816 showTimers();
5818 return;
5821 char *pch = NULL;
5822 char *saveptr;
5824 ioTag_t ioTag = IO_TAG_NONE;
5825 pch = strtok_r(cmdline, " ", &saveptr);
5826 if (!pch || !strToPin(pch, &ioTag)) {
5827 cliShowParseError(cmdName);
5829 return;
5830 } else if (!IOGetByTag(ioTag)) {
5831 cliPrintErrorLinef(cmdName, "PIN NOT USED ON BOARD.");
5833 return;
5836 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5837 bool isExistingTimerOpt = false;
5838 /* find existing entry, or go for next available */
5839 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5840 if (timerIOConfig(i)->ioTag == ioTag) {
5841 timerIOIndex = i;
5842 isExistingTimerOpt = true;
5844 break;
5847 /* first available empty slot */
5848 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5849 timerIOIndex = i;
5853 if (timerIOIndex < 0) {
5854 cliPrintErrorLinef(cmdName, "PIN TIMER MAP FULL.");
5856 return;
5859 pch = strtok_r(NULL, " ", &saveptr);
5860 if (pch) {
5861 int timerIndex = TIMER_INDEX_UNDEFINED;
5862 if (strcasecmp(pch, "list") == 0) {
5863 /* output the list of available options */
5864 const timerHardware_t *timer;
5865 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5866 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5867 timer->alternateFunction,
5868 timerGetTIMNumber(timer->tim),
5869 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5870 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
5874 return;
5875 } else if (strncasecmp(pch, "af", 2) == 0) {
5876 unsigned alternateFunction = atoi(&pch[2]);
5878 const timerHardware_t *timer;
5879 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5880 if (timer->alternateFunction == alternateFunction) {
5881 timerIndex = index;
5883 break;
5887 if (!timer) {
5888 cliPrintErrorLinef(cmdName, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5890 return;
5892 } else if (strcasecmp(pch, "none") != 0) {
5893 cliPrintErrorLinef(cmdName, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5895 return;
5898 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
5899 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
5900 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
5901 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
5903 char optvalString[DMA_OPT_STRING_BUFSIZE];
5904 alternateFunctionToString(ioTag, timerIndex, optvalString);
5906 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5907 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
5909 if (timerIndex == oldTimerIndex) {
5910 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
5911 } else {
5912 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5915 return;
5916 } else {
5917 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
5919 return;
5922 #endif
5924 #if defined(USE_RESOURCE_MGMT)
5925 static void cliResource(const char *cmdName, char *cmdline)
5927 char *pch = NULL;
5928 char *saveptr;
5930 pch = strtok_r(cmdline, " ", &saveptr);
5931 if (!pch) {
5932 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
5934 return;
5935 } else if (strcasecmp(pch, "show") == 0) {
5936 #ifdef MINIMAL_CLI
5937 cliPrintLine("IO");
5938 #else
5939 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
5940 cliRepeat('-', 20);
5941 #endif
5942 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
5943 const char* owner;
5944 owner = ownerNames[ioRecs[i].owner];
5946 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
5947 if (ioRecs[i].index > 0) {
5948 cliPrintf(" %d", ioRecs[i].index);
5950 cliPrintLinefeed();
5953 pch = strtok_r(NULL, " ", &saveptr);
5954 if (strcasecmp(pch, "all") == 0) {
5955 #if defined(USE_TIMER_MGMT)
5956 cliTimer(cmdName, "show");
5957 #endif
5958 #if defined(USE_DMA)
5959 cliDma(cmdName, "show");
5960 #endif
5963 return;
5966 unsigned resourceIndex = 0;
5967 for (; ; resourceIndex++) {
5968 if (resourceIndex >= ARRAYLEN(resourceTable)) {
5969 cliPrintErrorLinef(cmdName, "INVALID RESOURCE NAME: '%s'", pch);
5970 return;
5973 const char *resourceName = ownerNames[resourceTable[resourceIndex].owner];
5974 if (strncasecmp(pch, resourceName, strlen(resourceName)) == 0) {
5975 break;
5979 pch = strtok_r(NULL, " ", &saveptr);
5980 int index = atoi(pch);
5982 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
5983 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
5984 cliShowArgumentRangeError(cmdName, "INDEX", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
5985 return;
5987 index -= 1;
5989 pch = strtok_r(NULL, " ", &saveptr);
5992 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
5994 if (strlen(pch) > 0) {
5995 if (strToPin(pch, tag)) {
5996 if (*tag == IO_TAG_NONE) {
5997 #ifdef MINIMAL_CLI
5998 cliPrintLine("Freed");
5999 #else
6000 cliPrintLine("Resource is freed");
6001 #endif
6002 return;
6003 } else {
6004 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
6005 if (rec) {
6006 resourceCheck(resourceIndex, index, *tag);
6007 #ifdef MINIMAL_CLI
6008 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6009 #else
6010 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
6011 #endif
6012 } else {
6013 cliShowParseError(cmdName);
6015 return;
6020 cliShowParseError(cmdName);
6022 #endif
6024 #ifdef USE_DSHOT_TELEMETRY
6025 static void cliDshotTelemetryInfo(const char *cmdName, char *cmdline)
6027 UNUSED(cmdName);
6028 UNUSED(cmdline);
6030 if (useDshotTelemetry) {
6031 cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
6032 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
6033 uint32_t directionChangeCycles = dshotDMAHandlerCycleCounters.changeDirectionCompletedAt - dshotDMAHandlerCycleCounters.irqAt;
6034 uint32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
6035 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
6036 cliPrintLinefeed();
6038 #ifdef USE_DSHOT_TELEMETRY_STATS
6039 cliPrintLine("Motor eRPM RPM Hz Invalid");
6040 cliPrintLine("===== ======= ====== ===== =======");
6041 #else
6042 cliPrintLine("Motor eRPM RPM Hz");
6043 cliPrintLine("===== ======= ====== =====");
6044 #endif
6045 for (uint8_t i = 0; i < getMotorCount(); i++) {
6046 cliPrintf("%5d %7d %6d %5d ", i,
6047 (int)getDshotTelemetry(i) * 100,
6048 (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount,
6049 (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount / 60);
6050 #ifdef USE_DSHOT_TELEMETRY_STATS
6051 if (isDshotMotorTelemetryActive(i)) {
6052 const int calcPercent = getDshotTelemetryMotorInvalidPercent(i);
6053 cliPrintLinef("%3d.%02d%%", calcPercent / 100, calcPercent % 100);
6054 } else {
6055 cliPrintLine("NO DATA");
6057 #else
6058 cliPrintLinefeed();
6059 #endif
6061 cliPrintLinefeed();
6063 const int len = MAX_GCR_EDGES;
6064 #ifdef DEBUG_BBDECODE
6065 extern uint16_t bbBuffer[134];
6066 for (int i = 0; i < 134; i++) {
6067 cliPrintf("%u ", (int)bbBuffer[i]);
6069 cliPrintLinefeed();
6070 #endif
6071 for (int i = 0; i < len; i++) {
6072 cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
6074 cliPrintLinefeed();
6075 for (int i = 1; i < len; i++) {
6076 cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
6078 cliPrintLinefeed();
6079 } else {
6080 cliPrintLine("Dshot telemetry not enabled");
6083 #endif
6085 static void printConfig(const char *cmdName, char *cmdline, bool doDiff)
6087 dumpFlags_t dumpMask = DUMP_MASTER;
6088 char *options;
6089 if ((options = checkCommand(cmdline, "master"))) {
6090 dumpMask = DUMP_MASTER; // only
6091 } else if ((options = checkCommand(cmdline, "profile"))) {
6092 dumpMask = DUMP_PROFILE; // only
6093 } else if ((options = checkCommand(cmdline, "rates"))) {
6094 dumpMask = DUMP_RATES; // only
6095 } else if ((options = checkCommand(cmdline, "hardware"))) {
6096 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
6097 } else if ((options = checkCommand(cmdline, "all"))) {
6098 dumpMask = DUMP_ALL; // all profiles and rates
6099 } else {
6100 options = cmdline;
6103 if (doDiff) {
6104 dumpMask = dumpMask | DO_DIFF;
6107 if (checkCommand(options, "defaults")) {
6108 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
6109 } else if (checkCommand(options, "bare")) {
6110 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
6113 backupAndResetConfigs((dumpMask & BARE) == 0);
6115 #ifdef USE_CLI_BATCH
6116 bool batchModeEnabled = false;
6117 #endif
6118 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
6119 cliPrintHashLine("version");
6120 printVersion(cmdName, false);
6122 if (!(dumpMask & BARE)) {
6123 #ifdef USE_CLI_BATCH
6124 cliPrintHashLine("start the command batch");
6125 cliPrintLine("batch start");
6126 batchModeEnabled = true;
6127 #endif
6129 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
6130 cliPrintHashLine("reset configuration to default settings");
6131 cliPrintLine("defaults nosave");
6135 #if defined(USE_BOARD_INFO)
6136 cliPrintLinefeed();
6137 printBoardName(dumpMask);
6138 printManufacturerId(dumpMask);
6139 #endif
6141 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
6142 cliMcuId(cmdName, "");
6143 #if defined(USE_SIGNATURE)
6144 cliSignature(cmdName, "");
6145 #endif
6148 if (!(dumpMask & HARDWARE_ONLY)) {
6149 printName(dumpMask, &pilotConfig_Copy);
6152 #ifdef USE_RESOURCE_MGMT
6153 printResource(dumpMask, "resources");
6154 #if defined(USE_TIMER_MGMT)
6155 printTimer(dumpMask, "timer");
6156 #endif
6157 #ifdef USE_DMA_SPEC
6158 printDmaopt(dumpMask, "dma");
6159 #endif
6160 #endif
6162 if (!(dumpMask & HARDWARE_ONLY)) {
6163 #ifndef USE_QUAD_MIXER_ONLY
6164 const char *mixerHeadingStr = "mixer";
6165 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
6166 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
6167 const char *formatMixer = "mixer %s";
6168 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
6169 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
6171 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
6173 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
6175 #ifdef USE_SERVOS
6176 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
6178 const char *servoMixHeadingStr = "servo mixer";
6179 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
6180 cliPrintHashLine(servoMixHeadingStr);
6181 cliPrintLine("smix reset\r\n");
6182 servoMixHeadingStr = NULL;
6184 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
6185 #endif
6186 #endif
6188 printFeature(dumpMask, featureConfig_Copy.enabledFeatures, featureConfig()->enabledFeatures, "feature");
6190 #if defined(USE_BEEPER)
6191 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
6193 #if defined(USE_DSHOT)
6194 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
6195 #endif
6196 #endif // USE_BEEPER
6198 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
6200 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
6202 #ifdef USE_LED_STRIP_STATUS_MODE
6203 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
6205 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
6207 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
6208 #endif
6210 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
6212 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
6214 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
6216 #ifdef USE_VTX_TABLE
6217 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
6218 #endif
6220 #ifdef USE_VTX_CONTROL
6221 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
6222 #endif
6224 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
6227 if (dumpMask & HARDWARE_ONLY) {
6228 dumpAllValues(cmdName, HARDWARE_VALUE, dumpMask, "master");
6229 } else {
6230 dumpAllValues(cmdName, MASTER_VALUE, dumpMask, "master");
6232 if (dumpMask & DUMP_ALL) {
6233 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
6234 cliDumpPidProfile(cmdName, pidProfileIndex, dumpMask);
6237 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
6239 if (!(dumpMask & BARE)) {
6240 cliPrintHashLine("restore original profile selection");
6242 cliProfile(cmdName, "");
6245 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
6247 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
6248 cliDumpRateProfile(cmdName, rateIndex, dumpMask);
6251 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
6253 if (!(dumpMask & BARE)) {
6254 cliPrintHashLine("restore original rateprofile selection");
6256 cliRateProfile(cmdName, "");
6258 cliPrintHashLine("save configuration");
6259 cliPrint("save");
6260 #ifdef USE_CLI_BATCH
6261 batchModeEnabled = false;
6262 #endif
6265 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
6266 } else {
6267 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6269 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6272 } else if (dumpMask & DUMP_PROFILE) {
6273 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6274 } else if (dumpMask & DUMP_RATES) {
6275 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6278 #ifdef USE_CLI_BATCH
6279 if (batchModeEnabled) {
6280 cliPrintHashLine("end the command batch");
6281 cliPrintLine("batch end");
6283 #endif
6285 // restore configs from copies
6286 restoreConfigs();
6289 static void cliDump(const char *cmdName, char *cmdline)
6291 printConfig(cmdName, cmdline, false);
6294 static void cliDiff(const char *cmdName, char *cmdline)
6296 printConfig(cmdName, cmdline, true);
6299 #if defined(USE_USB_MSC)
6300 static void cliMsc(const char *cmdName, char *cmdline)
6302 if (mscCheckFilesystemReady()) {
6303 #ifdef USE_RTC_TIME
6304 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
6305 if (!isEmpty(cmdline)) {
6306 timezoneOffsetMinutes = atoi(cmdline);
6307 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
6308 cliPrintErrorLinef(cmdName, "INVALID TIMEZONE OFFSET");
6309 return;
6312 #else
6313 int timezoneOffsetMinutes = 0;
6314 UNUSED(cmdline);
6315 #endif
6316 cliPrintHashLine("Restarting in mass storage mode");
6317 cliPrint("\r\nRebooting");
6318 cliWriterFlush();
6319 waitForSerialPortToFinishTransmitting(cliPort);
6320 motorShutdown();
6322 systemResetToMsc(timezoneOffsetMinutes);
6323 } else {
6324 cliPrintHashLine("Storage not present or failed to initialize!");
6327 #endif
6329 typedef void cliCommandFn(const char* name, char *cmdline);
6331 typedef struct {
6332 const char *name;
6333 #ifndef MINIMAL_CLI
6334 const char *description;
6335 const char *args;
6336 #endif
6337 cliCommandFn *cliCommand;
6338 } clicmd_t;
6340 #ifndef MINIMAL_CLI
6341 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6343 name , \
6344 description , \
6345 args , \
6346 cliCommand \
6348 #else
6349 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6351 name, \
6352 cliCommand \
6354 #endif
6356 static void cliHelp(const char *cmdName, char *cmdline);
6358 // should be sorted a..z for bsearch()
6359 const clicmd_t cmdTable[] = {
6360 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
6361 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
6362 #ifdef USE_CLI_BATCH
6363 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
6364 #endif
6365 #if defined(USE_BEEPER)
6366 #if defined(USE_DSHOT)
6367 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6368 "\t<->[name]", cliBeacon),
6369 #endif
6370 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6371 "\t<->[name]", cliBeeper),
6372 #endif // USE_BEEPER
6373 #if defined(USE_RX_BIND)
6374 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
6375 #endif
6376 #if defined(USE_FLASH_BOOT_LOADER)
6377 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6378 #else
6379 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6380 #endif
6381 #if defined(USE_BOARD_INFO)
6382 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6383 #endif
6384 #ifdef USE_LED_STRIP_STATUS_MODE
6385 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6386 #endif
6387 #if defined(USE_CUSTOM_DEFAULTS)
6388 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave|bare|show]", cliDefaults),
6389 #else
6390 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave|show]", cliDefaults),
6391 #endif
6392 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6393 #ifdef USE_RESOURCE_MGMT
6395 #ifdef USE_DMA
6396 #ifdef USE_DMA_SPEC
6397 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6398 #else
6399 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6400 #endif
6401 #endif
6403 #endif
6404 #ifdef USE_DSHOT_TELEMETRY
6405 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6406 #endif
6407 #ifdef USE_DSHOT
6408 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6409 #endif
6410 CLI_COMMAND_DEF("dump", "dump configuration",
6411 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6412 #ifdef USE_ESCSERIAL
6413 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6414 #endif
6415 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
6416 CLI_COMMAND_DEF("feature", "configure features",
6417 "list\r\n"
6418 "\t<->[name]", cliFeature),
6419 #ifdef USE_FLASHFS
6420 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6421 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6422 #ifdef USE_FLASH_TOOLS
6423 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6424 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6425 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6426 #endif
6427 #endif
6428 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6429 #ifdef USE_GPS
6430 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6431 #endif
6432 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6433 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6434 #endif
6435 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp),
6436 #ifdef USE_LED_STRIP_STATUS_MODE
6437 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6438 #endif
6439 #if defined(USE_BOARD_INFO)
6440 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6441 #endif
6442 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6443 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6444 #ifndef USE_QUAD_MIXER_ONLY
6445 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6446 #endif
6447 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6448 #ifdef USE_LED_STRIP_STATUS_MODE
6449 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6450 #endif
6451 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6452 #ifdef USE_USB_MSC
6453 #ifdef USE_RTC_TIME
6454 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6455 #else
6456 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6457 #endif
6458 #endif
6459 #ifndef MINIMAL_CLI
6460 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6461 #endif
6462 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6463 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6464 #ifdef USE_RC_SMOOTHING_FILTER
6465 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6466 #endif // USE_RC_SMOOTHING_FILTER
6467 #ifdef USE_RESOURCE_MGMT
6468 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6469 #endif
6470 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6471 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6472 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
6473 #ifdef USE_SDCARD
6474 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6475 #endif
6476 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6477 #if defined(USE_SERIAL_PASSTHROUGH)
6478 #if defined(USE_PINIO)
6479 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),
6480 #else
6481 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6482 #endif
6483 #endif
6484 #ifdef USE_SERVOS
6485 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6486 #endif
6487 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6488 #if defined(USE_SIGNATURE)
6489 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6490 #endif
6491 #ifdef USE_SERVOS
6492 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6493 "\treset\r\n"
6494 "\tload <mixer>\r\n"
6495 "\treverse <servo> <source> r|n", cliServoMix),
6496 #endif
6497 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6498 #if defined(USE_TASK_STATISTICS)
6499 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6500 #endif
6501 #ifdef USE_TIMER_MGMT
6502 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6503 #endif
6504 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6505 #ifdef USE_VTX_CONTROL
6506 #ifdef MINIMAL_CLI
6507 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6508 #else
6509 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6510 #endif
6511 #endif
6512 #ifdef USE_VTX_TABLE
6513 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL, cliVtxInfo),
6514 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6515 #endif
6518 static void cliHelp(const char *cmdName, char *cmdline)
6520 bool anyMatches = false;
6522 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6523 bool printEntry = false;
6524 if (isEmpty(cmdline)) {
6525 printEntry = true;
6526 } else {
6527 if (strcasestr(cmdTable[i].name, cmdline)
6528 #ifndef MINIMAL_CLI
6529 || strcasestr(cmdTable[i].description, cmdline)
6530 #endif
6532 printEntry = true;
6536 if (printEntry) {
6537 anyMatches = true;
6538 cliPrint(cmdTable[i].name);
6539 #ifndef MINIMAL_CLI
6540 if (cmdTable[i].description) {
6541 cliPrintf(" - %s", cmdTable[i].description);
6543 if (cmdTable[i].args) {
6544 cliPrintf("\r\n\t%s", cmdTable[i].args);
6546 #endif
6547 cliPrintLinefeed();
6550 if (!isEmpty(cmdline) && !anyMatches) {
6551 cliPrintErrorLinef(cmdName, "NO MATCHES FOR '%s'", cmdline);
6555 static void processCharacter(const char c)
6557 if (bufferIndex && (c == '\n' || c == '\r')) {
6558 // enter pressed
6559 cliPrintLinefeed();
6561 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
6562 if (processingCustomDefaults) {
6563 cliPrint("d: ");
6565 #endif
6567 // Strip comment starting with # from line
6568 char *p = cliBuffer;
6569 p = strchr(p, '#');
6570 if (NULL != p) {
6571 bufferIndex = (uint32_t)(p - cliBuffer);
6574 // Strip trailing whitespace
6575 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6576 bufferIndex--;
6579 // Process non-empty lines
6580 if (bufferIndex > 0) {
6581 cliBuffer[bufferIndex] = 0; // null terminate
6583 const clicmd_t *cmd;
6584 char *options;
6585 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6586 if ((options = checkCommand(cliBuffer, cmd->name))) {
6587 break;
6590 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6591 cmd->cliCommand(cmd->name, options);
6592 } else {
6593 cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
6595 bufferIndex = 0;
6598 memset(cliBuffer, 0, sizeof(cliBuffer));
6600 // 'exit' will reset this flag, so we don't need to print prompt again
6601 if (!cliMode) {
6602 return;
6605 cliPrompt();
6606 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6607 if (!bufferIndex && c == ' ')
6608 return; // Ignore leading spaces
6609 cliBuffer[bufferIndex++] = c;
6610 cliWrite(c);
6614 static void processCharacterInteractive(const char c)
6616 if (c == '\t' || c == '?') {
6617 // do tab completion
6618 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6619 uint32_t i = bufferIndex;
6620 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6621 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) {
6622 continue;
6624 if (!pstart) {
6625 pstart = cmd;
6627 pend = cmd;
6629 if (pstart) { /* Buffer matches one or more commands */
6630 for (; ; bufferIndex++) {
6631 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6632 break;
6633 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6634 /* Unambiguous -- append a space */
6635 cliBuffer[bufferIndex++] = ' ';
6636 cliBuffer[bufferIndex] = '\0';
6637 break;
6639 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6642 if (!bufferIndex || pstart != pend) {
6643 /* Print list of ambiguous matches */
6644 cliPrint("\r\n\033[K");
6645 for (cmd = pstart; cmd <= pend; cmd++) {
6646 cliPrint(cmd->name);
6647 cliWrite('\t');
6649 cliPrompt();
6650 i = 0; /* Redraw prompt */
6652 for (; i < bufferIndex; i++)
6653 cliWrite(cliBuffer[i]);
6654 } else if (!bufferIndex && c == 4) { // CTRL-D
6655 cliExit("", cliBuffer);
6656 return;
6657 } else if (c == 12) { // NewPage / CTRL-L
6658 // clear screen
6659 cliPrint("\033[2J\033[1;1H");
6660 cliPrompt();
6661 } else if (c == 127) {
6662 // backspace
6663 if (bufferIndex) {
6664 cliBuffer[--bufferIndex] = 0;
6665 cliPrint("\010 \010");
6667 } else {
6668 processCharacter(c);
6672 void cliProcess(void)
6674 if (!cliWriter) {
6675 return;
6678 // Flush the buffer to get rid of any MSP data polls sent by configurator after CLI was invoked
6679 cliWriterFlush();
6681 while (serialRxBytesWaiting(cliPort)) {
6682 uint8_t c = serialRead(cliPort);
6684 processCharacterInteractive(c);
6688 #if defined(USE_CUSTOM_DEFAULTS)
6689 static bool cliProcessCustomDefaults(bool quiet)
6691 if (processingCustomDefaults || !hasCustomDefaults()) {
6692 return false;
6695 bufWriter_t *cliWriterTemp = NULL;
6696 if (quiet
6697 #if !defined(DEBUG_CUSTOM_DEFAULTS)
6698 || true
6699 #endif
6701 cliWriterTemp = cliWriter;
6702 cliWriter = NULL;
6704 if (quiet) {
6705 cliErrorWriter = NULL;
6708 memcpy(cliBufferTemp, cliBuffer, sizeof(cliBuffer));
6709 uint32_t bufferIndexTemp = bufferIndex;
6710 bufferIndex = 0;
6711 processingCustomDefaults = true;
6713 char *customDefaultsPtr = customDefaultsStart;
6714 while (customDefaultsHasNext(customDefaultsPtr)) {
6715 processCharacter(*customDefaultsPtr++);
6718 // Process a newline at the very end so that the last command gets executed,
6719 // even when the file did not contain a trailing newline
6720 processCharacter('\r');
6722 processingCustomDefaults = false;
6724 if (cliWriterTemp) {
6725 cliWriter = cliWriterTemp;
6726 cliErrorWriter = cliWriter;
6729 memcpy(cliBuffer, cliBufferTemp, sizeof(cliBuffer));
6730 bufferIndex = bufferIndexTemp;
6732 systemConfigMutable()->configurationState = CONFIGURATION_STATE_DEFAULTS_CUSTOM;
6734 return true;
6736 #endif
6738 void cliEnter(serialPort_t *serialPort)
6740 cliMode = true;
6741 cliPort = serialPort;
6742 setPrintfSerialPort(cliPort);
6743 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6744 cliErrorWriter = cliWriter;
6746 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
6748 #ifndef MINIMAL_CLI
6749 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6750 #else
6751 cliPrintLine("\r\nCLI");
6752 #endif
6753 setArmingDisabled(ARMING_DISABLED_CLI);
6755 cliPrompt();
6757 #ifdef USE_CLI_BATCH
6758 resetCommandBatch();
6759 #endif
6762 #endif // USE_CLI