Fix problem with empty string set as 'manufacturer_id'.
[betaflight.git] / src / main / cli / cli.c
blobf17fb351d57eb9a347a1cdf816342b8eac5d8757
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_eeprom.h"
58 #include "config/feature.h"
60 #include "drivers/accgyro/accgyro.h"
61 #include "drivers/adc.h"
62 #include "drivers/buf_writer.h"
63 #include "drivers/bus_spi.h"
64 #include "drivers/dma.h"
65 #include "drivers/dma_reqmap.h"
66 #include "drivers/dshot.h"
67 #include "drivers/dshot_command.h"
68 #include "drivers/dshot_dpwm.h"
69 #include "drivers/pwm_output_dshot_shared.h"
70 #include "drivers/camera_control.h"
71 #include "drivers/compass/compass.h"
72 #include "drivers/display.h"
73 #include "drivers/dma.h"
74 #include "drivers/flash.h"
75 #include "drivers/inverter.h"
76 #include "drivers/io.h"
77 #include "drivers/io_impl.h"
78 #include "drivers/light_led.h"
79 #include "drivers/motor.h"
80 #include "drivers/rangefinder/rangefinder_hcsr04.h"
81 #include "drivers/resource.h"
82 #include "drivers/sdcard.h"
83 #include "drivers/sensor.h"
84 #include "drivers/serial.h"
85 #include "drivers/serial_escserial.h"
86 #include "drivers/sound_beeper.h"
87 #include "drivers/stack_check.h"
88 #include "drivers/system.h"
89 #include "drivers/time.h"
90 #include "drivers/timer.h"
91 #include "drivers/transponder_ir.h"
92 #include "drivers/usb_msc.h"
93 #include "drivers/vtx_common.h"
94 #include "drivers/vtx_table.h"
96 #include "fc/board_info.h"
97 #include "fc/config.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_spi_cc2500.h"
146 #include "pg/rx_spi.h"
147 #include "pg/rx_pwm.h"
148 #include "pg/serial_uart.h"
149 #include "pg/sdio.h"
150 #include "pg/timerio.h"
151 #include "pg/timerup.h"
152 #include "pg/usb.h"
153 #include "pg/vtx_table.h"
155 #include "rx/rx.h"
156 #include "rx/spektrum.h"
157 #include "rx/rx_spi_common.h"
158 #include "rx/srxl2.h"
160 #include "scheduler/scheduler.h"
162 #include "sensors/acceleration.h"
163 #include "sensors/adcinternal.h"
164 #include "sensors/barometer.h"
165 #include "sensors/battery.h"
166 #include "sensors/boardalignment.h"
167 #include "sensors/compass.h"
168 #include "sensors/esc_sensor.h"
169 #include "sensors/gyro.h"
170 #include "sensors/sensors.h"
172 #include "telemetry/frsky_hub.h"
173 #include "telemetry/telemetry.h"
175 #include "cli.h"
177 static serialPort_t *cliPort = NULL;
179 #ifdef STM32F1
180 #define CLI_IN_BUFFER_SIZE 128
181 #else
182 // Space required to set array parameters
183 #define CLI_IN_BUFFER_SIZE 256
184 #endif
185 #define CLI_OUT_BUFFER_SIZE 64
187 static bufWriter_t *cliWriter = NULL;
188 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
190 static char cliBuffer[CLI_IN_BUFFER_SIZE];
191 static uint32_t bufferIndex = 0;
193 static bool configIsInCopy = false;
195 #define CURRENT_PROFILE_INDEX -1
196 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
197 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
199 static bool featureMaskIsCopied = false;
200 static uint32_t featureMaskCopy;
202 #ifdef USE_CLI_BATCH
203 static bool commandBatchActive = false;
204 static bool commandBatchError = false;
205 #endif
207 #if defined(USE_BOARD_INFO)
208 static bool boardInformationUpdated = false;
209 #if defined(USE_SIGNATURE)
210 static bool signatureUpdated = false;
211 #endif
212 #endif // USE_BOARD_INFO
214 static const char* const emptyName = "-";
215 static const char* const emptyString = "";
217 #if !defined(USE_CUSTOM_DEFAULTS)
218 #define CUSTOM_DEFAULTS_START ((char*)0)
219 #define CUSTOM_DEFAULTS_END ((char *)0)
220 #else
221 extern char __custom_defaults_start;
222 extern char __custom_defaults_end;
223 #define CUSTOM_DEFAULTS_START (&__custom_defaults_start)
224 #define CUSTOM_DEFAULTS_END (&__custom_defaults_end)
226 static bool processingCustomDefaults = false;
227 static char cliBufferTemp[CLI_IN_BUFFER_SIZE];
228 #endif
230 #if defined(USE_CUSTOM_DEFAULTS_ADDRESS)
231 static char __attribute__ ((section(".custom_defaults_start_address"))) *customDefaultsStart = CUSTOM_DEFAULTS_START;
232 static char __attribute__ ((section(".custom_defaults_end_address"))) *customDefaultsEnd = CUSTOM_DEFAULTS_END;
233 #endif
235 #ifndef USE_QUAD_MIXER_ONLY
236 // sync this with mixerMode_e
237 static const char * const mixerNames[] = {
238 "TRI", "QUADP", "QUADX", "BI",
239 "GIMBAL", "Y6", "HEX6",
240 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
241 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
242 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
243 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
245 #endif
247 // sync this with features_e
248 static const char * const featureNames[] = {
249 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
250 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
251 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
252 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
253 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
254 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
257 // sync this with rxFailsafeChannelMode_e
258 static const char rxFailsafeModeCharacters[] = "ahs";
260 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
261 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
262 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
265 #if defined(USE_SENSOR_NAMES)
266 // sync this with sensors_e
267 static const char * const sensorTypeNames[] = {
268 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
271 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
273 static const char * const *sensorHardwareNames[] = {
274 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
276 #endif // USE_SENSOR_NAMES
278 typedef enum dumpFlags_e {
279 DUMP_MASTER = (1 << 0),
280 DUMP_PROFILE = (1 << 1),
281 DUMP_RATES = (1 << 2),
282 DUMP_ALL = (1 << 3),
283 DO_DIFF = (1 << 4),
284 SHOW_DEFAULTS = (1 << 5),
285 HIDE_UNUSED = (1 << 6),
286 HARDWARE_ONLY = (1 << 7),
287 BARE = (1 << 8),
288 } dumpFlags_t;
290 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
292 typedef enum {
293 REBOOT_TARGET_FIRMWARE,
294 REBOOT_TARGET_BOOTLOADER_ROM,
295 REBOOT_TARGET_BOOTLOADER_FLASH,
296 } rebootTarget_e;
298 typedef struct serialPassthroughPort_e {
299 int id;
300 uint32_t baud;
301 unsigned mode;
302 serialPort_t *port;
303 } serialPassthroughPort_t;
305 static void cliWriterFlush()
307 if (cliWriter) {
308 bufWriterFlush(cliWriter);
313 static void cliPrint(const char *str)
315 if (cliWriter) {
316 while (*str) {
317 bufWriterAppend(cliWriter, *str++);
319 cliWriterFlush();
323 static void cliPrintLinefeed(void)
325 cliPrint("\r\n");
328 static void cliPrintLine(const char *str)
330 cliPrint(str);
331 cliPrintLinefeed();
334 #ifdef MINIMAL_CLI
335 #define cliPrintHashLine(str)
336 #else
337 static void cliPrintHashLine(const char *str)
339 cliPrint("\r\n# ");
340 cliPrintLine(str);
342 #endif
344 static void cliPutp(void *p, char ch)
346 bufWriterAppend(p, ch);
349 static void cliPrintfva(const char *format, va_list va)
351 if (cliWriter) {
352 tfp_format(cliWriter, cliPutp, format, va);
353 cliWriterFlush();
357 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
359 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
360 va_list va;
361 va_start(va, format);
362 cliPrintfva(format, va);
363 va_end(va);
364 cliPrintLinefeed();
365 return true;
366 } else {
367 return false;
371 static void cliWrite(uint8_t ch)
373 if (cliWriter) {
374 bufWriterAppend(cliWriter, ch);
378 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
380 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
381 cliWrite('#');
383 va_list va;
384 va_start(va, format);
385 cliPrintfva(format, va);
386 va_end(va);
387 cliPrintLinefeed();
388 return true;
389 } else {
390 return false;
394 static void cliPrintf(const char *format, ...)
396 va_list va;
397 va_start(va, format);
398 cliPrintfva(format, va);
399 va_end(va);
403 static void cliPrintLinef(const char *format, ...)
405 va_list va;
406 va_start(va, format);
407 cliPrintfva(format, va);
408 va_end(va);
409 cliPrintLinefeed();
412 static void cliPrintErrorVa(const char *format, va_list va)
414 cliPrint("###ERROR: ");
415 cliPrintfva(format, va);
416 va_end(va);
417 cliPrint("###");
419 #ifdef USE_CLI_BATCH
420 if (commandBatchActive) {
421 commandBatchError = true;
423 #endif
426 static void cliPrintError(const char *format, ...)
428 va_list va;
429 va_start(va, format);
430 cliPrintErrorVa(format, va);
433 static void cliPrintErrorLinef(const char *format, ...)
435 va_list va;
436 va_start(va, format);
437 cliPrintErrorVa(format, va);
438 cliPrintLinefeed();
441 static void getMinMax(const clivalue_t *var, int *min, int *max)
443 switch (var->type & VALUE_TYPE_MASK) {
444 case VAR_UINT8:
445 case VAR_UINT16:
446 *min = var->config.minmaxUnsigned.min;
447 *max = var->config.minmaxUnsigned.max;
449 break;
450 default:
451 *min = var->config.minmax.min;
452 *max = var->config.minmax.max;
454 break;
458 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
460 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
461 for (int i = 0; i < var->config.array.length; i++) {
462 switch (var->type & VALUE_TYPE_MASK) {
463 default:
464 case VAR_UINT8:
465 // uint8_t array
466 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
467 break;
469 case VAR_INT8:
470 // int8_t array
471 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
472 break;
474 case VAR_UINT16:
475 // uin16_t array
476 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
477 break;
479 case VAR_INT16:
480 // int16_t array
481 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
482 break;
484 case VAR_UINT32:
485 // uin32_t array
486 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
487 break;
490 if (i < var->config.array.length - 1) {
491 cliPrint(",");
494 } else {
495 int value = 0;
497 switch (var->type & VALUE_TYPE_MASK) {
498 case VAR_UINT8:
499 value = *(uint8_t *)valuePointer;
501 break;
502 case VAR_INT8:
503 value = *(int8_t *)valuePointer;
505 break;
506 case VAR_UINT16:
507 value = *(uint16_t *)valuePointer;
509 break;
510 case VAR_INT16:
511 value = *(int16_t *)valuePointer;
513 break;
514 case VAR_UINT32:
515 value = *(uint32_t *)valuePointer;
517 break;
520 bool valueIsCorrupted = false;
521 switch (var->type & VALUE_MODE_MASK) {
522 case MODE_DIRECT:
523 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
524 cliPrintf("%u", (uint32_t)value);
525 if ((uint32_t)value > var->config.u32Max) {
526 valueIsCorrupted = true;
527 } else if (full) {
528 cliPrintf(" 0 %u", var->config.u32Max);
530 } else {
531 int min;
532 int max;
533 getMinMax(var, &min, &max);
535 cliPrintf("%d", value);
536 if ((value < min) || (value > max)) {
537 valueIsCorrupted = true;
538 } else if (full) {
539 cliPrintf(" %d %d", min, max);
542 break;
543 case MODE_LOOKUP:
544 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
545 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
546 } else {
547 valueIsCorrupted = true;
549 break;
550 case MODE_BITSET:
551 if (value & 1 << var->config.bitpos) {
552 cliPrintf("ON");
553 } else {
554 cliPrintf("OFF");
556 break;
557 case MODE_STRING:
558 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
559 break;
562 if (valueIsCorrupted) {
563 cliPrintLinefeed();
564 cliPrintError("CORRUPTED CONFIG: %s = %d", var->name, value);
570 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
572 bool result = true;
573 int elementCount = 1;
574 uint32_t mask = 0xffffffff;
576 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
577 elementCount = var->config.array.length;
579 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
580 mask = 1 << var->config.bitpos;
582 for (int i = 0; i < elementCount; i++) {
583 switch (var->type & VALUE_TYPE_MASK) {
584 case VAR_UINT8:
585 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
586 break;
588 case VAR_INT8:
589 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
590 break;
592 case VAR_UINT16:
593 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
594 break;
595 case VAR_INT16:
596 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
597 break;
598 case VAR_UINT32:
599 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
600 break;
604 return result;
607 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
609 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
610 cliPrintHashLine(headingStr);
611 return NULL;
612 } else {
613 return headingStr;
617 static void backupPgConfig(const pgRegistry_t *pg)
619 memcpy(pg->copy, pg->address, pg->size);
622 static void restorePgConfig(const pgRegistry_t *pg)
624 memcpy(pg->address, pg->copy, pg->size);
627 static void backupConfigs(void)
629 if (configIsInCopy) {
630 return;
633 // make copies of configs to do differencing
634 PG_FOREACH(pg) {
635 backupPgConfig(pg);
638 configIsInCopy = true;
641 static void restoreConfigs(void)
643 if (!configIsInCopy) {
644 return;
647 PG_FOREACH(pg) {
648 restorePgConfig(pg);
651 configIsInCopy = false;
654 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
655 static bool isReadingConfigFromCopy()
657 return configIsInCopy;
659 #endif
661 static bool isWritingConfigToCopy()
663 return configIsInCopy
664 #if defined(USE_CUSTOM_DEFAULTS)
665 && !processingCustomDefaults
666 #endif
670 #if defined(USE_CUSTOM_DEFAULTS)
671 bool cliProcessCustomDefaults(void);
672 #endif
674 static void backupAndResetConfigs(const bool useCustomDefaults)
676 backupConfigs();
678 // reset all configs to defaults to do differencing
679 resetConfig();
681 #if defined(USE_CUSTOM_DEFAULTS)
682 if (useCustomDefaults) {
683 if (!cliProcessCustomDefaults()) {
684 cliPrintLine("###WARNING: NO CUSTOM DEFAULTS FOUND###");
687 #else
688 UNUSED(useCustomDefaults);
689 #endif
692 static uint8_t getPidProfileIndexToUse()
694 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
697 static uint8_t getRateProfileIndexToUse()
699 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
703 static uint16_t getValueOffset(const clivalue_t *value)
705 switch (value->type & VALUE_SECTION_MASK) {
706 case MASTER_VALUE:
707 case HARDWARE_VALUE:
708 return value->offset;
709 case PROFILE_VALUE:
710 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
711 case PROFILE_RATE_VALUE:
712 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
714 return 0;
717 STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
719 const pgRegistry_t* rec = pgFind(value->pgn);
720 if (isWritingConfigToCopy()) {
721 return CONST_CAST(void *, rec->copy + getValueOffset(value));
722 } else {
723 return CONST_CAST(void *, rec->address + getValueOffset(value));
727 static const char *dumpPgValue(const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
729 const pgRegistry_t *pg = pgFind(value->pgn);
730 #ifdef DEBUG
731 if (!pg) {
732 cliPrintLinef("VALUE %s ERROR", value->name);
733 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
735 #endif
737 const char *format = "set %s = ";
738 const char *defaultFormat = "#set %s = ";
739 const int valueOffset = getValueOffset(value);
740 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
742 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
743 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
744 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
745 cliPrintf(defaultFormat, value->name);
746 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
747 cliPrintLinefeed();
749 cliPrintf(format, value->name);
750 printValuePointer(value, pg->copy + valueOffset, false);
751 cliPrintLinefeed();
753 return headingStr;
756 static void dumpAllValues(uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
758 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
760 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
761 const clivalue_t *value = &valueTable[i];
762 cliWriterFlush();
763 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
764 headingStr = dumpPgValue(value, dumpMask, headingStr);
769 static void cliPrintVar(const clivalue_t *var, bool full)
771 const void *ptr = cliGetValuePointer(var);
773 printValuePointer(var, ptr, full);
776 static void cliPrintVarRange(const clivalue_t *var)
778 switch (var->type & VALUE_MODE_MASK) {
779 case (MODE_DIRECT): {
780 switch (var->type & VALUE_TYPE_MASK) {
781 case VAR_UINT32:
782 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
784 break;
785 case VAR_UINT8:
786 case VAR_UINT16:
787 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
789 break;
790 default:
791 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
793 break;
796 break;
797 case (MODE_LOOKUP): {
798 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
799 cliPrint("Allowed values: ");
800 bool firstEntry = true;
801 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
802 if (tableEntry->values[i]) {
803 if (!firstEntry) {
804 cliPrint(", ");
806 cliPrintf("%s", tableEntry->values[i]);
807 firstEntry = false;
810 cliPrintLinefeed();
812 break;
813 case (MODE_ARRAY): {
814 cliPrintLinef("Array length: %d", var->config.array.length);
816 break;
817 case (MODE_BITSET): {
818 cliPrintLinef("Allowed values: OFF, ON");
820 break;
824 static void cliSetVar(const clivalue_t *var, const uint32_t value)
826 void *ptr = cliGetValuePointer(var);
827 uint32_t workValue;
828 uint32_t mask;
830 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
831 switch (var->type & VALUE_TYPE_MASK) {
832 case VAR_UINT8:
833 mask = (1 << var->config.bitpos) & 0xff;
834 if (value) {
835 workValue = *(uint8_t *)ptr | mask;
836 } else {
837 workValue = *(uint8_t *)ptr & ~mask;
839 *(uint8_t *)ptr = workValue;
840 break;
842 case VAR_UINT16:
843 mask = (1 << var->config.bitpos) & 0xffff;
844 if (value) {
845 workValue = *(uint16_t *)ptr | mask;
846 } else {
847 workValue = *(uint16_t *)ptr & ~mask;
849 *(uint16_t *)ptr = workValue;
850 break;
852 case VAR_UINT32:
853 mask = 1 << var->config.bitpos;
854 if (value) {
855 workValue = *(uint32_t *)ptr | mask;
856 } else {
857 workValue = *(uint32_t *)ptr & ~mask;
859 *(uint32_t *)ptr = workValue;
860 break;
862 } else {
863 switch (var->type & VALUE_TYPE_MASK) {
864 case VAR_UINT8:
865 *(uint8_t *)ptr = value;
866 break;
868 case VAR_INT8:
869 *(int8_t *)ptr = value;
870 break;
872 case VAR_UINT16:
873 *(uint16_t *)ptr = value;
874 break;
876 case VAR_INT16:
877 *(int16_t *)ptr = value;
878 break;
880 case VAR_UINT32:
881 *(uint32_t *)ptr = value;
882 break;
887 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
888 static void cliRepeat(char ch, uint8_t len)
890 if (cliWriter) {
891 for (int i = 0; i < len; i++) {
892 bufWriterAppend(cliWriter, ch);
894 cliPrintLinefeed();
897 #endif
899 static void cliPrompt(void)
901 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
902 if (processingCustomDefaults) {
903 cliPrint("\r\nd: #");
904 } else
905 #endif
907 cliPrint("\r\n# ");
911 static void cliShowParseError(void)
913 cliPrintErrorLinef("PARSE ERROR");
916 static void cliShowArgumentRangeError(char *name, int min, int max)
918 cliPrintErrorLinef("%s NOT BETWEEN %d AND %d", name, min, max);
921 static const char *nextArg(const char *currentArg)
923 const char *ptr = strchr(currentArg, ' ');
924 while (ptr && *ptr == ' ') {
925 ptr++;
928 return ptr;
931 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
933 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
934 ptr = nextArg(ptr);
935 if (ptr) {
936 int val = atoi(ptr);
937 val = CHANNEL_VALUE_TO_STEP(val);
938 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
939 if (argIndex == 0) {
940 range->startStep = val;
941 } else {
942 range->endStep = val;
944 (*validArgumentCount)++;
949 return ptr;
952 // Check if a string's length is zero
953 static bool isEmpty(const char *string)
955 return (string == NULL || *string == '\0') ? true : false;
958 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
960 // print out rxConfig failsafe settings
961 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
962 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
963 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
964 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
965 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
966 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
967 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
968 if (requireValue) {
969 const char *format = "rxfail %u %c %d";
970 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
971 channel,
972 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
973 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
975 cliDumpPrintLinef(dumpMask, equalsDefault, format,
976 channel,
977 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
978 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
980 } else {
981 const char *format = "rxfail %u %c";
982 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
983 channel,
984 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
986 cliDumpPrintLinef(dumpMask, equalsDefault, format,
987 channel,
988 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
994 static void cliRxFailsafe(char *cmdline)
996 uint8_t channel;
997 char buf[3];
999 if (isEmpty(cmdline)) {
1000 // print out rxConfig failsafe settings
1001 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1002 cliRxFailsafe(itoa(channel, buf, 10));
1004 } else {
1005 const char *ptr = cmdline;
1006 channel = atoi(ptr++);
1007 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
1009 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
1011 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
1012 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
1013 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1015 ptr = nextArg(ptr);
1016 if (ptr) {
1017 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
1018 if (p) {
1019 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
1020 mode = rxFailsafeModesTable[type][requestedMode];
1021 } else {
1022 mode = RX_FAILSAFE_MODE_INVALID;
1024 if (mode == RX_FAILSAFE_MODE_INVALID) {
1025 cliShowParseError();
1026 return;
1029 requireValue = mode == RX_FAILSAFE_MODE_SET;
1031 ptr = nextArg(ptr);
1032 if (ptr) {
1033 if (!requireValue) {
1034 cliShowParseError();
1035 return;
1037 uint16_t value = atoi(ptr);
1038 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
1039 if (value > MAX_RXFAIL_RANGE_STEP) {
1040 cliPrintLine("Value out of range");
1041 return;
1044 channelFailsafeConfig->step = value;
1045 } else if (requireValue) {
1046 cliShowParseError();
1047 return;
1049 channelFailsafeConfig->mode = mode;
1052 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
1054 // double use of cliPrintf below
1055 // 1. acknowledge interpretation on command,
1056 // 2. query current setting on single item,
1058 if (requireValue) {
1059 cliPrintLinef("rxfail %u %c %d",
1060 channel,
1061 modeCharacter,
1062 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1064 } else {
1065 cliPrintLinef("rxfail %u %c",
1066 channel,
1067 modeCharacter
1070 } else {
1071 cliShowArgumentRangeError("CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1076 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1078 const char *format = "aux %u %u %u %u %u %u %u";
1079 // print out aux channel settings
1080 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1081 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1082 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1083 bool equalsDefault = false;
1084 if (defaultModeActivationConditions) {
1085 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1086 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1087 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1088 const box_t *box = findBoxByBoxId(macDefault->modeId);
1089 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1090 if (box) {
1091 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1093 box->permanentId,
1094 macDefault->auxChannelIndex,
1095 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1096 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1097 macDefault->modeLogic,
1098 linkedTo ? linkedTo->permanentId : 0
1102 const box_t *box = findBoxByBoxId(mac->modeId);
1103 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1104 if (box) {
1105 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1107 box->permanentId,
1108 mac->auxChannelIndex,
1109 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1110 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1111 mac->modeLogic,
1112 linkedTo ? linkedTo->permanentId : 0
1118 static void cliAux(char *cmdline)
1120 int i, val = 0;
1121 const char *ptr;
1123 if (isEmpty(cmdline)) {
1124 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1125 } else {
1126 ptr = cmdline;
1127 i = atoi(ptr++);
1128 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1129 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1130 uint8_t validArgumentCount = 0;
1131 ptr = nextArg(ptr);
1132 if (ptr) {
1133 val = atoi(ptr);
1134 const box_t *box = findBoxByPermanentId(val);
1135 if (box) {
1136 mac->modeId = box->boxId;
1137 validArgumentCount++;
1140 ptr = nextArg(ptr);
1141 if (ptr) {
1142 val = atoi(ptr);
1143 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1144 mac->auxChannelIndex = val;
1145 validArgumentCount++;
1148 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1149 ptr = nextArg(ptr);
1150 if (ptr) {
1151 val = atoi(ptr);
1152 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1153 mac->modeLogic = val;
1154 validArgumentCount++;
1157 ptr = nextArg(ptr);
1158 if (ptr) {
1159 val = atoi(ptr);
1160 const box_t *box = findBoxByPermanentId(val);
1161 if (box) {
1162 mac->linkedTo = box->boxId;
1163 validArgumentCount++;
1166 if (validArgumentCount == 4) { // for backwards compatibility
1167 mac->modeLogic = MODELOGIC_OR;
1168 mac->linkedTo = 0;
1169 } else if (validArgumentCount == 5) { // for backwards compatibility
1170 mac->linkedTo = 0;
1171 } else if (validArgumentCount != 6) {
1172 memset(mac, 0, sizeof(modeActivationCondition_t));
1174 analyzeModeActivationConditions();
1175 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1177 findBoxByBoxId(mac->modeId)->permanentId,
1178 mac->auxChannelIndex,
1179 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1180 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1181 mac->modeLogic,
1182 findBoxByBoxId(mac->linkedTo)->permanentId
1184 } else {
1185 cliShowArgumentRangeError("INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1190 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1192 const char *format = "serial %d %d %ld %ld %ld %ld";
1193 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1194 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
1195 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1196 continue;
1198 bool equalsDefault = false;
1199 if (serialConfigDefault) {
1200 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1201 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1202 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1203 serialConfigDefault->portConfigs[i].identifier,
1204 serialConfigDefault->portConfigs[i].functionMask,
1205 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1206 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1207 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1208 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1211 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1212 serialConfig->portConfigs[i].identifier,
1213 serialConfig->portConfigs[i].functionMask,
1214 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1215 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1216 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1217 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1222 static void cliSerial(char *cmdline)
1224 const char *format = "serial %d %d %ld %ld %ld %ld";
1225 if (isEmpty(cmdline)) {
1226 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1227 return;
1229 serialPortConfig_t portConfig;
1230 memset(&portConfig, 0 , sizeof(portConfig));
1232 serialPortConfig_t *currentConfig;
1234 uint8_t validArgumentCount = 0;
1236 const char *ptr = cmdline;
1238 int val = atoi(ptr++);
1239 currentConfig = serialFindPortConfiguration(val);
1240 if (currentConfig) {
1241 portConfig.identifier = val;
1242 validArgumentCount++;
1245 ptr = nextArg(ptr);
1246 if (ptr) {
1247 val = atoi(ptr);
1248 portConfig.functionMask = val & 0xFFFF;
1249 validArgumentCount++;
1252 for (int i = 0; i < 4; i ++) {
1253 ptr = nextArg(ptr);
1254 if (!ptr) {
1255 break;
1258 val = atoi(ptr);
1260 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1261 if (baudRates[baudRateIndex] != (uint32_t) val) {
1262 break;
1265 switch (i) {
1266 case 0:
1267 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1268 continue;
1270 portConfig.msp_baudrateIndex = baudRateIndex;
1271 break;
1272 case 1:
1273 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1274 continue;
1276 portConfig.gps_baudrateIndex = baudRateIndex;
1277 break;
1278 case 2:
1279 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1280 continue;
1282 portConfig.telemetry_baudrateIndex = baudRateIndex;
1283 break;
1284 case 3:
1285 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1286 continue;
1288 portConfig.blackbox_baudrateIndex = baudRateIndex;
1289 break;
1292 validArgumentCount++;
1295 if (validArgumentCount < 6) {
1296 cliShowParseError();
1297 return;
1300 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1302 cliDumpPrintLinef(0, false, format,
1303 portConfig.identifier,
1304 portConfig.functionMask,
1305 baudRates[portConfig.msp_baudrateIndex],
1306 baudRates[portConfig.gps_baudrateIndex],
1307 baudRates[portConfig.telemetry_baudrateIndex],
1308 baudRates[portConfig.blackbox_baudrateIndex]
1313 #if defined(USE_SERIAL_PASSTHROUGH)
1314 static void cbCtrlLine(void *context, uint16_t ctrl)
1316 #ifdef USE_PINIO
1317 int contextValue = (int)(long)context;
1318 if (contextValue) {
1319 pinioSet(contextValue - 1, !(ctrl & CTRL_LINE_STATE_DTR));
1320 } else
1321 #endif /* USE_PINIO */
1322 UNUSED(context);
1324 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1325 systemReset();
1329 static int cliParseSerialMode(const char *tok)
1331 int mode = 0;
1333 if (strcasestr(tok, "rx")) {
1334 mode |= MODE_RX;
1336 if (strcasestr(tok, "tx")) {
1337 mode |= MODE_TX;
1340 return mode;
1343 static void cliSerialPassthrough(char *cmdline)
1345 if (isEmpty(cmdline)) {
1346 cliShowParseError();
1347 return;
1350 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, NULL}, {cliPort->identifier, 0, 0, cliPort} };
1351 bool enableBaudCb = false;
1352 int port1PinioDtr = 0;
1353 bool port1ResetOnDtr = false;
1354 bool escSensorPassthrough = false;
1355 char *saveptr;
1356 char* tok = strtok_r(cmdline, " ", &saveptr);
1357 int index = 0;
1359 while (tok != NULL) {
1360 switch (index) {
1361 case 0:
1362 if (strcasestr(tok, "esc_sensor")) {
1363 escSensorPassthrough = true;
1364 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1365 ports[0].id = portConfig->identifier;
1366 } else {
1367 ports[0].id = atoi(tok);
1369 break;
1370 case 1:
1371 ports[0].baud = atoi(tok);
1372 break;
1373 case 2:
1374 ports[0].mode = cliParseSerialMode(tok);
1375 break;
1376 case 3:
1377 if (strncasecmp(tok, "reset", strlen(tok)) == 0) {
1378 port1ResetOnDtr = true;
1379 #ifdef USE_PINIO
1380 } else if (strncasecmp(tok, "none", strlen(tok)) == 0) {
1381 port1PinioDtr = 0;
1382 } else {
1383 port1PinioDtr = atoi(tok);
1384 if (port1PinioDtr < 0 || port1PinioDtr > PINIO_COUNT) {
1385 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1386 return ;
1388 #endif /* USE_PINIO */
1390 break;
1391 case 4:
1392 ports[1].id = atoi(tok);
1393 ports[1].port = NULL;
1394 break;
1395 case 5:
1396 ports[1].baud = atoi(tok);
1397 break;
1398 case 6:
1399 ports[1].mode = cliParseSerialMode(tok);
1400 break;
1402 index++;
1403 tok = strtok_r(NULL, " ", &saveptr);
1406 // Port checks
1407 if (ports[0].id == ports[1].id) {
1408 cliPrintLinef("Port1 and port2 are same");
1409 return ;
1412 for (int i = 0; i < 2; i++) {
1413 if (findSerialPortIndexByIdentifier(ports[i].id) == -1) {
1414 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1415 return ;
1416 } else {
1417 cliPrintLinef("Port%d: %d ", i + 1, ports[i].id);
1421 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1422 enableBaudCb = true;
1425 for (int i = 0; i < 2; i++) {
1426 serialPort_t **port = &(ports[i].port);
1427 if (*port != NULL) {
1428 continue;
1431 int portIndex = i + 1;
1432 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(ports[i].id);
1433 if (!portUsage || portUsage->serialPort == NULL) {
1434 bool isUseDefaultBaud = false;
1435 if (ports[i].baud == 0) {
1436 // Set default baud
1437 ports[i].baud = 57600;
1438 isUseDefaultBaud = true;
1441 if (!ports[i].mode) {
1442 ports[i].mode = MODE_RXTX;
1445 *port = openSerialPort(ports[i].id, FUNCTION_NONE, NULL, NULL,
1446 ports[i].baud, ports[i].mode,
1447 SERIAL_NOT_INVERTED);
1448 if (!*port) {
1449 cliPrintLinef("Port%d could not be opened.", portIndex);
1450 return;
1453 if (isUseDefaultBaud) {
1454 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex, ports[i].baud);
1455 } else {
1456 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex, ports[i].baud);
1458 } else {
1459 *port = portUsage->serialPort;
1460 // If the user supplied a mode, override the port's mode, otherwise
1461 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1462 // Set the baud rate if specified
1463 if (ports[i].baud) {
1464 cliPrintf("Port%d is already open, setting baud = %d.\n\r", portIndex, ports[i].baud);
1465 serialSetBaudRate(*port, ports[i].baud);
1466 } else {
1467 cliPrintf("Port%d is already open, baud = %d.\n\r", portIndex, (*port)->baudRate);
1470 if (ports[i].mode && (*port)->mode != ports[i].mode) {
1471 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1472 portIndex, (*port)->mode, ports[i].mode);
1473 serialSetMode(*port, ports[i].mode);
1476 // If this port has a rx callback associated we need to remove it now.
1477 // Otherwise no data will be pushed in the serial port buffer!
1478 if ((*port)->rxCallback) {
1479 (*port)->rxCallback = NULL;
1484 // If no baud rate is specified allow to be set via USB
1485 if (enableBaudCb) {
1486 cliPrintLine("Port1 baud rate change over USB enabled.");
1487 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1488 // baud rate over USB without setting it using the serialpassthrough command
1489 serialSetBaudRateCb(ports[0].port, serialSetBaudRate, ports[1].port);
1492 char *resetMessage = "";
1493 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1494 resetMessage = "or drop DTR ";
1497 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1499 if ((ports[1].id == SERIAL_PORT_USB_VCP) && (port1ResetOnDtr
1500 #ifdef USE_PINIO
1501 || port1PinioDtr
1502 #endif /* USE_PINIO */
1503 )) {
1504 // Register control line state callback
1505 serialSetCtrlLineStateCb(ports[0].port, cbCtrlLine, (void *)(intptr_t)(port1PinioDtr));
1508 // XXX Review ESC pass through under refactored motor handling
1509 #ifdef USE_PWM_OUTPUT
1510 if (escSensorPassthrough) {
1511 // pwmDisableMotors();
1512 motorDisable();
1513 delay(5);
1514 unsigned motorsCount = getMotorCount();
1515 for (unsigned i = 0; i < motorsCount; i++) {
1516 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1517 if (tag) {
1518 const timerHardware_t *timerHardware = timerGetByTag(tag);
1519 if (timerHardware) {
1520 IO_t io = IOGetByTag(tag);
1521 IOInit(io, OWNER_MOTOR, 0);
1522 IOConfigGPIO(io, IOCFG_OUT_PP);
1523 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1524 IOLo(io);
1525 } else {
1526 IOHi(io);
1532 #endif
1534 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1536 #endif
1538 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1540 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1541 // print out adjustment ranges channel settings
1542 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1543 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1544 const adjustmentRange_t *ar = &adjustmentRanges[i];
1545 bool equalsDefault = false;
1546 if (defaultAdjustmentRanges) {
1547 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1548 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1549 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1550 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1552 arDefault->auxChannelIndex,
1553 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1554 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1555 arDefault->adjustmentConfig,
1556 arDefault->auxSwitchChannelIndex,
1557 arDefault->adjustmentCenter,
1558 arDefault->adjustmentScale
1561 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1563 ar->auxChannelIndex,
1564 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1565 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1566 ar->adjustmentConfig,
1567 ar->auxSwitchChannelIndex,
1568 ar->adjustmentCenter,
1569 ar->adjustmentScale
1574 static void cliAdjustmentRange(char *cmdline)
1576 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1577 int i, val = 0;
1578 const char *ptr;
1580 if (isEmpty(cmdline)) {
1581 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1582 } else {
1583 ptr = cmdline;
1584 i = atoi(ptr++);
1585 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1586 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1587 uint8_t validArgumentCount = 0;
1589 ptr = nextArg(ptr);
1590 if (ptr) {
1591 val = atoi(ptr);
1592 // Was: slot
1593 // Keeping the parameter to retain backwards compatibility for the command format.
1594 validArgumentCount++;
1596 ptr = nextArg(ptr);
1597 if (ptr) {
1598 val = atoi(ptr);
1599 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1600 ar->auxChannelIndex = val;
1601 validArgumentCount++;
1605 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1607 ptr = nextArg(ptr);
1608 if (ptr) {
1609 val = atoi(ptr);
1610 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1611 ar->adjustmentConfig = val;
1612 validArgumentCount++;
1615 ptr = nextArg(ptr);
1616 if (ptr) {
1617 val = atoi(ptr);
1618 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1619 ar->auxSwitchChannelIndex = val;
1620 validArgumentCount++;
1624 if (validArgumentCount != 6) {
1625 memset(ar, 0, sizeof(adjustmentRange_t));
1626 cliShowParseError();
1627 return;
1630 // Optional arguments
1631 ar->adjustmentCenter = 0;
1632 ar->adjustmentScale = 0;
1634 ptr = nextArg(ptr);
1635 if (ptr) {
1636 val = atoi(ptr);
1637 ar->adjustmentCenter = val;
1638 validArgumentCount++;
1640 ptr = nextArg(ptr);
1641 if (ptr) {
1642 val = atoi(ptr);
1643 ar->adjustmentScale = val;
1644 validArgumentCount++;
1647 activeAdjustmentRangeReset();
1649 cliDumpPrintLinef(0, false, format,
1651 ar->auxChannelIndex,
1652 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1653 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1654 ar->adjustmentConfig,
1655 ar->auxSwitchChannelIndex,
1656 ar->adjustmentCenter,
1657 ar->adjustmentScale
1660 } else {
1661 cliShowArgumentRangeError("INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1666 #ifndef USE_QUAD_MIXER_ONLY
1667 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1669 const char *format = "mmix %d %s %s %s %s";
1670 char buf0[FTOA_BUFFER_LENGTH];
1671 char buf1[FTOA_BUFFER_LENGTH];
1672 char buf2[FTOA_BUFFER_LENGTH];
1673 char buf3[FTOA_BUFFER_LENGTH];
1674 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1675 if (customMotorMixer[i].throttle == 0.0f)
1676 break;
1677 const float thr = customMotorMixer[i].throttle;
1678 const float roll = customMotorMixer[i].roll;
1679 const float pitch = customMotorMixer[i].pitch;
1680 const float yaw = customMotorMixer[i].yaw;
1681 bool equalsDefault = false;
1682 if (defaultCustomMotorMixer) {
1683 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1684 const float rollDefault = defaultCustomMotorMixer[i].roll;
1685 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1686 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1687 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1689 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1690 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1692 ftoa(thrDefault, buf0),
1693 ftoa(rollDefault, buf1),
1694 ftoa(pitchDefault, buf2),
1695 ftoa(yawDefault, buf3));
1697 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1699 ftoa(thr, buf0),
1700 ftoa(roll, buf1),
1701 ftoa(pitch, buf2),
1702 ftoa(yaw, buf3));
1705 #endif // USE_QUAD_MIXER_ONLY
1707 static void cliMotorMix(char *cmdline)
1709 #ifdef USE_QUAD_MIXER_ONLY
1710 UNUSED(cmdline);
1711 #else
1712 int check = 0;
1713 uint8_t len;
1714 const char *ptr;
1716 if (isEmpty(cmdline)) {
1717 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1718 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1719 // erase custom mixer
1720 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1721 customMotorMixerMutable(i)->throttle = 0.0f;
1723 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1724 ptr = nextArg(cmdline);
1725 if (ptr) {
1726 len = strlen(ptr);
1727 for (uint32_t i = 0; ; i++) {
1728 if (mixerNames[i] == NULL) {
1729 cliPrintErrorLinef("INVALID NAME");
1730 break;
1732 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1733 mixerLoadMix(i, customMotorMixerMutable(0));
1734 cliPrintLinef("Loaded %s", mixerNames[i]);
1735 cliMotorMix("");
1736 break;
1740 } else {
1741 ptr = cmdline;
1742 uint32_t i = atoi(ptr); // get motor number
1743 if (i < MAX_SUPPORTED_MOTORS) {
1744 ptr = nextArg(ptr);
1745 if (ptr) {
1746 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1747 check++;
1749 ptr = nextArg(ptr);
1750 if (ptr) {
1751 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1752 check++;
1754 ptr = nextArg(ptr);
1755 if (ptr) {
1756 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1757 check++;
1759 ptr = nextArg(ptr);
1760 if (ptr) {
1761 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1762 check++;
1764 if (check != 4) {
1765 cliShowParseError();
1766 } else {
1767 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1769 } else {
1770 cliShowArgumentRangeError("INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1773 #endif
1776 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1778 const char *format = "rxrange %u %u %u";
1779 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1780 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1781 bool equalsDefault = false;
1782 if (defaultChannelRangeConfigs) {
1783 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1784 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1785 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1787 defaultChannelRangeConfigs[i].min,
1788 defaultChannelRangeConfigs[i].max
1791 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1793 channelRangeConfigs[i].min,
1794 channelRangeConfigs[i].max
1799 static void cliRxRange(char *cmdline)
1801 const char *format = "rxrange %u %u %u";
1802 int i, validArgumentCount = 0;
1803 const char *ptr;
1805 if (isEmpty(cmdline)) {
1806 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1807 } else if (strcasecmp(cmdline, "reset") == 0) {
1808 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1809 } else {
1810 ptr = cmdline;
1811 i = atoi(ptr);
1812 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1813 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1815 ptr = nextArg(ptr);
1816 if (ptr) {
1817 rangeMin = atoi(ptr);
1818 validArgumentCount++;
1821 ptr = nextArg(ptr);
1822 if (ptr) {
1823 rangeMax = atoi(ptr);
1824 validArgumentCount++;
1827 if (validArgumentCount != 2) {
1828 cliShowParseError();
1829 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1830 cliShowParseError();
1831 } else {
1832 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1833 channelRangeConfig->min = rangeMin;
1834 channelRangeConfig->max = rangeMax;
1835 cliDumpPrintLinef(0, false, format,
1837 channelRangeConfig->min,
1838 channelRangeConfig->max
1842 } else {
1843 cliShowArgumentRangeError("CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1848 #ifdef USE_LED_STRIP_STATUS_MODE
1849 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1851 const char *format = "led %u %s";
1852 char ledConfigBuffer[20];
1853 char ledConfigDefaultBuffer[20];
1854 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1855 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1856 ledConfig_t ledConfig = ledConfigs[i];
1857 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1858 bool equalsDefault = false;
1859 if (defaultLedConfigs) {
1860 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1861 equalsDefault = ledConfig == ledConfigDefault;
1862 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1863 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1864 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1866 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1870 static void cliLed(char *cmdline)
1872 const char *format = "led %u %s";
1873 char ledConfigBuffer[20];
1874 int i;
1875 const char *ptr;
1877 if (isEmpty(cmdline)) {
1878 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
1879 } else {
1880 ptr = cmdline;
1881 i = atoi(ptr);
1882 if (i < LED_MAX_STRIP_LENGTH) {
1883 ptr = nextArg(cmdline);
1884 if (parseLedStripConfig(i, ptr)) {
1885 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1886 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1887 } else {
1888 cliShowParseError();
1890 } else {
1891 cliShowArgumentRangeError("INDEX", 0, LED_MAX_STRIP_LENGTH - 1);
1896 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
1898 const char *format = "color %u %d,%u,%u";
1899 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1900 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1901 const hsvColor_t *color = &colors[i];
1902 bool equalsDefault = false;
1903 if (defaultColors) {
1904 const hsvColor_t *colorDefault = &defaultColors[i];
1905 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1906 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1907 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1909 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1913 static void cliColor(char *cmdline)
1915 const char *format = "color %u %d,%u,%u";
1916 if (isEmpty(cmdline)) {
1917 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
1918 } else {
1919 const char *ptr = cmdline;
1920 const int i = atoi(ptr);
1921 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1922 ptr = nextArg(cmdline);
1923 if (parseColor(i, ptr)) {
1924 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
1925 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1926 } else {
1927 cliShowParseError();
1929 } else {
1930 cliShowArgumentRangeError("INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1935 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
1937 const char *format = "mode_color %u %u %u";
1938 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1939 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1940 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1941 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
1942 bool equalsDefault = false;
1943 if (defaultLedStripConfig) {
1944 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1945 equalsDefault = colorIndex == colorIndexDefault;
1946 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1947 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1949 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1953 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1954 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
1955 bool equalsDefault = false;
1956 if (defaultLedStripConfig) {
1957 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1958 equalsDefault = colorIndex == colorIndexDefault;
1959 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1960 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1962 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1965 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
1966 bool equalsDefault = false;
1967 if (defaultLedStripConfig) {
1968 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1969 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1970 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1971 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1973 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1976 static void cliModeColor(char *cmdline)
1978 if (isEmpty(cmdline)) {
1979 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
1980 } else {
1981 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1982 int args[ARGS_COUNT];
1983 int argNo = 0;
1984 char *saveptr;
1985 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1986 while (ptr && argNo < ARGS_COUNT) {
1987 args[argNo++] = atoi(ptr);
1988 ptr = strtok_r(NULL, " ", &saveptr);
1991 if (ptr != NULL || argNo != ARGS_COUNT) {
1992 cliShowParseError();
1993 return;
1996 int modeIdx = args[MODE];
1997 int funIdx = args[FUNCTION];
1998 int color = args[COLOR];
1999 if (!setModeColor(modeIdx, funIdx, color)) {
2000 cliShowParseError();
2001 return;
2003 // values are validated
2004 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
2007 #endif
2009 #ifdef USE_SERVOS
2010 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
2012 // print out servo settings
2013 const char *format = "servo %u %d %d %d %d %d";
2014 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2015 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2016 const servoParam_t *servoConf = &servoParams[i];
2017 bool equalsDefault = false;
2018 if (defaultServoParams) {
2019 const servoParam_t *defaultServoConf = &defaultServoParams[i];
2020 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
2021 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2022 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2024 defaultServoConf->min,
2025 defaultServoConf->max,
2026 defaultServoConf->middle,
2027 defaultServoConf->rate,
2028 defaultServoConf->forwardFromChannel
2031 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2033 servoConf->min,
2034 servoConf->max,
2035 servoConf->middle,
2036 servoConf->rate,
2037 servoConf->forwardFromChannel
2040 // print servo directions
2041 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2042 const char *format = "smix reverse %d %d r";
2043 const servoParam_t *servoConf = &servoParams[i];
2044 const servoParam_t *servoConfDefault = &defaultServoParams[i];
2045 if (defaultServoParams) {
2046 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
2047 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2048 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
2049 if (servoConfDefault->reversedSources & (1 << channel)) {
2050 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
2052 if (servoConf->reversedSources & (1 << channel)) {
2053 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
2056 } else {
2057 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2058 if (servoConf->reversedSources & (1 << channel)) {
2059 cliDumpPrintLinef(dumpMask, true, format, i , channel);
2066 static void cliServo(char *cmdline)
2068 const char *format = "servo %u %d %d %d %d %d";
2069 enum { SERVO_ARGUMENT_COUNT = 6 };
2070 int16_t arguments[SERVO_ARGUMENT_COUNT];
2072 servoParam_t *servo;
2074 int i;
2075 char *ptr;
2077 if (isEmpty(cmdline)) {
2078 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2079 } else {
2080 int validArgumentCount = 0;
2082 ptr = cmdline;
2084 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2086 // If command line doesn't fit the format, don't modify the config
2087 while (*ptr) {
2088 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2089 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2090 cliShowParseError();
2091 return;
2094 arguments[validArgumentCount++] = atoi(ptr);
2096 do {
2097 ptr++;
2098 } while (*ptr >= '0' && *ptr <= '9');
2099 } else if (*ptr == ' ') {
2100 ptr++;
2101 } else {
2102 cliShowParseError();
2103 return;
2107 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2109 i = arguments[INDEX];
2111 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2112 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2113 cliShowParseError();
2114 return;
2117 servo = servoParamsMutable(i);
2119 if (
2120 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
2121 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
2122 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2123 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
2124 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2125 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2127 cliShowParseError();
2128 return;
2131 servo->min = arguments[MIN];
2132 servo->max = arguments[MAX];
2133 servo->middle = arguments[MIDDLE];
2134 servo->rate = arguments[RATE];
2135 servo->forwardFromChannel = arguments[FORWARD];
2137 cliDumpPrintLinef(0, false, format,
2139 servo->min,
2140 servo->max,
2141 servo->middle,
2142 servo->rate,
2143 servo->forwardFromChannel
2148 #endif
2150 #ifdef USE_SERVOS
2151 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2153 const char *format = "smix %d %d %d %d %d %d %d %d";
2154 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2155 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2156 const servoMixer_t customServoMixer = customServoMixers[i];
2157 if (customServoMixer.rate == 0) {
2158 break;
2161 bool equalsDefault = false;
2162 if (defaultCustomServoMixers) {
2163 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2164 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2166 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2167 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2169 customServoMixerDefault.targetChannel,
2170 customServoMixerDefault.inputSource,
2171 customServoMixerDefault.rate,
2172 customServoMixerDefault.speed,
2173 customServoMixerDefault.min,
2174 customServoMixerDefault.max,
2175 customServoMixerDefault.box
2178 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2180 customServoMixer.targetChannel,
2181 customServoMixer.inputSource,
2182 customServoMixer.rate,
2183 customServoMixer.speed,
2184 customServoMixer.min,
2185 customServoMixer.max,
2186 customServoMixer.box
2191 static void cliServoMix(char *cmdline)
2193 int args[8], check = 0;
2194 int len = strlen(cmdline);
2196 if (len == 0) {
2197 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2198 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2199 // erase custom mixer
2200 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2201 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2202 servoParamsMutable(i)->reversedSources = 0;
2204 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2205 const char *ptr = nextArg(cmdline);
2206 if (ptr) {
2207 len = strlen(ptr);
2208 for (uint32_t i = 0; ; i++) {
2209 if (mixerNames[i] == NULL) {
2210 cliPrintErrorLinef("INVALID NAME");
2211 break;
2213 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2214 servoMixerLoadMix(i);
2215 cliPrintLinef("Loaded %s", mixerNames[i]);
2216 cliServoMix("");
2217 break;
2221 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2222 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2223 char *ptr = strchr(cmdline, ' ');
2225 if (ptr == NULL) {
2226 cliPrintf("s");
2227 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2228 cliPrintf("\ti%d", inputSource);
2229 cliPrintLinefeed();
2231 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2232 cliPrintf("%d", servoIndex);
2233 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2234 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2236 cliPrintLinefeed();
2238 return;
2241 char *saveptr;
2242 ptr = strtok_r(ptr, " ", &saveptr);
2243 while (ptr != NULL && check < ARGS_COUNT - 1) {
2244 args[check++] = atoi(ptr);
2245 ptr = strtok_r(NULL, " ", &saveptr);
2248 if (ptr == NULL || check != ARGS_COUNT - 1) {
2249 cliShowParseError();
2250 return;
2253 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2254 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2255 && (*ptr == 'r' || *ptr == 'n')) {
2256 if (*ptr == 'r') {
2257 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2258 } else {
2259 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2261 } else {
2262 cliShowParseError();
2263 return;
2266 cliServoMix("reverse");
2267 } else {
2268 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2269 char *saveptr;
2270 char *ptr = strtok_r(cmdline, " ", &saveptr);
2271 while (ptr != NULL && check < ARGS_COUNT) {
2272 args[check++] = atoi(ptr);
2273 ptr = strtok_r(NULL, " ", &saveptr);
2276 if (ptr != NULL || check != ARGS_COUNT) {
2277 cliShowParseError();
2278 return;
2281 int32_t i = args[RULE];
2282 if (i >= 0 && i < MAX_SERVO_RULES &&
2283 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2284 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2285 args[RATE] >= -100 && args[RATE] <= 100 &&
2286 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2287 args[MIN] >= 0 && args[MIN] <= 100 &&
2288 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2289 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2290 customServoMixersMutable(i)->targetChannel = args[TARGET];
2291 customServoMixersMutable(i)->inputSource = args[INPUT];
2292 customServoMixersMutable(i)->rate = args[RATE];
2293 customServoMixersMutable(i)->speed = args[SPEED];
2294 customServoMixersMutable(i)->min = args[MIN];
2295 customServoMixersMutable(i)->max = args[MAX];
2296 customServoMixersMutable(i)->box = args[BOX];
2297 cliServoMix("");
2298 } else {
2299 cliShowParseError();
2303 #endif
2305 #ifdef USE_SDCARD
2307 static void cliWriteBytes(const uint8_t *buffer, int count)
2309 while (count > 0) {
2310 cliWrite(*buffer);
2311 buffer++;
2312 count--;
2316 static void cliSdInfo(char *cmdline)
2318 UNUSED(cmdline);
2320 cliPrint("SD card: ");
2322 if (!sdcard_isInserted()) {
2323 cliPrintLine("None inserted");
2324 return;
2327 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2328 cliPrintLine("Startup failed");
2329 return;
2332 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2334 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2335 metadata->manufacturerID,
2336 metadata->numBlocks / 2, /* One block is half a kB */
2337 metadata->productionMonth,
2338 metadata->productionYear,
2339 metadata->productRevisionMajor,
2340 metadata->productRevisionMinor
2343 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2345 cliPrint("'\r\n" "Filesystem: ");
2347 switch (afatfs_getFilesystemState()) {
2348 case AFATFS_FILESYSTEM_STATE_READY:
2349 cliPrint("Ready");
2350 break;
2351 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2352 cliPrint("Initializing");
2353 break;
2354 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2355 case AFATFS_FILESYSTEM_STATE_FATAL:
2356 cliPrint("Fatal");
2358 switch (afatfs_getLastError()) {
2359 case AFATFS_ERROR_BAD_MBR:
2360 cliPrint(" - no FAT MBR partitions");
2361 break;
2362 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2363 cliPrint(" - bad FAT header");
2364 break;
2365 case AFATFS_ERROR_GENERIC:
2366 case AFATFS_ERROR_NONE:
2367 ; // Nothing more detailed to print
2368 break;
2370 break;
2372 cliPrintLinefeed();
2375 #endif
2377 #ifdef USE_FLASH_CHIP
2379 static void cliFlashInfo(char *cmdline)
2381 const flashGeometry_t *layout = flashGetGeometry();
2383 UNUSED(cmdline);
2385 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2386 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2388 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2389 const flashPartition_t *partition;
2390 if (index == 0) {
2391 cliPrintLine("Paritions:");
2393 partition = flashPartitionFindByIndex(index);
2394 if (!partition) {
2395 break;
2397 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2399 #ifdef USE_FLASHFS
2400 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2402 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2403 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2404 flashfsGetOffset()
2406 #endif
2410 static void cliFlashErase(char *cmdline)
2412 UNUSED(cmdline);
2414 if (!flashfsIsSupported()) {
2415 return;
2418 #ifndef MINIMAL_CLI
2419 uint32_t i = 0;
2420 cliPrintLine("Erasing, please wait ... ");
2421 #else
2422 cliPrintLine("Erasing,");
2423 #endif
2425 cliWriterFlush();
2426 flashfsEraseCompletely();
2428 while (!flashfsIsReady()) {
2429 #ifndef MINIMAL_CLI
2430 cliPrintf(".");
2431 if (i++ > 120) {
2432 i=0;
2433 cliPrintLinefeed();
2436 cliWriterFlush();
2437 #endif
2438 delay(100);
2440 beeper(BEEPER_BLACKBOX_ERASE);
2441 cliPrintLinefeed();
2442 cliPrintLine("Done.");
2445 #ifdef USE_FLASH_TOOLS
2447 static void cliFlashVerify(char *cmdline)
2449 UNUSED(cmdline);
2451 cliPrintLine("Verifying");
2452 if (flashfsVerifyEntireFlash()) {
2453 cliPrintLine("Success");
2454 } else {
2455 cliPrintLine("Failed");
2459 static void cliFlashWrite(char *cmdline)
2461 const uint32_t address = atoi(cmdline);
2462 const char *text = strchr(cmdline, ' ');
2464 if (!text) {
2465 cliShowParseError();
2466 } else {
2467 flashfsSeekAbs(address);
2468 flashfsWrite((uint8_t*)text, strlen(text), true);
2469 flashfsFlushSync();
2471 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2475 static void cliFlashRead(char *cmdline)
2477 uint32_t address = atoi(cmdline);
2479 const char *nextArg = strchr(cmdline, ' ');
2481 if (!nextArg) {
2482 cliShowParseError();
2483 } else {
2484 uint32_t length = atoi(nextArg);
2486 cliPrintLinef("Reading %u bytes at %u:", length, address);
2488 uint8_t buffer[32];
2489 while (length > 0) {
2490 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2492 for (int i = 0; i < bytesRead; i++) {
2493 cliWrite(buffer[i]);
2496 length -= bytesRead;
2497 address += bytesRead;
2499 if (bytesRead == 0) {
2500 //Assume we reached the end of the volume or something fatal happened
2501 break;
2504 cliPrintLinefeed();
2508 #endif
2509 #endif
2511 #ifdef USE_VTX_CONTROL
2512 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2514 // print out vtx channel settings
2515 const char *format = "vtx %u %u %u %u %u %u %u";
2516 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2517 bool equalsDefault = false;
2518 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2519 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2520 if (vtxConfigDefault) {
2521 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2522 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2523 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2524 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2526 cacDefault->auxChannelIndex,
2527 cacDefault->band,
2528 cacDefault->channel,
2529 cacDefault->power,
2530 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2531 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2534 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2536 cac->auxChannelIndex,
2537 cac->band,
2538 cac->channel,
2539 cac->power,
2540 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2541 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2546 static void cliVtx(char *cmdline)
2548 const char *format = "vtx %u %u %u %u %u %u %u";
2549 int i, val = 0;
2550 const char *ptr;
2552 if (isEmpty(cmdline)) {
2553 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2554 } else {
2555 ptr = cmdline;
2556 i = atoi(ptr++);
2557 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2558 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2559 uint8_t validArgumentCount = 0;
2560 ptr = nextArg(ptr);
2561 if (ptr) {
2562 val = atoi(ptr);
2563 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2564 cac->auxChannelIndex = val;
2565 validArgumentCount++;
2568 ptr = nextArg(ptr);
2569 if (ptr) {
2570 val = atoi(ptr);
2571 if (val >= 0 && val <= vtxTableBandCount) {
2572 cac->band = val;
2573 validArgumentCount++;
2576 ptr = nextArg(ptr);
2577 if (ptr) {
2578 val = atoi(ptr);
2579 if (val >= 0 && val <= vtxTableChannelCount) {
2580 cac->channel = val;
2581 validArgumentCount++;
2584 ptr = nextArg(ptr);
2585 if (ptr) {
2586 val = atoi(ptr);
2587 if (val >= 0 && val < vtxTablePowerLevels) {
2588 cac->power= val;
2589 validArgumentCount++;
2592 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2594 if (validArgumentCount != 6) {
2595 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2596 cliShowParseError();
2597 } else {
2598 cliDumpPrintLinef(0, false, format,
2600 cac->auxChannelIndex,
2601 cac->band,
2602 cac->channel,
2603 cac->power,
2604 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2605 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2608 } else {
2609 cliShowArgumentRangeError("INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2614 #endif // VTX_CONTROL
2616 #ifdef USE_VTX_TABLE
2618 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2620 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2621 char freqtmp[5 + 1];
2622 freqbuf[0] = 0;
2623 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2624 for (int channel = 0; channel < channels; channel++) {
2625 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2626 strcat(freqbuf, freqtmp);
2628 return freqbuf;
2631 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2633 char *fmt = "vtxtable band %d %s %c%s";
2634 bool equalsDefault = false;
2636 if (defaultConfig) {
2637 equalsDefault = true;
2638 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2639 equalsDefault = false;
2641 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2642 equalsDefault = false;
2644 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2645 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2646 equalsDefault = false;
2649 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2650 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2651 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2654 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2655 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2656 return headingStr;
2659 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2661 // (max 4 digit + 1 space) per level
2662 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2663 char pwrtmp[5 + 1];
2664 pwrbuf[0] = 0;
2665 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2666 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2667 strcat(pwrbuf, pwrtmp);
2669 return pwrbuf;
2672 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2674 char *fmt = "vtxtable powervalues%s";
2675 bool equalsDefault = false;
2676 if (defaultConfig) {
2677 equalsDefault = true;
2678 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2679 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2680 equalsDefault = false;
2683 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2684 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2685 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2688 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2689 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2690 return headingStr;
2693 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2695 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2696 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2697 pwrbuf[0] = 0;
2698 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2699 strcat(pwrbuf, " ");
2700 strcpy(pwrtmp, labels[pwrindex]);
2701 // trim trailing space
2702 char *sp;
2703 while ((sp = strchr(pwrtmp, ' '))) {
2704 *sp = 0;
2706 strcat(pwrbuf, pwrtmp);
2708 return pwrbuf;
2711 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2713 char *fmt = "vtxtable powerlabels%s";
2714 bool equalsDefault = false;
2715 if (defaultConfig) {
2716 equalsDefault = true;
2717 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2718 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2719 equalsDefault = false;
2722 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2723 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2724 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2727 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2728 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2729 return headingStr;
2732 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2734 bool equalsDefault;
2735 char *fmt;
2737 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2739 // bands
2740 equalsDefault = false;
2741 fmt = "vtxtable bands %d";
2742 if (defaultConfig) {
2743 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2744 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2745 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2747 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2749 // channels
2750 equalsDefault = false;
2751 fmt = "vtxtable channels %d";
2752 if (defaultConfig) {
2753 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2754 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2755 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2757 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2759 // band
2761 for (int band = 0; band < currentConfig->bands; band++) {
2762 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2765 // powerlevels
2767 equalsDefault = false;
2768 fmt = "vtxtable powerlevels %d";
2769 if (defaultConfig) {
2770 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2771 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2772 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2774 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2776 // powervalues
2778 // powerlabels
2779 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2780 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2783 static void cliVtxTable(char *cmdline)
2785 char *tok;
2786 char *saveptr;
2788 // Band number or nothing
2789 tok = strtok_r(cmdline, " ", &saveptr);
2791 if (!tok) {
2792 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2793 return;
2796 if (strcasecmp(tok, "bands") == 0) {
2797 tok = strtok_r(NULL, " ", &saveptr);
2798 int bands = atoi(tok);
2799 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2800 cliPrintErrorLinef("INVALID BAND COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_BANDS);
2801 return;
2803 if (bands < vtxTableConfigMutable()->bands) {
2804 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2805 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2808 vtxTableConfigMutable()->bands = bands;
2810 } else if (strcasecmp(tok, "channels") == 0) {
2811 tok = strtok_r(NULL, " ", &saveptr);
2813 int channels = atoi(tok);
2814 if (channels < 0 || channels > VTX_TABLE_MAX_BANDS) {
2815 cliPrintErrorLinef("INVALID CHANNEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_CHANNELS);
2816 return;
2818 if (channels < vtxTableConfigMutable()->channels) {
2819 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2820 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2823 vtxTableConfigMutable()->channels = channels;
2825 } else if (strcasecmp(tok, "powerlevels") == 0) {
2826 // Number of power levels
2827 tok = strtok_r(NULL, " ", &saveptr);
2828 if (tok) {
2829 int levels = atoi(tok);
2830 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2831 cliPrintErrorLinef("INVALID POWER LEVEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_POWER_LEVELS);
2832 } else {
2833 if (levels < vtxTableConfigMutable()->powerLevels) {
2834 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2835 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2837 vtxTableConfigMutable()->powerLevels = levels;
2839 } else {
2840 // XXX Show current level count?
2842 return;
2844 } else if (strcasecmp(tok, "powervalues") == 0) {
2845 // Power values
2846 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2847 int count;
2848 int levels = vtxTableConfigMutable()->powerLevels;
2850 memset(power, 0, sizeof(power));
2852 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2853 int value = atoi(tok);
2854 power[count] = value;
2857 // Check remaining tokens
2859 if (count < levels) {
2860 cliPrintErrorLinef("NOT ENOUGH VALUES (EXPECTED %d)", levels);
2861 return;
2862 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2863 cliPrintErrorLinef("TOO MANY VALUES (EXPECTED %d)", levels);
2864 return;
2867 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
2868 vtxTableConfigMutable()->powerValues[i] = power[i];
2871 } else if (strcasecmp(tok, "powerlabels") == 0) {
2872 // Power labels
2873 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
2874 int levels = vtxTableConfigMutable()->powerLevels;
2875 int count;
2876 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2877 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
2878 for (unsigned i = 0; i < strlen(label[count]); i++) {
2879 label[count][i] = toupper(label[count][i]);
2883 // Check remaining tokens
2885 if (count < levels) {
2886 cliPrintErrorLinef("NOT ENOUGH LABELS (EXPECTED %d)", levels);
2887 return;
2888 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2889 cliPrintErrorLinef("TOO MANY LABELS (EXPECTED %d)", levels);
2890 return;
2893 for (int i = 0; i < count; i++) {
2894 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
2896 } else if (strcasecmp(tok, "band") == 0) {
2898 int bands = vtxTableConfigMutable()->bands;
2900 tok = strtok_r(NULL, " ", &saveptr);
2901 if (!tok) {
2902 return;
2905 int band = atoi(tok);
2906 --band;
2908 if (band < 0 || band >= bands) {
2909 cliPrintErrorLinef("INVALID BAND NUMBER %s (EXPECTED 1-%d)", tok, bands);
2910 return;
2913 // Band name
2914 tok = strtok_r(NULL, " ", &saveptr);
2916 if (!tok) {
2917 return;
2920 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
2921 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
2922 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
2923 for (unsigned i = 0; i < strlen(bandname); i++) {
2924 bandname[i] = toupper(bandname[i]);
2927 // Band letter
2928 tok = strtok_r(NULL, " ", &saveptr);
2930 if (!tok) {
2931 return;
2934 char bandletter = toupper(tok[0]);
2936 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
2937 int channel = 0;
2938 int channels = vtxTableConfigMutable()->channels;
2939 bool isFactory = false;
2941 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
2942 if (channel == 0 && !isdigit(tok[0])) {
2943 channel -= 1;
2944 if (strcasecmp(tok, "FACTORY") == 0) {
2945 isFactory = true;
2946 } else if (strcasecmp(tok, "CUSTOM") == 0) {
2947 isFactory = false;
2948 } else {
2949 cliPrintErrorLinef("INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
2950 return;
2953 int freq = atoi(tok);
2954 if (freq < 0) {
2955 cliPrintErrorLinef("INVALID FREQUENCY %s", tok);
2956 return;
2958 bandfreq[channel] = freq;
2961 if (channel < channels) {
2962 cliPrintErrorLinef("NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
2963 return;
2964 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2965 cliPrintErrorLinef("TOO MANY FREQUENCIES (EXPECTED %d)", channels);
2966 return;
2969 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
2970 vtxTableConfigMutable()->bandLetters[band] = bandletter;
2972 for (int i = 0; i < channel; i++) {
2973 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
2975 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
2976 } else {
2977 // Bad subcommand
2978 cliPrintErrorLinef("INVALID SUBCOMMAND %s", tok);
2981 #endif // USE_VTX_TABLE
2983 static void printName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
2985 const bool equalsDefault = strlen(pilotConfig->name) == 0;
2986 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->name);
2989 static void cliName(char *cmdline)
2991 const unsigned int len = strlen(cmdline);
2992 bool updated = false;
2993 if (len > 0) {
2994 memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
2995 if (strncmp(cmdline, emptyName, len)) {
2996 strncpy(pilotConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
2998 updated = true;
3000 printName(DUMP_MASTER, pilotConfig());
3001 if (updated) {
3002 cliPrintLine("###WARNING: This command will be removed. Use 'set name = ' instead.###");
3006 #if defined(USE_BOARD_INFO)
3008 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
3010 static void printBoardName(dumpFlags_t dumpMask)
3012 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
3013 cliPrintLinef("board_name %s", getBoardName());
3017 static void cliBoardName(char *cmdline)
3019 const unsigned int len = strlen(cmdline);
3020 const char *boardName = getBoardName();
3021 if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
3022 cliPrintErrorLinef(ERROR_MESSAGE, "BOARD_NAME", boardName);
3023 } else {
3024 if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
3025 boardInformationUpdated = true;
3027 cliPrintHashLine("Set board_name.");
3029 printBoardName(DUMP_ALL);
3033 static void printManufacturerId(dumpFlags_t dumpMask)
3035 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
3036 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3040 static void cliManufacturerId(char *cmdline)
3042 const unsigned int len = strlen(cmdline);
3043 const char *manufacturerId = getManufacturerId();
3044 if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
3045 cliPrintErrorLinef(ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
3046 } else {
3047 if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
3048 boardInformationUpdated = true;
3050 cliPrintHashLine("Set manufacturer_id.");
3052 printManufacturerId(DUMP_ALL);
3056 #if defined(USE_SIGNATURE)
3057 static void writeSignature(char *signatureStr, uint8_t *signature)
3059 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3060 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
3064 static void cliSignature(char *cmdline)
3066 const int len = strlen(cmdline);
3068 uint8_t signature[SIGNATURE_LENGTH] = {0};
3069 if (len > 0) {
3070 if (len != 2 * SIGNATURE_LENGTH) {
3071 cliPrintErrorLinef("INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
3073 return;
3076 #define BLOCK_SIZE 2
3077 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3078 char temp[BLOCK_SIZE + 1];
3079 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3080 temp[BLOCK_SIZE] = '\0';
3081 char *end;
3082 unsigned result = strtoul(temp, &end, 16);
3083 if (end == &temp[BLOCK_SIZE]) {
3084 signature[i] = result;
3085 } else {
3086 cliPrintErrorLinef("INVALID CHARACTER FOUND: %c", end[0]);
3088 return;
3091 #undef BLOCK_SIZE
3094 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3095 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3096 writeSignature(signatureStr, getSignature());
3097 cliPrintErrorLinef(ERROR_MESSAGE, "SIGNATURE", signatureStr);
3098 } else {
3099 if (len > 0 && !configIsInCopy && setSignature(signature)) {
3100 signatureUpdated = true;
3102 writeSignature(signatureStr, getSignature());
3104 cliPrintHashLine("Set signature.");
3105 } else if (signatureUpdated || signatureIsSet()) {
3106 writeSignature(signatureStr, getSignature());
3109 cliPrintLinef("signature %s", signatureStr);
3112 #endif
3114 #undef ERROR_MESSAGE
3116 #endif // USE_BOARD_INFO
3118 static void cliMcuId(char *cmdline)
3120 UNUSED(cmdline);
3122 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3125 static uint32_t *getFeatureMask(void)
3127 if (featureMaskIsCopied) {
3128 return &featureMaskCopy;
3129 } else {
3130 return &featureConfigMutable()->enabledFeatures;
3134 static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
3136 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3137 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
3138 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3139 const char *format = "feature -%s";
3140 const bool equalsDefault = (~defaultMask | mask) & (1 << i);
3141 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3142 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
3143 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3146 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
3147 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3148 const char *format = "feature %s";
3149 if (defaultMask & (1 << i)) {
3150 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
3152 if (mask & (1 << i)) {
3153 const bool equalsDefault = (defaultMask | ~mask) & (1 << i);
3154 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3155 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3161 static void cliFeature(char *cmdline)
3163 uint32_t len = strlen(cmdline);
3164 const uint32_t mask = *getFeatureMask();
3165 if (len == 0) {
3166 cliPrint("Enabled: ");
3167 for (uint32_t i = 0; ; i++) {
3168 if (featureNames[i] == NULL) {
3169 break;
3171 if (mask & (1 << i)) {
3172 cliPrintf("%s ", featureNames[i]);
3175 cliPrintLinefeed();
3176 } else if (strncasecmp(cmdline, "list", len) == 0) {
3177 cliPrint("Available:");
3178 for (uint32_t i = 0; ; i++) {
3179 if (featureNames[i] == NULL)
3180 break;
3181 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
3182 cliPrintf(" %s", featureNames[i]);
3184 cliPrintLinefeed();
3185 return;
3186 } else {
3187 if (!featureMaskIsCopied && !configIsInCopy) {
3188 featureMaskCopy = featureConfig()->enabledFeatures;
3189 featureMaskIsCopied = true;
3191 uint32_t feature;
3193 bool remove = false;
3194 if (cmdline[0] == '-') {
3195 // remove feature
3196 remove = true;
3197 cmdline++; // skip over -
3198 len--;
3201 for (uint32_t i = 0; ; i++) {
3202 if (featureNames[i] == NULL) {
3203 cliPrintErrorLinef("INVALID NAME");
3204 break;
3207 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
3208 feature = 1 << i;
3209 #ifndef USE_GPS
3210 if (feature & FEATURE_GPS) {
3211 cliPrintLine("unavailable");
3212 break;
3214 #endif
3215 #ifndef USE_RANGEFINDER
3216 if (feature & FEATURE_RANGEFINDER) {
3217 cliPrintLine("unavailable");
3218 break;
3220 #endif
3221 if (remove) {
3222 featureClear(feature, getFeatureMask());
3223 cliPrint("Disabled");
3224 } else {
3225 featureSet(feature, getFeatureMask());
3226 cliPrint("Enabled");
3228 cliPrintLinef(" %s", featureNames[i]);
3229 break;
3235 #if defined(USE_BEEPER)
3236 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3238 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3239 const uint8_t beeperCount = beeperTableEntryCount();
3240 for (int32_t i = 0; i < beeperCount - 1; i++) {
3241 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3242 const char *formatOff = "%s -%s";
3243 const char *formatOn = "%s %s";
3244 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3245 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3246 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3247 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3248 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3253 static void processBeeperCommand(char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3255 uint32_t len = strlen(cmdline);
3256 uint8_t beeperCount = beeperTableEntryCount();
3258 if (len == 0) {
3259 cliPrintf("Disabled:");
3260 for (int32_t i = 0; ; i++) {
3261 if (i == beeperCount - 1) {
3262 if (*offFlags == 0)
3263 cliPrint(" none");
3264 break;
3267 if (beeperModeMaskForTableIndex(i) & *offFlags)
3268 cliPrintf(" %s", beeperNameForTableIndex(i));
3270 cliPrintLinefeed();
3271 } else if (strncasecmp(cmdline, "list", len) == 0) {
3272 cliPrint("Available:");
3273 for (uint32_t i = 0; i < beeperCount; i++) {
3274 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3275 cliPrintf(" %s", beeperNameForTableIndex(i));
3278 cliPrintLinefeed();
3279 } else {
3280 bool remove = false;
3281 if (cmdline[0] == '-') {
3282 remove = true; // this is for beeper OFF condition
3283 cmdline++;
3284 len--;
3287 for (uint32_t i = 0; ; i++) {
3288 if (i == beeperCount) {
3289 cliPrintErrorLinef("INVALID NAME");
3290 break;
3292 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3293 if (remove) { // beeper off
3294 if (i == BEEPER_ALL - 1) {
3295 *offFlags = allowedFlags;
3296 } else {
3297 *offFlags |= beeperModeMaskForTableIndex(i);
3299 cliPrint("Disabled");
3301 else { // beeper on
3302 if (i == BEEPER_ALL - 1) {
3303 *offFlags = 0;
3304 } else {
3305 *offFlags &= ~beeperModeMaskForTableIndex(i);
3307 cliPrint("Enabled");
3309 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3310 break;
3316 #if defined(USE_DSHOT)
3317 static void cliBeacon(char *cmdline)
3319 processBeeperCommand(cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3321 #endif
3323 static void cliBeeper(char *cmdline)
3325 processBeeperCommand(cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3327 #endif
3329 #if defined(USE_RX_SPI) || defined (USE_SERIALRX_SRXL2)
3330 void cliRxBind(char *cmdline){
3331 UNUSED(cmdline);
3332 if (featureIsEnabled(FEATURE_RX_SERIAL)) {
3333 switch (rxConfig()->serialrx_provider) {
3334 default:
3335 cliPrint("Not supported.");
3336 break;
3337 #if defined(USE_SERIALRX_SRXL2)
3338 case SERIALRX_SRXL2:
3339 srxl2Bind();
3340 cliPrint("Binding SRXL2 receiver...");
3341 break;
3342 #endif
3345 #if defined(USE_RX_SPI)
3346 else if (featureIsEnabled(FEATURE_RX_SPI)) {
3347 switch (rxSpiConfig()->rx_spi_protocol) {
3348 default:
3349 cliPrint("Not supported.");
3350 break;
3351 #if defined(USE_RX_FRSKY_SPI)
3352 #if defined(USE_RX_FRSKY_SPI_D)
3353 case RX_SPI_FRSKY_D:
3354 #endif
3355 #if defined(USE_RX_FRSKY_SPI_X)
3356 case RX_SPI_FRSKY_X:
3357 case RX_SPI_FRSKY_X_LBT:
3358 #endif
3359 #endif // USE_RX_FRSKY_SPI
3360 #ifdef USE_RX_SFHSS_SPI
3361 case RX_SPI_SFHSS:
3362 #endif
3363 #ifdef USE_RX_FLYSKY
3364 case RX_SPI_A7105_FLYSKY:
3365 case RX_SPI_A7105_FLYSKY_2A:
3366 #endif
3367 #ifdef USE_RX_SPEKTRUM
3368 case RX_SPI_CYRF6936_DSM:
3369 #endif
3370 #if defined(USE_RX_FRSKY_SPI) || defined(USE_RX_SFHSS_SPI) || defined(USE_RX_FLYSKY) || defined(USE_RX_SPEKTRUM)
3371 rxSpiBind();
3372 cliPrint("Binding...");
3373 break;
3374 #endif
3378 #endif
3380 #endif
3382 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3384 bool equalsDefault = true;
3385 char buf[16];
3386 char bufDefault[16];
3387 uint32_t i;
3389 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3390 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3391 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3392 if (defaultRxConfig) {
3393 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3394 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3397 buf[i] = '\0';
3399 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3400 const char *formatMap = "map %s";
3401 if (defaultRxConfig) {
3402 bufDefault[i] = '\0';
3403 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3405 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3409 static void cliMap(char *cmdline)
3411 uint32_t i;
3412 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3414 uint32_t len = strlen(cmdline);
3415 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3417 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3418 buf[i] = toupper((unsigned char)cmdline[i]);
3420 buf[i] = '\0';
3422 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3423 buf[i] = toupper((unsigned char)cmdline[i]);
3425 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3426 continue;
3428 cliShowParseError();
3429 return;
3431 parseRcChannels(buf, rxConfigMutable());
3432 } else if (len > 0) {
3433 cliShowParseError();
3434 return;
3437 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3438 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3441 buf[i] = '\0';
3442 cliPrintLinef("map %s", buf);
3445 static char *skipSpace(char *buffer)
3447 while (*(buffer) == ' ') {
3448 buffer++;
3451 return buffer;
3454 static char *checkCommand(char *cmdline, const char *command)
3456 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3457 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3458 return skipSpace(cmdline + strlen(command) + 1);
3459 } else {
3460 return 0;
3464 static void cliRebootEx(rebootTarget_e rebootTarget)
3466 cliPrint("\r\nRebooting");
3467 cliWriterFlush();
3468 waitForSerialPortToFinishTransmitting(cliPort);
3469 motorShutdown();
3471 switch (rebootTarget) {
3472 case REBOOT_TARGET_BOOTLOADER_ROM:
3473 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3475 break;
3476 #if defined(USE_FLASH_BOOT_LOADER)
3477 case REBOOT_TARGET_BOOTLOADER_FLASH:
3478 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3480 break;
3481 #endif
3482 case REBOOT_TARGET_FIRMWARE:
3483 default:
3484 systemReset();
3486 break;
3490 static void cliReboot(void)
3492 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3495 static void cliBootloader(char *cmdline)
3497 rebootTarget_e rebootTarget;
3498 if (
3499 #if !defined(USE_FLASH_BOOT_LOADER)
3500 isEmpty(cmdline) ||
3501 #endif
3502 strncasecmp(cmdline, "rom", 3) == 0) {
3503 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3505 cliPrintHashLine("restarting in ROM bootloader mode");
3506 #if defined(USE_FLASH_BOOT_LOADER)
3507 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3508 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3510 cliPrintHashLine("restarting in flash bootloader mode");
3511 #endif
3512 } else {
3513 cliPrintErrorLinef("Invalid option");
3515 return;
3518 cliRebootEx(rebootTarget);
3521 static void cliExit(char *cmdline)
3523 UNUSED(cmdline);
3525 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3526 cliWriterFlush();
3528 *cliBuffer = '\0';
3529 bufferIndex = 0;
3530 cliMode = false;
3531 // incase a motor was left running during motortest, clear it here
3532 mixerResetDisarmedMotors();
3533 cliReboot();
3536 #ifdef USE_GPS
3537 static void cliGpsPassthrough(char *cmdline)
3539 UNUSED(cmdline);
3541 gpsEnablePassthrough(cliPort);
3543 #endif
3545 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3546 static void cliPrintGyroRegisters(uint8_t whichSensor)
3548 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3549 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3550 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3553 static void cliDumpGyroRegisters(char *cmdline)
3555 #ifdef USE_MULTI_GYRO
3556 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3557 cliPrintLinef("\r\n# Gyro 1");
3558 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3560 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3561 cliPrintLinef("\r\n# Gyro 2");
3562 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3564 #else
3565 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3566 #endif
3567 UNUSED(cmdline);
3569 #endif
3572 static int parseOutputIndex(char *pch, bool allowAllEscs) {
3573 int outputIndex = atoi(pch);
3574 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3575 cliPrintLinef("Using output %d.", outputIndex);
3576 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3577 cliPrintLinef("Using all outputs.");
3578 } else {
3579 cliPrintErrorLinef("INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3581 return -1;
3584 return outputIndex;
3587 #if defined(USE_DSHOT)
3588 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3590 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3591 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3592 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3594 enum {
3595 ESC_INFO_KISS_V1,
3596 ESC_INFO_KISS_V2,
3597 ESC_INFO_BLHELI32
3600 #define ESC_INFO_VERSION_POSITION 12
3602 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
3604 bool escInfoReceived = false;
3605 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3606 uint8_t escInfoVersion;
3607 uint8_t frameLength;
3608 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3609 escInfoVersion = ESC_INFO_BLHELI32;
3610 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3611 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3612 escInfoVersion = ESC_INFO_KISS_V2;
3613 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3614 } else {
3615 escInfoVersion = ESC_INFO_KISS_V1;
3616 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3619 if (bytesRead == frameLength) {
3620 escInfoReceived = true;
3622 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3623 uint8_t firmwareVersion = 0;
3624 uint8_t firmwareSubVersion = 0;
3625 uint8_t escType = 0;
3626 switch (escInfoVersion) {
3627 case ESC_INFO_KISS_V1:
3628 firmwareVersion = escInfoBuffer[12];
3629 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3630 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3632 break;
3633 case ESC_INFO_KISS_V2:
3634 firmwareVersion = escInfoBuffer[13];
3635 firmwareSubVersion = escInfoBuffer[14];
3636 escType = escInfoBuffer[15];
3638 break;
3639 case ESC_INFO_BLHELI32:
3640 firmwareVersion = escInfoBuffer[13];
3641 firmwareSubVersion = escInfoBuffer[14];
3642 escType = escInfoBuffer[15];
3644 break;
3647 cliPrint("ESC Type: ");
3648 switch (escInfoVersion) {
3649 case ESC_INFO_KISS_V1:
3650 case ESC_INFO_KISS_V2:
3651 switch (escType) {
3652 case 1:
3653 cliPrintLine("KISS8A");
3655 break;
3656 case 2:
3657 cliPrintLine("KISS16A");
3659 break;
3660 case 3:
3661 cliPrintLine("KISS24A");
3663 break;
3664 case 5:
3665 cliPrintLine("KISS Ultralite");
3667 break;
3668 default:
3669 cliPrintLine("unknown");
3671 break;
3674 break;
3675 case ESC_INFO_BLHELI32:
3677 char *escType = (char *)(escInfoBuffer + 31);
3678 escType[32] = 0;
3679 cliPrintLine(escType);
3682 break;
3685 cliPrint("MCU Serial No: 0x");
3686 for (int i = 0; i < 12; i++) {
3687 if (i && (i % 3 == 0)) {
3688 cliPrint("-");
3690 cliPrintf("%02x", escInfoBuffer[i]);
3692 cliPrintLinefeed();
3694 switch (escInfoVersion) {
3695 case ESC_INFO_KISS_V1:
3696 case ESC_INFO_KISS_V2:
3697 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3699 break;
3700 case ESC_INFO_BLHELI32:
3701 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3703 break;
3705 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3706 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3707 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3708 if (escInfoVersion == ESC_INFO_BLHELI32) {
3709 uint8_t setting = escInfoBuffer[18];
3710 cliPrint("Low voltage Limit: ");
3711 switch (setting) {
3712 case 0:
3713 cliPrintLine("off");
3715 break;
3716 case 255:
3717 cliPrintLine("unsupported");
3719 break;
3720 default:
3721 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3723 break;
3726 setting = escInfoBuffer[19];
3727 cliPrint("Current Limit: ");
3728 switch (setting) {
3729 case 0:
3730 cliPrintLine("off");
3732 break;
3733 case 255:
3734 cliPrintLine("unsupported");
3736 break;
3737 default:
3738 cliPrintLinef("%d", setting);
3740 break;
3743 for (int i = 0; i < 4; i++) {
3744 setting = escInfoBuffer[i + 20];
3745 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3749 } else {
3750 cliPrintErrorLinef("CHECKSUM ERROR.");
3755 if (!escInfoReceived) {
3756 cliPrintLine("No Info.");
3760 static void executeEscInfoCommand(uint8_t escIndex)
3762 cliPrintLinef("Info for ESC %d:", escIndex);
3764 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3766 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3768 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, true);
3770 delay(10);
3772 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
3774 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3777 // XXX Review dshotprog command under refactored motor handling
3779 static void cliDshotProg(char *cmdline)
3781 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
3782 cliShowParseError();
3784 return;
3787 char *saveptr;
3788 char *pch = strtok_r(cmdline, " ", &saveptr);
3789 int pos = 0;
3790 int escIndex = 0;
3791 bool firstCommand = true;
3792 while (pch != NULL) {
3793 switch (pos) {
3794 case 0:
3795 escIndex = parseOutputIndex(pch, true);
3796 if (escIndex == -1) {
3797 return;
3800 break;
3801 default:
3803 int command = atoi(pch);
3804 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3805 if (firstCommand) {
3806 // pwmDisableMotors();
3807 motorDisable();
3809 if (command == DSHOT_CMD_ESC_INFO) {
3810 delay(5); // Wait for potential ESC telemetry transmission to finish
3811 } else {
3812 delay(1);
3815 firstCommand = false;
3818 if (command != DSHOT_CMD_ESC_INFO) {
3819 dshotCommandWrite(escIndex, getMotorCount(), command, true);
3820 } else {
3821 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3822 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3823 if (escIndex != ALL_MOTORS) {
3824 executeEscInfoCommand(escIndex);
3825 } else {
3826 for (uint8_t i = 0; i < getMotorCount(); i++) {
3827 executeEscInfoCommand(i);
3830 } else
3831 #endif
3833 cliPrintLine("Not supported.");
3837 cliPrintLinef("Command Sent: %d", command);
3839 } else {
3840 cliPrintErrorLinef("INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3844 break;
3847 pos++;
3848 pch = strtok_r(NULL, " ", &saveptr);
3851 motorEnable();
3853 #endif // USE_DSHOT
3855 #ifdef USE_ESCSERIAL
3856 static void cliEscPassthrough(char *cmdline)
3858 if (isEmpty(cmdline)) {
3859 cliShowParseError();
3861 return;
3864 char *saveptr;
3865 char *pch = strtok_r(cmdline, " ", &saveptr);
3866 int pos = 0;
3867 uint8_t mode = 0;
3868 int escIndex = 0;
3869 while (pch != NULL) {
3870 switch (pos) {
3871 case 0:
3872 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3873 mode = PROTOCOL_SIMONK;
3874 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3875 mode = PROTOCOL_BLHELI;
3876 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3877 mode = PROTOCOL_KISS;
3878 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3879 mode = PROTOCOL_KISSALL;
3880 } else {
3881 cliShowParseError();
3883 return;
3885 break;
3886 case 1:
3887 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
3888 if (escIndex == -1) {
3889 return;
3892 break;
3893 default:
3894 cliShowParseError();
3896 return;
3898 break;
3901 pos++;
3902 pch = strtok_r(NULL, " ", &saveptr);
3905 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
3906 cliPrintErrorLinef("Error starting ESC connection");
3909 #endif
3911 #ifndef USE_QUAD_MIXER_ONLY
3912 static void cliMixer(char *cmdline)
3914 int len;
3916 len = strlen(cmdline);
3918 if (len == 0) {
3919 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3920 return;
3921 } else if (strncasecmp(cmdline, "list", len) == 0) {
3922 cliPrint("Available:");
3923 for (uint32_t i = 0; ; i++) {
3924 if (mixerNames[i] == NULL)
3925 break;
3926 cliPrintf(" %s", mixerNames[i]);
3928 cliPrintLinefeed();
3929 return;
3932 for (uint32_t i = 0; ; i++) {
3933 if (mixerNames[i] == NULL) {
3934 cliPrintErrorLinef("INVALID NAME");
3935 return;
3937 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
3938 mixerConfigMutable()->mixerMode = i + 1;
3939 break;
3943 cliMixer("");
3945 #endif
3947 static void cliMotor(char *cmdline)
3949 if (isEmpty(cmdline)) {
3950 cliShowParseError();
3952 return;
3955 int motorIndex = 0;
3956 int motorValue = 0;
3958 char *saveptr;
3959 char *pch = strtok_r(cmdline, " ", &saveptr);
3960 int index = 0;
3961 while (pch != NULL) {
3962 switch (index) {
3963 case 0:
3964 motorIndex = parseOutputIndex(pch, true);
3965 if (motorIndex == -1) {
3966 return;
3969 break;
3970 case 1:
3971 motorValue = atoi(pch);
3973 break;
3975 index++;
3976 pch = strtok_r(NULL, " ", &saveptr);
3979 if (index == 2) {
3980 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
3981 cliShowArgumentRangeError("VALUE", 1000, 2000);
3982 } else {
3983 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
3985 if (motorIndex != ALL_MOTORS) {
3986 motor_disarmed[motorIndex] = motorOutputValue;
3988 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
3989 } else {
3990 for (int i = 0; i < getMotorCount(); i++) {
3991 motor_disarmed[i] = motorOutputValue;
3994 cliPrintLinef("all motors: %d", motorOutputValue);
3997 } else {
3998 cliShowParseError();
4002 #ifndef MINIMAL_CLI
4003 static void cliPlaySound(char *cmdline)
4005 int i;
4006 const char *name;
4007 static int lastSoundIdx = -1;
4009 if (isEmpty(cmdline)) {
4010 i = lastSoundIdx + 1; //next sound index
4011 if ((name=beeperNameForTableIndex(i)) == NULL) {
4012 while (true) { //no name for index; try next one
4013 if (++i >= beeperTableEntryCount())
4014 i = 0; //if end then wrap around to first entry
4015 if ((name=beeperNameForTableIndex(i)) != NULL)
4016 break; //if name OK then play sound below
4017 if (i == lastSoundIdx + 1) { //prevent infinite loop
4018 cliPrintErrorLinef("ERROR PLAYING SOUND");
4019 return;
4023 } else { //index value was given
4024 i = atoi(cmdline);
4025 if ((name=beeperNameForTableIndex(i)) == NULL) {
4026 cliPrintLinef("No sound for index %d", i);
4027 return;
4030 lastSoundIdx = i;
4031 beeperSilence();
4032 cliPrintLinef("Playing sound %d: %s", i, name);
4033 beeper(beeperModeForTableIndex(i));
4035 #endif
4037 static void cliProfile(char *cmdline)
4039 if (isEmpty(cmdline)) {
4040 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4041 return;
4042 } else {
4043 const int i = atoi(cmdline);
4044 if (i >= 0 && i < PID_PROFILE_COUNT) {
4045 changePidProfile(i);
4046 cliProfile("");
4047 } else {
4048 cliPrintErrorLinef("PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
4053 static void cliRateProfile(char *cmdline)
4055 if (isEmpty(cmdline)) {
4056 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4057 return;
4058 } else {
4059 const int i = atoi(cmdline);
4060 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
4061 changeControlRateProfile(i);
4062 cliRateProfile("");
4063 } else {
4064 cliPrintErrorLinef("RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
4069 static void cliDumpPidProfile(uint8_t pidProfileIndex, dumpFlags_t dumpMask)
4071 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4072 // Faulty values
4073 return;
4076 pidProfileIndexToUse = pidProfileIndex;
4078 cliPrintLinefeed();
4079 cliProfile("");
4081 char profileStr[10];
4082 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4083 dumpAllValues(PROFILE_VALUE, dumpMask, profileStr);
4085 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4088 static void cliDumpRateProfile(uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4090 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4091 // Faulty values
4092 return;
4095 rateProfileIndexToUse = rateProfileIndex;
4097 cliPrintLinefeed();
4098 cliRateProfile("");
4100 char rateProfileStr[14];
4101 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4102 dumpAllValues(PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4104 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4107 #ifdef USE_CLI_BATCH
4108 static void cliPrintCommandBatchWarning(const char *warning)
4110 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4111 if (warning) {
4112 cliPrintErrorLinef(warning);
4116 static void resetCommandBatch(void)
4118 commandBatchActive = false;
4119 commandBatchError = false;
4122 static void cliBatch(char *cmdline)
4124 if (strncasecmp(cmdline, "start", 5) == 0) {
4125 if (!commandBatchActive) {
4126 commandBatchActive = true;
4127 commandBatchError = false;
4129 cliPrintLine("Command batch started");
4130 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4131 if (commandBatchActive && commandBatchError) {
4132 cliPrintCommandBatchWarning(NULL);
4133 } else {
4134 cliPrintLine("Command batch ended");
4136 resetCommandBatch();
4137 } else {
4138 cliPrintErrorLinef("Invalid option");
4141 #endif
4143 static bool prepareSave(void)
4145 #if defined(USE_CUSTOM_DEFAULTS)
4146 if (processingCustomDefaults) {
4147 return true;
4149 #endif
4151 #ifdef USE_CLI_BATCH
4152 if (commandBatchActive && commandBatchError) {
4153 return false;
4155 #endif
4157 #if defined(USE_BOARD_INFO)
4158 if (boardInformationUpdated) {
4159 persistBoardInformation();
4161 #if defined(USE_SIGNATURE)
4162 if (signatureUpdated) {
4163 persistSignature();
4165 #endif
4166 #endif // USE_BOARD_INFO
4168 if (featureMaskIsCopied) {
4169 featureDisableAll();
4170 featureEnable(featureMaskCopy);
4173 return true;
4176 bool tryPrepareSave(void)
4178 bool success = prepareSave();
4179 #if defined(USE_CLI_BATCH)
4180 if (!success) {
4181 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
4182 resetCommandBatch();
4184 return false;
4186 #else
4187 UNUSED(success);
4188 #endif
4190 return true;
4193 static void cliSave(char *cmdline)
4195 UNUSED(cmdline);
4197 if (tryPrepareSave()) {
4198 writeEEPROM();
4199 cliPrintHashLine("saving");
4201 cliReboot();
4205 #if defined(USE_CUSTOM_DEFAULTS)
4206 bool resetConfigToCustomDefaults(void)
4208 resetConfig();
4210 #ifdef USE_CLI_BATCH
4211 commandBatchError = false;
4212 #endif
4214 cliProcessCustomDefaults();
4216 return prepareSave();
4219 static bool isCustomDefaults(char *ptr)
4221 return strncmp(ptr, "# " FC_FIRMWARE_NAME, 12) == 0;
4224 bool hasCustomDefaults(void)
4226 return isCustomDefaults(customDefaultsStart);
4228 #endif
4230 static void cliDefaults(char *cmdline)
4232 bool saveConfigs = true;
4233 #if defined(USE_CUSTOM_DEFAULTS)
4234 bool useCustomDefaults = true;
4235 #elif defined(USE_CUSTOM_DEFAULTS_ADDRESS)
4236 // Required to keep the linker from eliminating these
4237 if (customDefaultsStart != customDefaultsEnd) {
4238 delay(0);
4240 #endif
4242 if (isEmpty(cmdline)) {
4243 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
4244 saveConfigs = false;
4245 #if defined(USE_CUSTOM_DEFAULTS)
4246 } else if (strncasecmp(cmdline, "bare", 4) == 0) {
4247 useCustomDefaults = false;
4248 } else if (strncasecmp(cmdline, "show", 4) == 0) {
4249 char *customDefaultsPtr = customDefaultsStart;
4250 if (isCustomDefaults(customDefaultsPtr)) {
4251 while (*customDefaultsPtr && *customDefaultsPtr != 0xFF && customDefaultsPtr < customDefaultsEnd) {
4252 if (*customDefaultsPtr != '\n') {
4253 cliPrintf("%c", *customDefaultsPtr++);
4254 } else {
4255 cliPrintLinefeed();
4256 customDefaultsPtr++;
4259 } else {
4260 cliPrintError("NO CUSTOM DEFAULTS FOUND");
4263 return;
4264 #endif
4265 } else {
4266 cliPrintError("INVALID OPTION");
4268 return;
4271 cliPrintHashLine("resetting to defaults");
4273 resetConfig();
4275 #ifdef USE_CLI_BATCH
4276 // Reset only the error state and allow the batch active state to remain.
4277 // This way if a "defaults nosave" was issued after the "batch on" we'll
4278 // only reset the current error state but the batch will still be active
4279 // for subsequent commands.
4280 commandBatchError = false;
4281 #endif
4283 #if defined(USE_CUSTOM_DEFAULTS)
4284 if (useCustomDefaults) {
4285 cliProcessCustomDefaults();
4287 #endif
4289 if (saveConfigs && tryPrepareSave()) {
4290 writeUnmodifiedConfigToEEPROM();
4292 cliReboot();
4296 void cliPrintVarDefault(const clivalue_t *value)
4298 const pgRegistry_t *pg = pgFind(value->pgn);
4299 if (pg) {
4300 const char *defaultFormat = "Default value: ";
4301 const int valueOffset = getValueOffset(value);
4302 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
4303 if (!equalsDefault) {
4304 cliPrintf(defaultFormat, value->name);
4305 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
4306 cliPrintLinefeed();
4311 STATIC_UNIT_TESTED void cliGet(char *cmdline)
4313 const clivalue_t *val;
4314 int matchedCommands = 0;
4316 pidProfileIndexToUse = getCurrentPidProfileIndex();
4317 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4319 backupAndResetConfigs(true);
4321 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4322 if (strcasestr(valueTable[i].name, cmdline)) {
4323 val = &valueTable[i];
4324 if (matchedCommands > 0) {
4325 cliPrintLinefeed();
4327 cliPrintf("%s = ", valueTable[i].name);
4328 cliPrintVar(val, 0);
4329 cliPrintLinefeed();
4330 switch (val->type & VALUE_SECTION_MASK) {
4331 case PROFILE_VALUE:
4332 cliProfile("");
4334 break;
4335 case PROFILE_RATE_VALUE:
4336 cliRateProfile("");
4338 break;
4339 default:
4341 break;
4343 cliPrintVarRange(val);
4344 cliPrintVarDefault(val);
4346 matchedCommands++;
4350 restoreConfigs();
4352 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4353 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4355 if (!matchedCommands) {
4356 cliPrintErrorLinef("INVALID NAME");
4360 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
4362 while (*(bufEnd - 1) == ' ') {
4363 bufEnd--;
4366 return bufEnd - bufBegin;
4369 uint16_t cliGetSettingIndex(char *name, uint8_t length)
4371 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4372 const char *settingName = valueTable[i].name;
4374 // ensure exact match when setting to prevent setting variables with shorter names
4375 if (strncasecmp(name, settingName, strlen(settingName)) == 0 && length == strlen(settingName)) {
4376 return i;
4379 return valueTableEntryCount;
4382 STATIC_UNIT_TESTED void cliSet(char *cmdline)
4384 const uint32_t len = strlen(cmdline);
4385 char *eqptr;
4387 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4388 cliPrintLine("Current settings: ");
4390 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4391 const clivalue_t *val = &valueTable[i];
4392 cliPrintf("%s = ", valueTable[i].name);
4393 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4394 cliPrintLinefeed();
4396 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4397 // has equals
4399 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4401 // skip the '=' and any ' ' characters
4402 eqptr++;
4403 eqptr = skipSpace(eqptr);
4405 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4406 if (index >= valueTableEntryCount) {
4407 cliPrintErrorLinef("INVALID NAME");
4408 return;
4410 const clivalue_t *val = &valueTable[index];
4412 bool valueChanged = false;
4413 int16_t value = 0;
4414 switch (val->type & VALUE_MODE_MASK) {
4415 case MODE_DIRECT: {
4416 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4417 uint32_t value = strtoul(eqptr, NULL, 10);
4419 if (value <= val->config.u32Max) {
4420 cliSetVar(val, value);
4421 valueChanged = true;
4423 } else {
4424 int value = atoi(eqptr);
4426 int min;
4427 int max;
4428 getMinMax(val, &min, &max);
4430 if (value >= min && value <= max) {
4431 cliSetVar(val, value);
4432 valueChanged = true;
4437 break;
4438 case MODE_LOOKUP:
4439 case MODE_BITSET: {
4440 int tableIndex;
4441 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4442 tableIndex = TABLE_OFF_ON;
4443 } else {
4444 tableIndex = val->config.lookup.tableIndex;
4446 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4447 bool matched = false;
4448 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4449 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4451 if (matched) {
4452 value = tableValueIndex;
4454 cliSetVar(val, value);
4455 valueChanged = true;
4460 break;
4462 case MODE_ARRAY: {
4463 const uint8_t arrayLength = val->config.array.length;
4464 char *valPtr = eqptr;
4466 int i = 0;
4467 while (i < arrayLength && valPtr != NULL) {
4468 // skip spaces
4469 valPtr = skipSpace(valPtr);
4471 // process substring starting at valPtr
4472 // note: no need to copy substrings for atoi()
4473 // it stops at the first character that cannot be converted...
4474 switch (val->type & VALUE_TYPE_MASK) {
4475 default:
4476 case VAR_UINT8:
4478 // fetch data pointer
4479 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4480 // store value
4481 *data = (uint8_t)atoi((const char*) valPtr);
4484 break;
4485 case VAR_INT8:
4487 // fetch data pointer
4488 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4489 // store value
4490 *data = (int8_t)atoi((const char*) valPtr);
4493 break;
4494 case VAR_UINT16:
4496 // fetch data pointer
4497 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4498 // store value
4499 *data = (uint16_t)atoi((const char*) valPtr);
4502 break;
4503 case VAR_INT16:
4505 // fetch data pointer
4506 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4507 // store value
4508 *data = (int16_t)atoi((const char*) valPtr);
4511 break;
4512 case VAR_UINT32:
4514 // fetch data pointer
4515 uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
4516 // store value
4517 *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
4520 break;
4523 // find next comma (or end of string)
4524 valPtr = strchr(valPtr, ',') + 1;
4526 i++;
4530 // mark as changed
4531 valueChanged = true;
4533 break;
4534 case MODE_STRING: {
4535 char *valPtr = eqptr;
4536 valPtr = skipSpace(valPtr);
4538 const unsigned int len = strlen(valPtr);
4539 const uint8_t min = val->config.string.minlength;
4540 const uint8_t max = val->config.string.maxlength;
4541 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4542 strlen((char *)cliGetValuePointer(val)) == 0 ||
4543 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4545 if (updatable && len > 0 && len <= max) {
4546 memset((char *)cliGetValuePointer(val), 0, max);
4547 if (len >= min && strncmp(valPtr, emptyName, len)) {
4548 strncpy((char *)cliGetValuePointer(val), valPtr, len);
4550 valueChanged = true;
4551 } else {
4552 cliPrintErrorLinef("STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4555 break;
4558 if (valueChanged) {
4559 cliPrintf("%s set to ", val->name);
4560 cliPrintVar(val, 0);
4561 } else {
4562 cliPrintErrorLinef("INVALID VALUE");
4563 cliPrintVarRange(val);
4566 return;
4567 } else {
4568 // no equals, check for matching variables.
4569 cliGet(cmdline);
4573 static void cliStatus(char *cmdline)
4575 UNUSED(cmdline);
4577 // MCU type, clock, vrefint, core temperature
4579 cliPrintf("MCU %s Clock=%dMHz", MCU_TYPE_NAME, (SystemCoreClock / 1000000));
4581 #ifdef STM32F4
4582 // Only F4 is capable of switching between HSE/HSI (for now)
4583 int sysclkSource = SystemSYSCLKSource();
4585 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4586 const char *PLLSource[] = { "-HSI", "-HSE" };
4588 int pllSource;
4590 if (sysclkSource >= 2) {
4591 pllSource = SystemPLLSource();
4594 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4595 #endif
4597 #ifdef USE_ADC_INTERNAL
4598 uint16_t vrefintMv = getVrefMv();
4599 int16_t coretemp = getCoreTemperatureCelsius();
4600 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4601 #else
4602 cliPrintLinefeed();
4603 #endif
4605 // Stack and config sizes and usages
4607 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4608 #ifdef STACK_CHECK
4609 cliPrintf(", Stack used: %d", stackUsedSize());
4610 #endif
4611 cliPrintLinefeed();
4613 cliPrintLinef("Config size: %d, Max available config: %d", getEEPROMConfigSize(), getEEPROMStorageSize());
4615 // Sensors
4617 #if defined(USE_SENSOR_NAMES)
4618 const uint32_t detectedSensorsMask = sensorsMask();
4619 for (uint32_t i = 0; ; i++) {
4620 if (sensorTypeNames[i] == NULL) {
4621 break;
4623 const uint32_t mask = (1 << i);
4624 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4625 const uint8_t sensorHardwareIndex = detectedSensors[i];
4626 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4627 if (i) {
4628 cliPrint(", ");
4630 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4631 #if defined(USE_ACC)
4632 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4633 cliPrintf(".%c", acc.dev.revisionCode);
4635 #endif
4638 cliPrintLinefeed();
4639 #endif /* USE_SENSOR_NAMES */
4641 // Uptime and wall clock
4643 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4645 #ifdef USE_RTC_TIME
4646 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4647 dateTime_t dt;
4648 if (rtcGetDateTime(&dt)) {
4649 dateTimeFormatLocal(buf, &dt);
4650 cliPrintf(", Current Time: %s", buf);
4652 #endif
4653 cliPrintLinefeed();
4655 // Run status
4657 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
4658 const int rxRate = currentRxRefreshRate == 0 ? 0 : (int)(1000000.0f / ((float)currentRxRefreshRate));
4659 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
4660 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4661 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
4663 // Battery meter
4665 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4667 // Other devices and status
4669 #ifdef USE_I2C
4670 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4671 #else
4672 const uint16_t i2cErrorCounter = 0;
4673 #endif
4674 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4676 #ifdef USE_SDCARD
4677 cliSdInfo(NULL);
4678 #endif
4680 cliPrint("Arming disable flags:");
4681 armingDisableFlags_e flags = getArmingDisableFlags();
4682 while (flags) {
4683 const int bitpos = ffs(flags) - 1;
4684 flags &= ~(1 << bitpos);
4685 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4687 cliPrintLinefeed();
4690 #if defined(USE_TASK_STATISTICS)
4691 static void cliTasks(char *cmdline)
4693 UNUSED(cmdline);
4694 int maxLoadSum = 0;
4695 int averageLoadSum = 0;
4697 #ifndef MINIMAL_CLI
4698 if (systemConfig()->task_statistics) {
4699 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4700 } else {
4701 cliPrintLine("Task list");
4703 #endif
4704 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4705 cfTaskInfo_t taskInfo;
4706 getTaskInfo(taskId, &taskInfo);
4707 if (taskInfo.isEnabled) {
4708 int taskFrequency;
4709 int subTaskFrequency = 0;
4710 if (taskId == TASK_GYROPID) {
4711 subTaskFrequency = taskInfo.movingAverageCycleTime == 0.0f ? 0.0f : (int)(1000000.0f / (taskInfo.movingAverageCycleTime));
4712 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
4713 if (pidConfig()->pid_process_denom > 1) {
4714 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4715 } else {
4716 taskFrequency = subTaskFrequency;
4717 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
4719 } else {
4720 taskFrequency = taskInfo.averageDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.averageDeltaTime));
4721 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4723 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
4724 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
4725 if (taskId != TASK_SERIAL) {
4726 maxLoadSum += maxLoad;
4727 averageLoadSum += averageLoad;
4729 if (systemConfig()->task_statistics) {
4730 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4731 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
4732 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
4733 } else {
4734 cliPrintLinef("%6d", taskFrequency);
4736 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
4737 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
4740 schedulerResetTaskMaxExecutionTime(taskId);
4743 if (systemConfig()->task_statistics) {
4744 cfCheckFuncInfo_t checkFuncInfo;
4745 getCheckFuncInfo(&checkFuncInfo);
4746 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
4747 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
4750 #endif
4752 static void cliVersion(char *cmdline)
4754 UNUSED(cmdline);
4756 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4757 FC_FIRMWARE_NAME,
4758 targetName,
4759 systemConfig()->boardIdentifier,
4760 FC_VERSION_STRING,
4761 buildDate,
4762 buildTime,
4763 shortGitRevision,
4764 MSP_API_VERSION_STRING
4766 #ifdef FEATURE_CUT_LEVEL
4767 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL);
4768 #else
4769 cliPrintLinefeed();
4770 #endif
4773 #ifdef USE_RC_SMOOTHING_FILTER
4774 static void cliRcSmoothing(char *cmdline)
4776 UNUSED(cmdline);
4777 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
4778 cliPrint("# RC Smoothing Type: ");
4779 if (rxConfig()->rc_smoothing_type == RC_SMOOTHING_TYPE_FILTER) {
4780 cliPrintLine("FILTER");
4781 uint16_t avgRxFrameMs = rcSmoothingData->averageFrameTimeUs;
4782 if (rcSmoothingAutoCalculate()) {
4783 cliPrint("# Detected RX frame rate: ");
4784 if (avgRxFrameMs == 0) {
4785 cliPrintLine("NO SIGNAL");
4786 } else {
4787 cliPrintLinef("%d.%dms", avgRxFrameMs / 1000, avgRxFrameMs % 1000);
4790 cliPrint("# Input filter type: ");
4791 cliPrintLinef(lookupTables[TABLE_RC_SMOOTHING_INPUT_TYPE].values[rcSmoothingData->inputFilterType]);
4792 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingData->inputCutoffFrequency);
4793 if (rcSmoothingData->inputCutoffSetting == 0) {
4794 cliPrintLine("(auto)");
4795 } else {
4796 cliPrintLine("(manual)");
4798 cliPrint("# Derivative filter type: ");
4799 cliPrintLinef(lookupTables[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE].values[rcSmoothingData->derivativeFilterType]);
4800 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingData->derivativeCutoffFrequency);
4801 if (rcSmoothingData->derivativeFilterType == RC_SMOOTHING_DERIVATIVE_OFF) {
4802 cliPrintLine("off)");
4803 } else {
4804 if (rcSmoothingData->derivativeCutoffSetting == 0) {
4805 cliPrintLine("auto)");
4806 } else {
4807 cliPrintLine("manual)");
4810 } else {
4811 cliPrintLine("INTERPOLATION");
4814 #endif // USE_RC_SMOOTHING_FILTER
4816 #if defined(USE_RESOURCE_MGMT)
4818 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
4820 typedef struct {
4821 const uint8_t owner;
4822 pgn_t pgn;
4823 uint8_t stride;
4824 uint8_t offset;
4825 const uint8_t maxIndex;
4826 } cliResourceValue_t;
4828 // Handy macros for keeping the table tidy.
4829 // DEFS : Single entry
4830 // DEFA : Array of uint8_t (stride = 1)
4831 // DEFW : Wider stride case; array of structs.
4833 #define DEFS(owner, pgn, type, member) \
4834 { owner, pgn, 0, offsetof(type, member), 0 }
4836 #define DEFA(owner, pgn, type, member, max) \
4837 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
4839 #define DEFW(owner, pgn, type, member, max) \
4840 { owner, pgn, sizeof(type), offsetof(type, member), max }
4842 const cliResourceValue_t resourceTable[] = {
4843 #ifdef USE_BEEPER
4844 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
4845 #endif
4846 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
4847 #ifdef USE_SERVOS
4848 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
4849 #endif
4850 #if defined(USE_PPM)
4851 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
4852 #endif
4853 #if defined(USE_PWM)
4854 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
4855 #endif
4856 #ifdef USE_RANGEFINDER_HCSR04
4857 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
4858 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
4859 #endif
4860 #ifdef USE_LED_STRIP
4861 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
4862 #endif
4863 #ifdef USE_UART
4864 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
4865 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
4866 #endif
4867 #ifdef USE_INVERTER
4868 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
4869 #endif
4870 #ifdef USE_I2C
4871 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
4872 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
4873 #endif
4874 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
4875 #ifdef USE_SPEKTRUM_BIND
4876 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
4877 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
4878 #endif
4879 #ifdef USE_TRANSPONDER
4880 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
4881 #endif
4882 #ifdef USE_SPI
4883 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
4884 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
4885 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
4886 #endif
4887 #ifdef USE_ESCSERIAL
4888 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
4889 #endif
4890 #ifdef USE_CAMERA_CONTROL
4891 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
4892 #endif
4893 #ifdef USE_ADC
4894 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
4895 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
4896 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
4897 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
4898 #endif
4899 #ifdef USE_BARO
4900 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
4901 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
4902 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
4903 #endif
4904 #ifdef USE_MAG
4905 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
4906 #ifdef USE_MAG_DATA_READY_SIGNAL
4907 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
4908 #endif
4909 #endif
4910 #ifdef USE_SDCARD_SPI
4911 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
4912 #endif
4913 #ifdef USE_SDCARD
4914 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
4915 #endif
4916 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
4917 DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
4918 DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
4919 DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
4920 DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
4921 DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
4922 DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
4923 #endif
4924 #ifdef USE_PINIO
4925 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
4926 #endif
4927 #if defined(USE_USB_MSC)
4928 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
4929 #endif
4930 #ifdef USE_FLASH_CHIP
4931 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
4932 #endif
4933 #ifdef USE_MAX7456
4934 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
4935 #endif
4936 #ifdef USE_RX_SPI
4937 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
4938 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
4939 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
4940 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
4941 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
4942 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
4943 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
4944 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
4945 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
4946 #endif
4947 #endif
4948 #endif
4949 #ifdef USE_GYRO_EXTI
4950 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
4951 #endif
4952 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
4953 #ifdef USE_USB_DETECT
4954 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
4955 #endif
4956 #ifdef USE_VTX_RTC6705
4957 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
4958 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
4959 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
4960 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
4961 #endif
4962 #ifdef USE_PIN_PULL_UP_DOWN
4963 DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
4964 DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
4965 #endif
4968 #undef DEFS
4969 #undef DEFA
4970 #undef DEFW
4972 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
4974 const pgRegistry_t* rec = pgFind(value.pgn);
4975 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
4978 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
4980 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
4981 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
4982 const char* owner = ownerNames[resourceTable[i].owner];
4983 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
4984 const void *currentConfig;
4985 const void *defaultConfig;
4986 if (isReadingConfigFromCopy()) {
4987 currentConfig = pg->copy;
4988 defaultConfig = pg->address;
4989 } else {
4990 currentConfig = pg->address;
4991 defaultConfig = NULL;
4994 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
4995 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
4996 ioTag_t ioTagDefault = NULL;
4997 if (defaultConfig) {
4998 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5001 const bool equalsDefault = ioTag == ioTagDefault;
5002 const char *format = "resource %s %d %c%02d";
5003 const char *formatUnassigned = "resource %s %d NONE";
5004 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5005 if (ioTagDefault) {
5006 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
5007 } else if (defaultConfig) {
5008 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5010 if (ioTag) {
5011 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
5012 } else if (!(dumpMask & HIDE_UNUSED)) {
5013 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5019 static void printResourceOwner(uint8_t owner, uint8_t index)
5021 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
5023 if (resourceTable[owner].maxIndex > 0) {
5024 cliPrintf(" %d", RESOURCE_INDEX(index));
5028 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
5030 if (!newTag) {
5031 return;
5034 const char * format = "\r\nNOTE: %c%02d already assigned to ";
5035 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
5036 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
5037 ioTag_t *tag = getIoTag(resourceTable[r], i);
5038 if (*tag == newTag) {
5039 bool cleared = false;
5040 if (r == resourceIndex) {
5041 if (i == index) {
5042 continue;
5044 *tag = IO_TAG_NONE;
5045 cleared = true;
5048 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
5050 printResourceOwner(r, i);
5052 if (cleared) {
5053 cliPrintf(". ");
5054 printResourceOwner(r, i);
5055 cliPrintf(" disabled");
5058 cliPrintLine(".");
5064 static bool strToPin(char *pch, ioTag_t *tag)
5066 if (strcasecmp(pch, "NONE") == 0) {
5067 *tag = IO_TAG_NONE;
5068 return true;
5069 } else {
5070 unsigned pin = 0;
5071 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
5073 if (port < 8) {
5074 pch++;
5075 pin = atoi(pch);
5076 if (pin < 16) {
5077 *tag = DEFIO_TAG_MAKE(port, pin);
5078 return true;
5082 return false;
5085 #ifdef USE_DMA
5086 static void showDma(void)
5088 cliPrintLinefeed();
5090 #ifdef MINIMAL_CLI
5091 cliPrintLine("DMA:");
5092 #else
5093 cliPrintLine("Currently active DMA:");
5094 cliRepeat('-', 20);
5095 #endif
5096 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
5097 const resourceOwner_t *owner = dmaGetOwner(i);
5099 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
5100 if (owner->resourceIndex > 0) {
5101 cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
5102 } else {
5103 cliPrintLinef(" %s", ownerNames[owner->owner]);
5107 #endif
5109 #ifdef USE_DMA_SPEC
5111 typedef struct dmaoptEntry_s {
5112 char *device;
5113 dmaPeripheral_e peripheral;
5114 pgn_t pgn;
5115 uint8_t stride;
5116 uint8_t offset;
5117 uint8_t maxIndex;
5118 uint32_t presenceMask;
5119 } dmaoptEntry_t;
5121 #define MASK_IGNORED (0)
5123 // Handy macros for keeping the table tidy.
5124 // DEFS : Single entry
5125 // DEFA : Array of uint8_t (stride = 1)
5126 // DEFW : Wider stride case; array of structs.
5128 #define DEFS(device, peripheral, pgn, type, member) \
5129 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5131 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5132 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5134 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5135 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
5137 dmaoptEntry_t dmaoptEntryTable[] = {
5138 DEFW("SPI_TX", DMA_PERIPH_SPI_TX, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5139 DEFW("SPI_RX", DMA_PERIPH_SPI_RX, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5140 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
5141 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
5142 DEFW("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5143 DEFW("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5144 #ifdef STM32H7
5145 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
5146 #endif
5149 #undef DEFS
5150 #undef DEFA
5151 #undef DEFW
5153 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5154 #define DMA_OPT_STRING_BUFSIZE 5
5156 #ifdef STM32H7
5157 #define DMA_CHANREQ_STRING "Request"
5158 #else
5159 #define DMA_CHANREQ_STRING "Channel"
5160 #endif
5162 #define DMASPEC_FORMAT_STRING "DMA%d Stream %d " DMA_CHANREQ_STRING " %d"
5164 static void optToString(int optval, char *buf)
5166 if (optval == DMA_OPT_UNUSED) {
5167 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
5168 } else {
5169 tfp_sprintf(buf, "%d", optval);
5173 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5175 if (dmaopt != DMA_OPT_UNUSED) {
5176 printValue(dumpMask, equalsDefault,
5177 "dma %s %d %d",
5178 entry->device, DMA_OPT_UI_INDEX(index), dmaopt);
5180 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5181 dmaCode_t dmaCode = 0;
5182 if (dmaChannelSpec) {
5183 dmaCode = dmaChannelSpec->code;
5185 printValue(dumpMask, equalsDefault,
5186 "# %s %d: " DMASPEC_FORMAT_STRING,
5187 entry->device, DMA_OPT_UI_INDEX(index), DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5188 } else if (!(dumpMask & HIDE_UNUSED)) {
5189 printValue(dumpMask, equalsDefault,
5190 "dma %s %d NONE",
5191 entry->device, DMA_OPT_UI_INDEX(index));
5195 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5197 const pgRegistry_t* pg = pgFind(entry->pgn);
5198 const void *currentConfig;
5199 const void *defaultConfig;
5201 if (isReadingConfigFromCopy()) {
5202 currentConfig = pg->copy;
5203 defaultConfig = pg->address;
5204 } else {
5205 currentConfig = pg->address;
5206 defaultConfig = NULL;
5209 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5210 dmaoptValue_t defaultOpt;
5212 if (defaultConfig) {
5213 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5214 } else {
5215 defaultOpt = DMA_OPT_UNUSED;
5218 bool equalsDefault = currentOpt == defaultOpt;
5219 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5221 if (defaultConfig) {
5222 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5225 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5226 return headingStr;
5229 #if defined(USE_TIMER_MGMT)
5230 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5232 const char *format = "dma pin %c%02d %d";
5234 if (dmaopt != DMA_OPT_UNUSED) {
5235 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5236 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5237 dmaopt
5240 if (printDetails) {
5241 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5242 dmaCode_t dmaCode = 0;
5243 if (dmaChannelSpec) {
5244 dmaCode = dmaChannelSpec->code;
5245 printValue(dumpMask, false,
5246 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5247 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5248 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5252 } else if (!(dumpMask & HIDE_UNUSED)) {
5253 printValue(dumpMask, equalsDefault,
5254 "dma pin %c%02d NONE",
5255 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5260 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5262 const ioTag_t ioTag = currentConfig[index].ioTag;
5264 if (!ioTag) {
5265 return headingStr;
5268 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5269 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5271 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5272 bool equalsDefault = defaultDmaopt == dmaopt;
5273 if (defaultConfig) {
5274 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5275 if (defaultConfig[i].ioTag == ioTag) {
5276 defaultDmaopt = defaultConfig[i].dmaopt;
5278 // 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.
5279 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5281 tagsInUse[index] = true;
5283 break;
5288 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5290 if (defaultConfig) {
5291 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5294 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5295 return headingStr;
5297 #endif
5299 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5301 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5302 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5303 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5304 for (int index = 0; index < entry->maxIndex; index++) {
5305 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5309 #if defined(USE_TIMER_MGMT)
5310 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5311 const timerIOConfig_t *currentConfig;
5312 const timerIOConfig_t *defaultConfig;
5314 if (isReadingConfigFromCopy()) {
5315 currentConfig = (timerIOConfig_t *)pg->copy;
5316 defaultConfig = (timerIOConfig_t *)pg->address;
5317 } else {
5318 currentConfig = (timerIOConfig_t *)pg->address;
5319 defaultConfig = NULL;
5322 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5323 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5324 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5327 if (defaultConfig) {
5328 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5329 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5330 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5331 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5332 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5334 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5338 #endif
5341 static void cliDmaopt(char *cmdline)
5343 char *pch = NULL;
5344 char *saveptr;
5346 // Peripheral name or command option
5347 pch = strtok_r(cmdline, " ", &saveptr);
5348 if (!pch) {
5349 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5351 return;
5352 } else if (strcasecmp(pch, "list") == 0) {
5353 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5355 return;
5358 dmaoptEntry_t *entry = NULL;
5359 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5360 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5361 entry = &dmaoptEntryTable[i];
5365 if (!entry && strcasecmp(pch, "pin") != 0) {
5366 cliPrintErrorLinef("BAD DEVICE: %s", pch);
5367 return;
5370 // Index
5371 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5373 int index = 0;
5374 dmaoptValue_t *optaddr = NULL;
5376 ioTag_t ioTag = IO_TAG_NONE;
5377 #if defined(USE_TIMER_MGMT)
5378 timerIOConfig_t *timerIoConfig = NULL;
5379 #endif
5380 const timerHardware_t *timer = NULL;
5381 pch = strtok_r(NULL, " ", &saveptr);
5382 if (entry) {
5383 index = atoi(pch) - 1;
5384 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5385 cliPrintErrorLinef("BAD INDEX: '%s'", pch ? pch : "");
5386 return;
5389 const pgRegistry_t* pg = pgFind(entry->pgn);
5390 const void *currentConfig;
5391 if (isWritingConfigToCopy()) {
5392 currentConfig = pg->copy;
5393 } else {
5394 currentConfig = pg->address;
5396 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5397 orgval = *optaddr;
5398 } else {
5399 // It's a pin
5400 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5401 cliPrintErrorLinef("INVALID PIN: '%s'", pch ? pch : "");
5403 return;
5406 orgval = dmaoptByTag(ioTag);
5407 #if defined(USE_TIMER_MGMT)
5408 timerIoConfig = timerIoConfigByTag(ioTag);
5409 #endif
5410 timer = timerGetByTag(ioTag);
5413 // opt or list
5414 pch = strtok_r(NULL, " ", &saveptr);
5415 if (!pch) {
5416 if (entry) {
5417 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5419 #if defined(USE_TIMER_MGMT)
5420 else {
5421 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5423 #endif
5425 return;
5426 } else if (strcasecmp(pch, "list") == 0) {
5427 // Show possible opts
5428 const dmaChannelSpec_t *dmaChannelSpec;
5429 if (entry) {
5430 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5431 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5433 } else {
5434 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5435 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5439 return;
5440 } else if (pch) {
5441 int optval;
5442 if (strcasecmp(pch, "none") == 0) {
5443 optval = DMA_OPT_UNUSED;
5444 } else {
5445 optval = atoi(pch);
5447 if (entry) {
5448 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5449 cliPrintErrorLinef("INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5451 return;
5453 } else {
5454 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5455 cliPrintErrorLinef("INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5457 return;
5462 char optvalString[DMA_OPT_STRING_BUFSIZE];
5463 optToString(optval, optvalString);
5465 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5466 optToString(orgval, orgvalString);
5468 if (optval != orgval) {
5469 if (entry) {
5470 *optaddr = optval;
5472 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5473 } else {
5474 #if defined(USE_TIMER_MGMT)
5475 timerIoConfig->dmaopt = optval;
5476 #endif
5478 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5480 } else {
5481 if (entry) {
5482 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5483 } else {
5484 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5489 #endif // USE_DMA_SPEC
5491 #ifdef USE_DMA
5492 static void cliDma(char* cmdline)
5494 int len = strlen(cmdline);
5495 if (len && strncasecmp(cmdline, "show", len) == 0) {
5496 showDma();
5498 return;
5501 #if defined(USE_DMA_SPEC)
5502 cliDmaopt(cmdline);
5503 #else
5504 cliShowParseError();
5505 #endif
5507 #endif
5508 #endif // USE_RESOURCE_MGMT
5510 #ifdef USE_TIMER_MGMT
5511 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5513 const char *format = "timer %c%02d AF%d";
5514 const char *emptyFormat = "timer %c%02d NONE";
5516 if (timerIndex > 0) {
5517 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5518 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5519 IO_GPIOPortIdxByTag(ioTag) + 'A',
5520 IO_GPIOPinIdxByTag(ioTag),
5521 timer->alternateFunction
5523 if (printDetails) {
5524 printValue(dumpMask, false,
5525 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5526 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5527 timerGetTIMNumber(timer->tim),
5528 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5529 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5530 timer->alternateFunction
5533 } else {
5534 printValue(dumpMask, equalsDefault, emptyFormat,
5535 IO_GPIOPortIdxByTag(ioTag) + 'A',
5536 IO_GPIOPinIdxByTag(ioTag)
5541 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5543 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5544 const timerIOConfig_t *currentConfig;
5545 const timerIOConfig_t *defaultConfig;
5547 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5548 if (isReadingConfigFromCopy()) {
5549 currentConfig = (timerIOConfig_t *)pg->copy;
5550 defaultConfig = (timerIOConfig_t *)pg->address;
5551 } else {
5552 currentConfig = (timerIOConfig_t *)pg->address;
5553 defaultConfig = NULL;
5556 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5557 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5558 const ioTag_t ioTag = currentConfig[i].ioTag;
5560 if (!ioTag) {
5561 continue;
5564 const uint8_t timerIndex = currentConfig[i].index;
5566 uint8_t defaultTimerIndex = 0;
5567 if (defaultConfig) {
5568 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5569 if (defaultConfig[i].ioTag == ioTag) {
5570 defaultTimerIndex = defaultConfig[i].index;
5571 tagsInUse[i] = true;
5573 break;
5578 const bool equalsDefault = defaultTimerIndex == timerIndex;
5579 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5580 if (defaultConfig && defaultTimerIndex) {
5581 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5584 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5587 if (defaultConfig) {
5588 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5589 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5590 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5591 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5593 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5599 #define TIMER_INDEX_UNDEFINED -1
5600 #define TIMER_AF_STRING_BUFSIZE 5
5602 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5604 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5605 if (!timer) {
5606 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5607 } else {
5608 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5612 static void showTimers(void)
5614 cliPrintLinefeed();
5616 #ifdef MINIMAL_CLI
5617 cliPrintLine("Timers:");
5618 #else
5619 cliPrintLine("Currently active Timers:");
5620 cliRepeat('-', 23);
5621 #endif
5623 #ifdef USE_DSHOT_BITBANG
5624 resourceOwner_t bitbangOwner = { OWNER_DSHOT_BITBANG, 0 };
5625 #endif
5626 int8_t timerNumber;
5627 for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
5628 cliPrintf("TIM%d:", timerNumber);
5629 bool timerUsed = false;
5630 for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
5631 const resourceOwner_t *timerOwner = timerGetOwner(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
5632 #ifdef USE_DSHOT_BITBANG
5633 if (!timerOwner->owner) {
5634 const timerHardware_t* timer;
5635 int pacerIndex = 0;
5636 while ((timer = dshotBitbangGetPacerTimer(pacerIndex++))) {
5637 if (timerGetTIMNumber(timer->tim) == timerNumber && timer->channel == CC_CHANNEL_FROM_INDEX(timerIndex)) {
5638 timerOwner = &bitbangOwner;
5639 bitbangOwner.resourceIndex++;
5640 break;
5644 #endif
5645 if (timerOwner->owner) {
5646 if (!timerUsed) {
5647 timerUsed = true;
5649 cliPrintLinefeed();
5652 if (timerOwner->resourceIndex > 0) {
5653 cliPrintLinef(" CH%d: %s %d", timerIndex + 1, ownerNames[timerOwner->owner], timerOwner->resourceIndex);
5654 } else {
5655 cliPrintLinef(" CH%d: %s", timerIndex + 1, ownerNames[timerOwner->owner]);
5660 if (!timerUsed) {
5661 cliPrintLine(" FREE");
5666 static void cliTimer(char *cmdline)
5668 int len = strlen(cmdline);
5670 if (len == 0) {
5671 printTimer(DUMP_MASTER, NULL);
5673 return;
5674 } else if (strncasecmp(cmdline, "list", len) == 0) {
5675 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5677 return;
5678 } else if (strncasecmp(cmdline, "show", len) == 0) {
5679 showTimers();
5681 return;
5684 char *pch = NULL;
5685 char *saveptr;
5687 ioTag_t ioTag = IO_TAG_NONE;
5688 pch = strtok_r(cmdline, " ", &saveptr);
5689 if (!pch || !strToPin(pch, &ioTag)) {
5690 cliShowParseError();
5692 return;
5693 } else if (!IOGetByTag(ioTag)) {
5694 cliPrintErrorLinef("PIN NOT USED ON BOARD.");
5696 return;
5699 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5700 bool isExistingTimerOpt = false;
5701 /* find existing entry, or go for next available */
5702 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5703 if (timerIOConfig(i)->ioTag == ioTag) {
5704 timerIOIndex = i;
5705 isExistingTimerOpt = true;
5707 break;
5710 /* first available empty slot */
5711 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5712 timerIOIndex = i;
5716 if (timerIOIndex < 0) {
5717 cliPrintErrorLinef("PIN TIMER MAP FULL.");
5719 return;
5722 pch = strtok_r(NULL, " ", &saveptr);
5723 if (pch) {
5724 int timerIndex;
5725 if (strcasecmp(pch, "list") == 0) {
5726 /* output the list of available options */
5727 const timerHardware_t *timer;
5728 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5729 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5730 timer->alternateFunction,
5731 timerGetTIMNumber(timer->tim),
5732 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5733 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
5737 return;
5738 } else if (strcasecmp(pch, "none") == 0) {
5739 timerIndex = TIMER_INDEX_UNDEFINED;
5740 } else if (strncasecmp(pch, "af", 2) == 0) {
5741 unsigned alternateFunction = atoi(&pch[2]);
5743 const timerHardware_t *timer;
5744 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5745 if (timer->alternateFunction == alternateFunction) {
5746 timerIndex = index;
5748 break;
5752 if (!timer) {
5753 cliPrintErrorLinef("INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5755 return;
5757 } else {
5758 timerIndex = atoi(pch);
5760 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex + 1);
5762 if (!timer) {
5763 cliPrintErrorLinef("INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5765 return;
5769 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
5770 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
5771 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
5772 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
5774 char optvalString[DMA_OPT_STRING_BUFSIZE];
5775 alternateFunctionToString(ioTag, timerIndex, optvalString);
5777 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5778 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
5780 if (timerIndex == oldTimerIndex) {
5781 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
5782 } else {
5783 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5786 return;
5787 } else {
5788 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
5790 return;
5793 #endif
5795 #if defined(USE_RESOURCE_MGMT)
5796 static void cliResource(char *cmdline)
5798 char *pch = NULL;
5799 char *saveptr;
5801 pch = strtok_r(cmdline, " ", &saveptr);
5802 if (!pch) {
5803 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
5805 return;
5806 } else if (strcasecmp(pch, "show") == 0) {
5807 #ifdef MINIMAL_CLI
5808 cliPrintLine("IO");
5809 #else
5810 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
5811 cliRepeat('-', 20);
5812 #endif
5813 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
5814 const char* owner;
5815 owner = ownerNames[ioRecs[i].owner];
5817 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
5818 if (ioRecs[i].index > 0) {
5819 cliPrintf(" %d", ioRecs[i].index);
5821 cliPrintLinefeed();
5824 pch = strtok_r(NULL, " ", &saveptr);
5825 if (strcasecmp(pch, "all") == 0) {
5826 #if defined(USE_TIMER_MGMT)
5827 cliTimer("show");
5828 #endif
5829 #if defined(USE_DMA)
5830 cliDma("show");
5831 #endif
5834 return;
5837 uint8_t resourceIndex = 0;
5838 int index = 0;
5839 for (resourceIndex = 0; ; resourceIndex++) {
5840 if (resourceIndex >= ARRAYLEN(resourceTable)) {
5841 cliPrintErrorLinef("INVALID RESOURCE NAME: '%s'", pch);
5842 return;
5845 const char * resourceName = ownerNames[resourceTable[resourceIndex].owner];
5846 if (strncasecmp(pch, resourceName, strlen(resourceName)) == 0) {
5847 break;
5851 pch = strtok_r(NULL, " ", &saveptr);
5852 index = atoi(pch);
5854 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
5855 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
5856 cliShowArgumentRangeError("INDEX", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
5857 return;
5859 index -= 1;
5861 pch = strtok_r(NULL, " ", &saveptr);
5864 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
5866 if (strlen(pch) > 0) {
5867 if (strToPin(pch, tag)) {
5868 if (*tag == IO_TAG_NONE) {
5869 #ifdef MINIMAL_CLI
5870 cliPrintLine("Freed");
5871 #else
5872 cliPrintLine("Resource is freed");
5873 #endif
5874 return;
5875 } else {
5876 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
5877 if (rec) {
5878 resourceCheck(resourceIndex, index, *tag);
5879 #ifdef MINIMAL_CLI
5880 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5881 #else
5882 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5883 #endif
5884 } else {
5885 cliShowParseError();
5887 return;
5892 cliShowParseError();
5894 #endif
5896 #ifdef USE_DSHOT_TELEMETRY
5897 static void cliDshotTelemetryInfo(char *cmdline)
5899 UNUSED(cmdline);
5901 if (useDshotTelemetry) {
5902 cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
5903 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
5904 uint32_t directionChangeCycles = dshotDMAHandlerCycleCounters.changeDirectionCompletedAt - dshotDMAHandlerCycleCounters.irqAt;
5905 uint32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
5906 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
5907 cliPrintLinefeed();
5909 #ifdef USE_DSHOT_TELEMETRY_STATS
5910 cliPrintLine("Motor eRPM RPM Hz Invalid");
5911 cliPrintLine("===== ======= ====== ===== =======");
5912 #else
5913 cliPrintLine("Motor eRPM RPM Hz");
5914 cliPrintLine("===== ======= ====== =====");
5915 #endif
5916 for (uint8_t i = 0; i < getMotorCount(); i++) {
5917 cliPrintf("%5d %7d %6d %5d ", i,
5918 (int)getDshotTelemetry(i) * 100,
5919 (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount,
5920 (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount / 60);
5921 #ifdef USE_DSHOT_TELEMETRY_STATS
5922 if (isDshotMotorTelemetryActive(i)) {
5923 const int calcPercent = getDshotTelemetryMotorInvalidPercent(i);
5924 cliPrintLinef("%3d.%02d%%", calcPercent / 100, calcPercent % 100);
5925 } else {
5926 cliPrintLine("NO DATA");
5928 #else
5929 cliPrintLinefeed();
5930 #endif
5932 cliPrintLinefeed();
5934 const int len = MAX_GCR_EDGES;
5935 #ifdef DEBUG_BBDECODE
5936 extern uint16_t bbBuffer[134];
5937 for (int i = 0; i < 134; i++) {
5938 cliPrintf("%u ", (int)bbBuffer[i]);
5940 cliPrintLinefeed();
5941 #endif
5942 for (int i = 0; i < len; i++) {
5943 cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
5945 cliPrintLinefeed();
5946 for (int i = 1; i < len; i++) {
5947 cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
5949 cliPrintLinefeed();
5950 } else {
5951 cliPrintLine("Dshot telemetry not enabled");
5954 #endif
5956 static void printConfig(char *cmdline, bool doDiff)
5958 dumpFlags_t dumpMask = DUMP_MASTER;
5959 char *options;
5960 if ((options = checkCommand(cmdline, "master"))) {
5961 dumpMask = DUMP_MASTER; // only
5962 } else if ((options = checkCommand(cmdline, "profile"))) {
5963 dumpMask = DUMP_PROFILE; // only
5964 } else if ((options = checkCommand(cmdline, "rates"))) {
5965 dumpMask = DUMP_RATES; // only
5966 } else if ((options = checkCommand(cmdline, "hardware"))) {
5967 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
5968 } else if ((options = checkCommand(cmdline, "all"))) {
5969 dumpMask = DUMP_ALL; // all profiles and rates
5970 } else {
5971 options = cmdline;
5974 if (doDiff) {
5975 dumpMask = dumpMask | DO_DIFF;
5978 if (checkCommand(options, "defaults")) {
5979 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
5980 } else if (checkCommand(options, "bare")) {
5981 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
5984 backupAndResetConfigs((dumpMask & BARE) == 0);
5986 #ifdef USE_CLI_BATCH
5987 bool batchModeEnabled = false;
5988 #endif
5989 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
5990 cliPrintHashLine("version");
5991 cliVersion(NULL);
5993 if (!(dumpMask & BARE)) {
5994 #ifdef USE_CLI_BATCH
5995 cliPrintHashLine("start the command batch");
5996 cliPrintLine("batch start");
5997 batchModeEnabled = true;
5998 #endif
6000 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
6001 cliPrintHashLine("reset configuration to default settings");
6002 cliPrintLine("defaults nosave");
6006 #if defined(USE_BOARD_INFO)
6007 cliPrintLinefeed();
6008 printBoardName(dumpMask);
6009 printManufacturerId(dumpMask);
6010 #endif
6012 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
6013 cliMcuId(NULL);
6014 #if defined(USE_SIGNATURE)
6015 cliSignature("");
6016 #endif
6019 if (!(dumpMask & HARDWARE_ONLY)) {
6020 printName(dumpMask, &pilotConfig_Copy);
6023 #ifdef USE_RESOURCE_MGMT
6024 printResource(dumpMask, "resources");
6025 #if defined(USE_TIMER_MGMT)
6026 printTimer(dumpMask, "timer");
6027 #endif
6028 #ifdef USE_DMA_SPEC
6029 printDmaopt(dumpMask, "dma");
6030 #endif
6031 #endif
6033 if (!(dumpMask & HARDWARE_ONLY)) {
6034 #ifndef USE_QUAD_MIXER_ONLY
6035 const char *mixerHeadingStr = "mixer";
6036 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
6037 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
6038 const char *formatMixer = "mixer %s";
6039 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
6040 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
6042 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
6044 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
6046 #ifdef USE_SERVOS
6047 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
6049 const char *servoMixHeadingStr = "servo mixer";
6050 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
6051 cliPrintHashLine(servoMixHeadingStr);
6052 cliPrintLine("smix reset\r\n");
6053 servoMixHeadingStr = NULL;
6055 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
6056 #endif
6057 #endif
6059 printFeature(dumpMask, featureConfig_Copy.enabledFeatures, *getFeatureMask(), "feature");
6061 #if defined(USE_BEEPER)
6062 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
6064 #if defined(USE_DSHOT)
6065 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
6066 #endif
6067 #endif // USE_BEEPER
6069 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
6071 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
6073 #ifdef USE_LED_STRIP_STATUS_MODE
6074 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
6076 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
6078 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
6079 #endif
6081 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
6083 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
6085 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
6087 #ifdef USE_VTX_CONTROL
6088 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
6089 #endif
6091 #ifdef USE_VTX_TABLE
6092 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
6093 #endif
6095 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
6098 if (dumpMask & HARDWARE_ONLY) {
6099 dumpAllValues(HARDWARE_VALUE, dumpMask, "master");
6100 } else {
6101 dumpAllValues(MASTER_VALUE, dumpMask, "master");
6103 if (dumpMask & DUMP_ALL) {
6104 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
6105 cliDumpPidProfile(pidProfileIndex, dumpMask);
6108 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
6110 if (!(dumpMask & BARE)) {
6111 cliPrintHashLine("restore original profile selection");
6113 cliProfile("");
6116 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
6118 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
6119 cliDumpRateProfile(rateIndex, dumpMask);
6122 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
6124 if (!(dumpMask & BARE)) {
6125 cliPrintHashLine("restore original rateprofile selection");
6127 cliRateProfile("");
6129 cliPrintHashLine("save configuration");
6130 cliPrint("save");
6131 #ifdef USE_CLI_BATCH
6132 batchModeEnabled = false;
6133 #endif
6136 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
6137 } else {
6138 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
6140 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
6143 } else if (dumpMask & DUMP_PROFILE) {
6144 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
6145 } else if (dumpMask & DUMP_RATES) {
6146 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
6149 #ifdef USE_CLI_BATCH
6150 if (batchModeEnabled) {
6151 cliPrintHashLine("end the command batch");
6152 cliPrintLine("batch end");
6154 #endif
6156 // restore configs from copies
6157 restoreConfigs();
6160 static void cliDump(char *cmdline)
6162 printConfig(cmdline, false);
6165 static void cliDiff(char *cmdline)
6167 printConfig(cmdline, true);
6170 #if defined(USE_USB_MSC)
6171 static void cliMsc(char *cmdline)
6173 if (mscCheckFilesystemReady()) {
6174 #ifdef USE_RTC_TIME
6175 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
6176 if (!isEmpty(cmdline)) {
6177 timezoneOffsetMinutes = atoi(cmdline);
6178 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
6179 cliPrintErrorLinef("INVALID TIMEZONE OFFSET");
6180 return;
6183 #else
6184 int timezoneOffsetMinutes = 0;
6185 UNUSED(cmdline);
6186 #endif
6187 cliPrintHashLine("Restarting in mass storage mode");
6188 cliPrint("\r\nRebooting");
6189 cliWriterFlush();
6190 waitForSerialPortToFinishTransmitting(cliPort);
6191 motorShutdown();
6193 systemResetToMsc(timezoneOffsetMinutes);
6194 } else {
6195 cliPrintHashLine("Storage not present or failed to initialize!");
6198 #endif
6201 typedef struct {
6202 const char *name;
6203 #ifndef MINIMAL_CLI
6204 const char *description;
6205 const char *args;
6206 #endif
6207 void (*func)(char *cmdline);
6208 } clicmd_t;
6210 #ifndef MINIMAL_CLI
6211 #define CLI_COMMAND_DEF(name, description, args, method) \
6213 name , \
6214 description , \
6215 args , \
6216 method \
6218 #else
6219 #define CLI_COMMAND_DEF(name, description, args, method) \
6221 name, \
6222 method \
6224 #endif
6226 static void cliHelp(char *cmdline);
6228 // should be sorted a..z for bsearch()
6229 const clicmd_t cmdTable[] = {
6230 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
6231 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
6232 #ifdef USE_CLI_BATCH
6233 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
6234 #endif
6235 #if defined(USE_BEEPER)
6236 #if defined(USE_DSHOT)
6237 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6238 "\t<->[name]", cliBeacon),
6239 #endif
6240 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6241 "\t<->[name]", cliBeeper),
6242 #endif // USE_BEEPER
6243 #if defined(USE_RX_SPI) || defined (USE_SERIALRX_SRXL2)
6244 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
6245 #endif
6246 #if defined(USE_FLASH_BOOT_LOADER)
6247 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6248 #else
6249 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6250 #endif
6251 #if defined(USE_BOARD_INFO)
6252 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6253 #endif
6254 #ifdef USE_LED_STRIP_STATUS_MODE
6255 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6256 #endif
6257 #if defined(USE_CUSTOM_DEFAULTS)
6258 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave|bare|show]", cliDefaults),
6259 #else
6260 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave|show]", cliDefaults),
6261 #endif
6262 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6263 #ifdef USE_RESOURCE_MGMT
6265 #ifdef USE_DMA
6266 #ifdef USE_DMA_SPEC
6267 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6268 #else
6269 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6270 #endif
6271 #endif
6273 #endif
6274 #ifdef USE_DSHOT_TELEMETRY
6275 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6276 #endif
6277 #ifdef USE_DSHOT
6278 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6279 #endif
6280 CLI_COMMAND_DEF("dump", "dump configuration",
6281 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6282 #ifdef USE_ESCSERIAL
6283 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6284 #endif
6285 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
6286 CLI_COMMAND_DEF("feature", "configure features",
6287 "list\r\n"
6288 "\t<->[name]", cliFeature),
6289 #ifdef USE_FLASHFS
6290 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6291 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6292 #ifdef USE_FLASH_TOOLS
6293 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6294 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6295 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6296 #endif
6297 #endif
6298 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6299 #ifdef USE_GPS
6300 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6301 #endif
6302 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6303 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6304 #endif
6305 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
6306 #ifdef USE_LED_STRIP_STATUS_MODE
6307 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6308 #endif
6309 #if defined(USE_BOARD_INFO)
6310 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6311 #endif
6312 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6313 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6314 #ifndef USE_QUAD_MIXER_ONLY
6315 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6316 #endif
6317 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6318 #ifdef USE_LED_STRIP_STATUS_MODE
6319 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6320 #endif
6321 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6322 #ifdef USE_USB_MSC
6323 #ifdef USE_RTC_TIME
6324 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6325 #else
6326 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6327 #endif
6328 #endif
6329 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
6330 #ifndef MINIMAL_CLI
6331 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6332 #endif
6333 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6334 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6335 #ifdef USE_RC_SMOOTHING_FILTER
6336 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6337 #endif // USE_RC_SMOOTHING_FILTER
6338 #ifdef USE_RESOURCE_MGMT
6339 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6340 #endif
6341 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6342 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6343 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
6344 #ifdef USE_SDCARD
6345 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6346 #endif
6347 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6348 #if defined(USE_SERIAL_PASSTHROUGH)
6349 #if defined(USE_PINIO)
6350 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode>1] [none|<dtr pinio>|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6351 #else
6352 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6353 #endif
6354 #endif
6355 #ifdef USE_SERVOS
6356 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6357 #endif
6358 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6359 #if defined(USE_SIGNATURE)
6360 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6361 #endif
6362 #ifdef USE_SERVOS
6363 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6364 "\treset\r\n"
6365 "\tload <mixer>\r\n"
6366 "\treverse <servo> <source> r|n", cliServoMix),
6367 #endif
6368 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6369 #if defined(USE_TASK_STATISTICS)
6370 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6371 #endif
6372 #ifdef USE_TIMER_MGMT
6373 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6374 #endif
6375 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6376 #ifdef USE_VTX_CONTROL
6377 #ifdef MINIMAL_CLI
6378 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6379 #else
6380 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6381 #endif
6382 #endif
6383 #ifdef USE_VTX_TABLE
6384 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6385 #endif
6388 static void cliHelp(char *cmdline)
6390 UNUSED(cmdline);
6392 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6393 cliPrint(cmdTable[i].name);
6394 #ifndef MINIMAL_CLI
6395 if (cmdTable[i].description) {
6396 cliPrintf(" - %s", cmdTable[i].description);
6398 if (cmdTable[i].args) {
6399 cliPrintf("\r\n\t%s", cmdTable[i].args);
6401 #endif
6402 cliPrintLinefeed();
6406 static void processCharacter(const char c)
6408 if (bufferIndex && (c == '\n' || c == '\r')) {
6409 // enter pressed
6410 cliPrintLinefeed();
6412 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
6413 if (processingCustomDefaults) {
6414 cliPrint("d: ");
6416 #endif
6418 // Strip comment starting with # from line
6419 char *p = cliBuffer;
6420 p = strchr(p, '#');
6421 if (NULL != p) {
6422 bufferIndex = (uint32_t)(p - cliBuffer);
6425 // Strip trailing whitespace
6426 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6427 bufferIndex--;
6430 // Process non-empty lines
6431 if (bufferIndex > 0) {
6432 cliBuffer[bufferIndex] = 0; // null terminate
6434 const clicmd_t *cmd;
6435 char *options;
6436 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6437 if ((options = checkCommand(cliBuffer, cmd->name))) {
6438 break;
6441 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6442 cmd->func(options);
6443 } else {
6444 cliPrintError("UNKNOWN COMMAND, TRY 'HELP'");
6446 bufferIndex = 0;
6449 memset(cliBuffer, 0, sizeof(cliBuffer));
6451 // 'exit' will reset this flag, so we don't need to print prompt again
6452 if (!cliMode) {
6453 return;
6456 cliPrompt();
6457 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6458 if (!bufferIndex && c == ' ')
6459 return; // Ignore leading spaces
6460 cliBuffer[bufferIndex++] = c;
6461 cliWrite(c);
6465 static void processCharacterInteractive(const char c)
6467 if (c == '\t' || c == '?') {
6468 // do tab completion
6469 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6470 uint32_t i = bufferIndex;
6471 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6472 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) {
6473 continue;
6475 if (!pstart) {
6476 pstart = cmd;
6478 pend = cmd;
6480 if (pstart) { /* Buffer matches one or more commands */
6481 for (; ; bufferIndex++) {
6482 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6483 break;
6484 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6485 /* Unambiguous -- append a space */
6486 cliBuffer[bufferIndex++] = ' ';
6487 cliBuffer[bufferIndex] = '\0';
6488 break;
6490 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6493 if (!bufferIndex || pstart != pend) {
6494 /* Print list of ambiguous matches */
6495 cliPrint("\r\033[K");
6496 for (cmd = pstart; cmd <= pend; cmd++) {
6497 cliPrint(cmd->name);
6498 cliWrite('\t');
6500 cliPrompt();
6501 i = 0; /* Redraw prompt */
6503 for (; i < bufferIndex; i++)
6504 cliWrite(cliBuffer[i]);
6505 } else if (!bufferIndex && c == 4) { // CTRL-D
6506 cliExit(cliBuffer);
6507 return;
6508 } else if (c == 12) { // NewPage / CTRL-L
6509 // clear screen
6510 cliPrint("\033[2J\033[1;1H");
6511 cliPrompt();
6512 } else if (c == 127) {
6513 // backspace
6514 if (bufferIndex) {
6515 cliBuffer[--bufferIndex] = 0;
6516 cliPrint("\010 \010");
6518 } else {
6519 processCharacter(c);
6523 void cliProcess(void)
6525 if (!cliWriter) {
6526 return;
6529 // Flush the buffer to get rid of any MSP data polls sent by configurator after CLI was invoked
6530 cliWriterFlush();
6532 while (serialRxBytesWaiting(cliPort)) {
6533 uint8_t c = serialRead(cliPort);
6535 processCharacterInteractive(c);
6539 #if defined(USE_CUSTOM_DEFAULTS)
6540 bool cliProcessCustomDefaults(void)
6542 char *customDefaultsPtr = customDefaultsStart;
6543 if (processingCustomDefaults || !isCustomDefaults(customDefaultsPtr)) {
6544 return false;
6547 #if !defined(DEBUG_CUSTOM_DEFAULTS)
6548 bufWriter_t *cliWriterTemp = cliWriter;
6549 cliWriter = NULL;
6550 #endif
6551 memcpy(cliBufferTemp, cliBuffer, sizeof(cliBuffer));
6552 uint32_t bufferIndexTemp = bufferIndex;
6553 bufferIndex = 0;
6554 processingCustomDefaults = true;
6556 while (*customDefaultsPtr && *customDefaultsPtr != 0xFF && customDefaultsPtr < customDefaultsEnd) {
6557 processCharacter(*customDefaultsPtr++);
6560 processingCustomDefaults = false;
6561 #if !defined(DEBUG_CUSTOM_DEFAULTS)
6562 cliWriter = cliWriterTemp;
6563 #endif
6564 memcpy(cliBuffer, cliBufferTemp, sizeof(cliBuffer));
6565 bufferIndex = bufferIndexTemp;
6567 systemConfigMutable()->configurationState = CONFIGURATION_STATE_DEFAULTS_CUSTOM;
6569 return true;
6571 #endif
6573 void cliEnter(serialPort_t *serialPort)
6575 cliMode = true;
6576 cliPort = serialPort;
6577 setPrintfSerialPort(cliPort);
6578 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6580 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
6582 #ifndef MINIMAL_CLI
6583 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6584 #else
6585 cliPrintLine("\r\nCLI");
6586 #endif
6587 setArmingDisabled(ARMING_DISABLED_CLI);
6589 cliPrompt();
6591 #ifdef USE_CLI_BATCH
6592 resetCommandBatch();
6593 #endif
6596 #endif // USE_CLI