Cleaned up the scheduler.
[betaflight.git] / src / main / cli / cli.c
blob5d175e4e15ada83a6c1fbe21f8d871f005574c47
1 /*
2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
8 * any later version.
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <math.h>
27 #include <ctype.h>
29 #include "platform.h"
31 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
32 // signal that we're in cli mode
33 bool cliMode = false;
35 #ifdef USE_CLI
37 #include "blackbox/blackbox.h"
39 #include "build/build_config.h"
40 #include "build/debug.h"
41 #include "build/version.h"
43 #include "cli/settings.h"
45 #include "cms/cms.h"
47 #include "common/axis.h"
48 #include "common/color.h"
49 #include "common/maths.h"
50 #include "common/printf.h"
51 #include "common/printf_serial.h"
52 #include "common/strtol.h"
53 #include "common/time.h"
54 #include "common/typeconversion.h"
55 #include "common/utils.h"
57 #include "config/config.h"
58 #include "config/config_eeprom.h"
59 #include "config/feature.h"
61 #include "drivers/accgyro/accgyro.h"
62 #include "drivers/adc.h"
63 #include "drivers/buf_writer.h"
64 #include "drivers/bus_spi.h"
65 #include "drivers/dma.h"
66 #include "drivers/dma_reqmap.h"
67 #include "drivers/dshot.h"
68 #include "drivers/dshot_command.h"
69 #include "drivers/dshot_dpwm.h"
70 #include "drivers/pwm_output_dshot_shared.h"
71 #include "drivers/camera_control.h"
72 #include "drivers/compass/compass.h"
73 #include "drivers/display.h"
74 #include "drivers/dma.h"
75 #include "drivers/flash.h"
76 #include "drivers/inverter.h"
77 #include "drivers/io.h"
78 #include "drivers/io_impl.h"
79 #include "drivers/light_led.h"
80 #include "drivers/motor.h"
81 #include "drivers/rangefinder/rangefinder_hcsr04.h"
82 #include "drivers/resource.h"
83 #include "drivers/sdcard.h"
84 #include "drivers/sensor.h"
85 #include "drivers/serial.h"
86 #include "drivers/serial_escserial.h"
87 #include "drivers/sound_beeper.h"
88 #include "drivers/stack_check.h"
89 #include "drivers/system.h"
90 #include "drivers/time.h"
91 #include "drivers/timer.h"
92 #include "drivers/transponder_ir.h"
93 #include "drivers/usb_msc.h"
94 #include "drivers/vtx_common.h"
95 #include "drivers/vtx_table.h"
97 #include "fc/board_info.h"
98 #include "fc/controlrate_profile.h"
99 #include "fc/core.h"
100 #include "fc/rc.h"
101 #include "fc/rc_adjustments.h"
102 #include "fc/rc_controls.h"
103 #include "fc/runtime_config.h"
105 #include "flight/failsafe.h"
106 #include "flight/imu.h"
107 #include "flight/mixer.h"
108 #include "flight/pid.h"
109 #include "flight/position.h"
110 #include "flight/servos.h"
112 #include "io/asyncfatfs/asyncfatfs.h"
113 #include "io/beeper.h"
114 #include "io/flashfs.h"
115 #include "io/gimbal.h"
116 #include "io/gps.h"
117 #include "io/ledstrip.h"
118 #include "io/serial.h"
119 #include "io/transponder_ir.h"
120 #include "io/usb_msc.h"
121 #include "io/vtx_control.h"
122 #include "io/vtx.h"
124 #include "msp/msp.h"
125 #include "msp/msp_box.h"
126 #include "msp/msp_protocol.h"
128 #include "osd/osd.h"
130 #include "pg/adc.h"
131 #include "pg/beeper.h"
132 #include "pg/beeper_dev.h"
133 #include "pg/board.h"
134 #include "pg/bus_i2c.h"
135 #include "pg/bus_spi.h"
136 #include "pg/gyrodev.h"
137 #include "pg/max7456.h"
138 #include "pg/mco.h"
139 #include "pg/motor.h"
140 #include "pg/pinio.h"
141 #include "pg/pin_pull_up_down.h"
142 #include "pg/pg.h"
143 #include "pg/pg_ids.h"
144 #include "pg/rx.h"
145 #include "pg/rx_pwm.h"
146 #include "pg/rx_spi_cc2500.h"
147 #include "pg/serial_uart.h"
148 #include "pg/sdio.h"
149 #include "pg/timerio.h"
150 #include "pg/timerup.h"
151 #include "pg/usb.h"
152 #include "pg/vtx_table.h"
154 #include "rx/rx_bind.h"
155 #include "rx/rx_spi.h"
157 #include "scheduler/scheduler.h"
159 #include "sensors/acceleration.h"
160 #include "sensors/adcinternal.h"
161 #include "sensors/barometer.h"
162 #include "sensors/battery.h"
163 #include "sensors/boardalignment.h"
164 #include "sensors/compass.h"
165 #include "sensors/esc_sensor.h"
166 #include "sensors/gyro.h"
167 #include "sensors/gyro_init.h"
168 #include "sensors/sensors.h"
170 #include "telemetry/frsky_hub.h"
171 #include "telemetry/telemetry.h"
173 #include "cli.h"
175 static serialPort_t *cliPort = NULL;
177 #ifdef STM32F1
178 #define CLI_IN_BUFFER_SIZE 128
179 #else
180 // Space required to set array parameters
181 #define CLI_IN_BUFFER_SIZE 256
182 #endif
183 #define CLI_OUT_BUFFER_SIZE 64
185 static bufWriter_t *cliWriter = NULL;
186 static bufWriter_t *cliErrorWriter = NULL;
187 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
189 static char cliBuffer[CLI_IN_BUFFER_SIZE];
190 static uint32_t bufferIndex = 0;
192 static bool configIsInCopy = false;
194 #define CURRENT_PROFILE_INDEX -1
195 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
196 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
198 #ifdef USE_CLI_BATCH
199 static bool commandBatchActive = false;
200 static bool commandBatchError = false;
201 #endif
203 #if defined(USE_BOARD_INFO)
204 static bool boardInformationUpdated = false;
205 #if defined(USE_SIGNATURE)
206 static bool signatureUpdated = false;
207 #endif
208 #endif // USE_BOARD_INFO
210 static const char* const emptyName = "-";
211 static const char* const emptyString = "";
213 #if !defined(USE_CUSTOM_DEFAULTS)
214 #define CUSTOM_DEFAULTS_START ((char*)0)
215 #define CUSTOM_DEFAULTS_END ((char *)0)
216 #else
217 extern char __custom_defaults_start;
218 extern char __custom_defaults_end;
219 #define CUSTOM_DEFAULTS_START (&__custom_defaults_start)
220 #define CUSTOM_DEFAULTS_END (&__custom_defaults_end)
222 static bool processingCustomDefaults = false;
223 static char cliBufferTemp[CLI_IN_BUFFER_SIZE];
224 #endif
226 #if defined(USE_CUSTOM_DEFAULTS_ADDRESS)
227 static char __attribute__ ((section(".custom_defaults_start_address"))) *customDefaultsStart = CUSTOM_DEFAULTS_START;
228 static char __attribute__ ((section(".custom_defaults_end_address"))) *customDefaultsEnd = CUSTOM_DEFAULTS_END;
229 #endif
231 #ifndef USE_QUAD_MIXER_ONLY
232 // sync this with mixerMode_e
233 static const char * const mixerNames[] = {
234 "TRI", "QUADP", "QUADX", "BI",
235 "GIMBAL", "Y6", "HEX6",
236 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
237 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
238 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
239 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
241 #endif
243 // sync this with features_e
244 static const char * const featureNames[] = {
245 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
246 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
247 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
248 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
249 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
250 "", "", "RX_SPI", "", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
253 // sync this with rxFailsafeChannelMode_e
254 static const char rxFailsafeModeCharacters[] = "ahs";
256 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
257 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
258 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
261 #if defined(USE_SENSOR_NAMES)
262 // sync this with sensors_e
263 static const char *const sensorTypeNames[] = {
264 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
267 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
269 static const char * const *sensorHardwareNames[] = {
270 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
272 #endif // USE_SENSOR_NAMES
274 // Needs to be aligned with mcuTypeId_e
275 static const char *mcuTypeNames[] = {
276 "SIMULATOR",
277 "F103",
278 "F303",
279 "F40X",
280 "F411",
281 "F446",
282 "F722",
283 "F745",
284 "F746",
285 "F765",
286 "H750",
287 "H743 (Rev Unknown)",
288 "H743 (Rev.Y)",
289 "H743 (Rev.X)",
290 "H743 (Rev.V)",
293 typedef enum dumpFlags_e {
294 DUMP_MASTER = (1 << 0),
295 DUMP_PROFILE = (1 << 1),
296 DUMP_RATES = (1 << 2),
297 DUMP_ALL = (1 << 3),
298 DO_DIFF = (1 << 4),
299 SHOW_DEFAULTS = (1 << 5),
300 HIDE_UNUSED = (1 << 6),
301 HARDWARE_ONLY = (1 << 7),
302 BARE = (1 << 8),
303 } dumpFlags_t;
305 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
307 typedef enum {
308 REBOOT_TARGET_FIRMWARE,
309 REBOOT_TARGET_BOOTLOADER_ROM,
310 REBOOT_TARGET_BOOTLOADER_FLASH,
311 } rebootTarget_e;
313 typedef struct serialPassthroughPort_s {
314 int id;
315 uint32_t baud;
316 unsigned mode;
317 serialPort_t *port;
318 } serialPassthroughPort_t;
320 static void cliWriterFlushInternal(bufWriter_t *writer)
322 if (writer) {
323 bufWriterFlush(writer);
327 void cliPrintInternal(bufWriter_t *writer, const char *str)
329 if (writer) {
330 while (*str) {
331 bufWriterAppend(writer, *str++);
333 cliWriterFlushInternal(writer);
337 static void cliWriterFlush()
339 cliWriterFlushInternal(cliWriter);
342 void cliPrint(const char *str)
344 cliPrintInternal(cliWriter, str);
347 void cliPrintLinefeed(void)
349 cliPrint("\r\n");
352 void cliPrintLine(const char *str)
354 cliPrint(str);
355 cliPrintLinefeed();
358 #ifdef MINIMAL_CLI
359 #define cliPrintHashLine(str)
360 #else
361 static void cliPrintHashLine(const char *str)
363 cliPrint("\r\n# ");
364 cliPrintLine(str);
366 #endif
368 static void cliPutp(void *p, char ch)
370 bufWriterAppend(p, ch);
373 static void cliPrintfva(const char *format, va_list va)
375 if (cliWriter) {
376 tfp_format(cliWriter, cliPutp, format, va);
377 cliWriterFlush();
381 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
383 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
384 va_list va;
385 va_start(va, format);
386 cliPrintfva(format, va);
387 va_end(va);
388 cliPrintLinefeed();
389 return true;
390 } else {
391 return false;
395 static void cliWrite(uint8_t ch)
397 if (cliWriter) {
398 bufWriterAppend(cliWriter, ch);
402 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
404 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
405 cliWrite('#');
407 va_list va;
408 va_start(va, format);
409 cliPrintfva(format, va);
410 va_end(va);
411 cliPrintLinefeed();
412 return true;
413 } else {
414 return false;
418 void cliPrintf(const char *format, ...)
420 va_list va;
421 va_start(va, format);
422 cliPrintfva(format, va);
423 va_end(va);
427 void cliPrintLinef(const char *format, ...)
429 va_list va;
430 va_start(va, format);
431 cliPrintfva(format, va);
432 va_end(va);
433 cliPrintLinefeed();
436 static void cliPrintErrorVa(const char *format, va_list va)
438 if (cliErrorWriter) {
439 cliPrintInternal(cliErrorWriter, "###ERROR: ");
441 tfp_format(cliErrorWriter, cliPutp, format, va);
442 va_end(va);
444 cliPrintInternal(cliErrorWriter, "###");
447 #ifdef USE_CLI_BATCH
448 if (commandBatchActive) {
449 commandBatchError = true;
451 #endif
454 static void cliPrintError(const char *format, ...)
456 va_list va;
457 va_start(va, format);
458 cliPrintErrorVa(format, va);
460 if (!cliWriter) {
461 // Supply our own linefeed in case we are printing inside a custom defaults operation
462 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
463 // instead of expecting the directly following command to supply the line feed.
464 cliPrintInternal(cliErrorWriter, "\r\n");
468 static void cliPrintErrorLinef(const char *format, ...)
470 va_list va;
471 va_start(va, format);
472 cliPrintErrorVa(format, va);
473 cliPrintInternal(cliErrorWriter, "\r\n");
476 static void getMinMax(const clivalue_t *var, int *min, int *max)
478 switch (var->type & VALUE_TYPE_MASK) {
479 case VAR_UINT8:
480 case VAR_UINT16:
481 *min = var->config.minmaxUnsigned.min;
482 *max = var->config.minmaxUnsigned.max;
484 break;
485 default:
486 *min = var->config.minmax.min;
487 *max = var->config.minmax.max;
489 break;
493 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
495 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
496 for (int i = 0; i < var->config.array.length; i++) {
497 switch (var->type & VALUE_TYPE_MASK) {
498 default:
499 case VAR_UINT8:
500 // uint8_t array
501 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
502 break;
504 case VAR_INT8:
505 // int8_t array
506 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
507 break;
509 case VAR_UINT16:
510 // uin16_t array
511 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
512 break;
514 case VAR_INT16:
515 // int16_t array
516 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
517 break;
519 case VAR_UINT32:
520 // uin32_t array
521 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
522 break;
525 if (i < var->config.array.length - 1) {
526 cliPrint(",");
529 } else {
530 int value = 0;
532 switch (var->type & VALUE_TYPE_MASK) {
533 case VAR_UINT8:
534 value = *(uint8_t *)valuePointer;
536 break;
537 case VAR_INT8:
538 value = *(int8_t *)valuePointer;
540 break;
541 case VAR_UINT16:
542 value = *(uint16_t *)valuePointer;
544 break;
545 case VAR_INT16:
546 value = *(int16_t *)valuePointer;
548 break;
549 case VAR_UINT32:
550 value = *(uint32_t *)valuePointer;
552 break;
555 bool valueIsCorrupted = false;
556 switch (var->type & VALUE_MODE_MASK) {
557 case MODE_DIRECT:
558 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
559 cliPrintf("%u", (uint32_t)value);
560 if ((uint32_t)value > var->config.u32Max) {
561 valueIsCorrupted = true;
562 } else if (full) {
563 cliPrintf(" 0 %u", var->config.u32Max);
565 } else {
566 int min;
567 int max;
568 getMinMax(var, &min, &max);
570 cliPrintf("%d", value);
571 if ((value < min) || (value > max)) {
572 valueIsCorrupted = true;
573 } else if (full) {
574 cliPrintf(" %d %d", min, max);
577 break;
578 case MODE_LOOKUP:
579 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
580 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
581 } else {
582 valueIsCorrupted = true;
584 break;
585 case MODE_BITSET:
586 if (value & 1 << var->config.bitpos) {
587 cliPrintf("ON");
588 } else {
589 cliPrintf("OFF");
591 break;
592 case MODE_STRING:
593 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
594 break;
597 if (valueIsCorrupted) {
598 cliPrintLinefeed();
599 cliPrintError("CORRUPTED CONFIG: %s = %d", var->name, value);
605 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
607 bool result = true;
608 int elementCount = 1;
609 uint32_t mask = 0xffffffff;
611 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
612 elementCount = var->config.array.length;
614 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
615 mask = 1 << var->config.bitpos;
617 for (int i = 0; i < elementCount; i++) {
618 switch (var->type & VALUE_TYPE_MASK) {
619 case VAR_UINT8:
620 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
621 break;
623 case VAR_INT8:
624 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
625 break;
627 case VAR_UINT16:
628 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
629 break;
630 case VAR_INT16:
631 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
632 break;
633 case VAR_UINT32:
634 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
635 break;
639 return result;
642 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
644 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
645 cliPrintHashLine(headingStr);
646 return NULL;
647 } else {
648 return headingStr;
652 static void backupPgConfig(const pgRegistry_t *pg)
654 memcpy(pg->copy, pg->address, pg->size);
657 static void restorePgConfig(const pgRegistry_t *pg)
659 memcpy(pg->address, pg->copy, pg->size);
662 static void backupConfigs(void)
664 if (configIsInCopy) {
665 return;
668 // make copies of configs to do differencing
669 PG_FOREACH(pg) {
670 backupPgConfig(pg);
673 configIsInCopy = true;
676 static void restoreConfigs(void)
678 if (!configIsInCopy) {
679 return;
682 PG_FOREACH(pg) {
683 restorePgConfig(pg);
686 configIsInCopy = false;
689 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
690 static bool isReadingConfigFromCopy()
692 return configIsInCopy;
694 #endif
696 static bool isWritingConfigToCopy()
698 return configIsInCopy
699 #if defined(USE_CUSTOM_DEFAULTS)
700 && !processingCustomDefaults
701 #endif
705 #if defined(USE_CUSTOM_DEFAULTS)
706 static bool cliProcessCustomDefaults(bool quiet);
707 #endif
709 static void backupAndResetConfigs(const bool useCustomDefaults)
711 backupConfigs();
713 // reset all configs to defaults to do differencing
714 resetConfig();
716 #if defined(USE_CUSTOM_DEFAULTS)
717 if (useCustomDefaults) {
718 if (!cliProcessCustomDefaults(true)) {
719 cliPrintLine("###WARNING: NO CUSTOM DEFAULTS FOUND###");
722 #else
723 UNUSED(useCustomDefaults);
724 #endif
727 static uint8_t getPidProfileIndexToUse()
729 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
732 static uint8_t getRateProfileIndexToUse()
734 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
738 static uint16_t getValueOffset(const clivalue_t *value)
740 switch (value->type & VALUE_SECTION_MASK) {
741 case MASTER_VALUE:
742 case HARDWARE_VALUE:
743 return value->offset;
744 case PROFILE_VALUE:
745 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
746 case PROFILE_RATE_VALUE:
747 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
749 return 0;
752 STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
754 const pgRegistry_t* rec = pgFind(value->pgn);
755 if (isWritingConfigToCopy()) {
756 return CONST_CAST(void *, rec->copy + getValueOffset(value));
757 } else {
758 return CONST_CAST(void *, rec->address + getValueOffset(value));
762 static const char *dumpPgValue(const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
764 const pgRegistry_t *pg = pgFind(value->pgn);
765 #ifdef DEBUG
766 if (!pg) {
767 cliPrintLinef("VALUE %s ERROR", value->name);
768 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
770 #endif
772 const char *format = "set %s = ";
773 const char *defaultFormat = "#set %s = ";
774 const int valueOffset = getValueOffset(value);
775 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
777 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
778 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
779 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
780 cliPrintf(defaultFormat, value->name);
781 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
782 cliPrintLinefeed();
784 cliPrintf(format, value->name);
785 printValuePointer(value, pg->copy + valueOffset, false);
786 cliPrintLinefeed();
788 return headingStr;
791 static void dumpAllValues(uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
793 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
795 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
796 const clivalue_t *value = &valueTable[i];
797 cliWriterFlush();
798 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
799 headingStr = dumpPgValue(value, dumpMask, headingStr);
804 static void cliPrintVar(const clivalue_t *var, bool full)
806 const void *ptr = cliGetValuePointer(var);
808 printValuePointer(var, ptr, full);
811 static void cliPrintVarRange(const clivalue_t *var)
813 switch (var->type & VALUE_MODE_MASK) {
814 case (MODE_DIRECT): {
815 switch (var->type & VALUE_TYPE_MASK) {
816 case VAR_UINT32:
817 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
819 break;
820 case VAR_UINT8:
821 case VAR_UINT16:
822 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
824 break;
825 default:
826 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
828 break;
831 break;
832 case (MODE_LOOKUP): {
833 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
834 cliPrint("Allowed values: ");
835 bool firstEntry = true;
836 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
837 if (tableEntry->values[i]) {
838 if (!firstEntry) {
839 cliPrint(", ");
841 cliPrintf("%s", tableEntry->values[i]);
842 firstEntry = false;
845 cliPrintLinefeed();
847 break;
848 case (MODE_ARRAY): {
849 cliPrintLinef("Array length: %d", var->config.array.length);
851 break;
852 case (MODE_STRING): {
853 cliPrintLinef("String length: %d - %d", var->config.string.minlength, var->config.string.maxlength);
855 break;
856 case (MODE_BITSET): {
857 cliPrintLinef("Allowed values: OFF, ON");
859 break;
863 static void cliSetVar(const clivalue_t *var, const uint32_t value)
865 void *ptr = cliGetValuePointer(var);
866 uint32_t workValue;
867 uint32_t mask;
869 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
870 switch (var->type & VALUE_TYPE_MASK) {
871 case VAR_UINT8:
872 mask = (1 << var->config.bitpos) & 0xff;
873 if (value) {
874 workValue = *(uint8_t *)ptr | mask;
875 } else {
876 workValue = *(uint8_t *)ptr & ~mask;
878 *(uint8_t *)ptr = workValue;
879 break;
881 case VAR_UINT16:
882 mask = (1 << var->config.bitpos) & 0xffff;
883 if (value) {
884 workValue = *(uint16_t *)ptr | mask;
885 } else {
886 workValue = *(uint16_t *)ptr & ~mask;
888 *(uint16_t *)ptr = workValue;
889 break;
891 case VAR_UINT32:
892 mask = 1 << var->config.bitpos;
893 if (value) {
894 workValue = *(uint32_t *)ptr | mask;
895 } else {
896 workValue = *(uint32_t *)ptr & ~mask;
898 *(uint32_t *)ptr = workValue;
899 break;
901 } else {
902 switch (var->type & VALUE_TYPE_MASK) {
903 case VAR_UINT8:
904 *(uint8_t *)ptr = value;
905 break;
907 case VAR_INT8:
908 *(int8_t *)ptr = value;
909 break;
911 case VAR_UINT16:
912 *(uint16_t *)ptr = value;
913 break;
915 case VAR_INT16:
916 *(int16_t *)ptr = value;
917 break;
919 case VAR_UINT32:
920 *(uint32_t *)ptr = value;
921 break;
926 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
927 static void cliRepeat(char ch, uint8_t len)
929 if (cliWriter) {
930 for (int i = 0; i < len; i++) {
931 bufWriterAppend(cliWriter, ch);
933 cliPrintLinefeed();
936 #endif
938 static void cliPrompt(void)
940 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
941 if (processingCustomDefaults) {
942 cliPrint("\r\nd: #");
943 } else
944 #endif
946 cliPrint("\r\n# ");
950 static void cliShowParseError(void)
952 cliPrintErrorLinef("PARSE ERROR");
955 static void cliShowArgumentRangeError(char *name, int min, int max)
957 cliPrintErrorLinef("%s NOT BETWEEN %d AND %d", name, min, max);
960 static const char *nextArg(const char *currentArg)
962 const char *ptr = strchr(currentArg, ' ');
963 while (ptr && *ptr == ' ') {
964 ptr++;
967 return ptr;
970 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
972 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
973 ptr = nextArg(ptr);
974 if (ptr) {
975 int val = atoi(ptr);
976 val = CHANNEL_VALUE_TO_STEP(val);
977 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
978 if (argIndex == 0) {
979 range->startStep = val;
980 } else {
981 range->endStep = val;
983 (*validArgumentCount)++;
988 return ptr;
991 // Check if a string's length is zero
992 static bool isEmpty(const char *string)
994 return (string == NULL || *string == '\0') ? true : false;
997 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
999 // print out rxConfig failsafe settings
1000 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1001 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1002 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
1003 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
1004 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
1005 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1006 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1007 if (requireValue) {
1008 const char *format = "rxfail %u %c %d";
1009 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1010 channel,
1011 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
1012 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
1014 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1015 channel,
1016 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
1017 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1019 } else {
1020 const char *format = "rxfail %u %c";
1021 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1022 channel,
1023 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
1025 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1026 channel,
1027 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
1033 static void cliRxFailsafe(char *cmdline)
1035 uint8_t channel;
1036 char buf[3];
1038 if (isEmpty(cmdline)) {
1039 // print out rxConfig failsafe settings
1040 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1041 cliRxFailsafe(itoa(channel, buf, 10));
1043 } else {
1044 const char *ptr = cmdline;
1045 channel = atoi(ptr++);
1046 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
1048 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
1050 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
1051 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
1052 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1054 ptr = nextArg(ptr);
1055 if (ptr) {
1056 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
1057 if (p) {
1058 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
1059 mode = rxFailsafeModesTable[type][requestedMode];
1060 } else {
1061 mode = RX_FAILSAFE_MODE_INVALID;
1063 if (mode == RX_FAILSAFE_MODE_INVALID) {
1064 cliShowParseError();
1065 return;
1068 requireValue = mode == RX_FAILSAFE_MODE_SET;
1070 ptr = nextArg(ptr);
1071 if (ptr) {
1072 if (!requireValue) {
1073 cliShowParseError();
1074 return;
1076 uint16_t value = atoi(ptr);
1077 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
1078 if (value > MAX_RXFAIL_RANGE_STEP) {
1079 cliPrintLine("Value out of range");
1080 return;
1083 channelFailsafeConfig->step = value;
1084 } else if (requireValue) {
1085 cliShowParseError();
1086 return;
1088 channelFailsafeConfig->mode = mode;
1091 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
1093 // double use of cliPrintf below
1094 // 1. acknowledge interpretation on command,
1095 // 2. query current setting on single item,
1097 if (requireValue) {
1098 cliPrintLinef("rxfail %u %c %d",
1099 channel,
1100 modeCharacter,
1101 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1103 } else {
1104 cliPrintLinef("rxfail %u %c",
1105 channel,
1106 modeCharacter
1109 } else {
1110 cliShowArgumentRangeError("CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1115 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1117 const char *format = "aux %u %u %u %u %u %u %u";
1118 // print out aux channel settings
1119 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1120 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1121 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1122 bool equalsDefault = false;
1123 if (defaultModeActivationConditions) {
1124 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1125 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1126 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1127 const box_t *box = findBoxByBoxId(macDefault->modeId);
1128 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1129 if (box) {
1130 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1132 box->permanentId,
1133 macDefault->auxChannelIndex,
1134 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1135 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1136 macDefault->modeLogic,
1137 linkedTo ? linkedTo->permanentId : 0
1141 const box_t *box = findBoxByBoxId(mac->modeId);
1142 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1143 if (box) {
1144 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1146 box->permanentId,
1147 mac->auxChannelIndex,
1148 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1149 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1150 mac->modeLogic,
1151 linkedTo ? linkedTo->permanentId : 0
1157 static void cliAux(char *cmdline)
1159 int i, val = 0;
1160 const char *ptr;
1162 if (isEmpty(cmdline)) {
1163 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1164 } else {
1165 ptr = cmdline;
1166 i = atoi(ptr++);
1167 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1168 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1169 uint8_t validArgumentCount = 0;
1170 ptr = nextArg(ptr);
1171 if (ptr) {
1172 val = atoi(ptr);
1173 const box_t *box = findBoxByPermanentId(val);
1174 if (box) {
1175 mac->modeId = box->boxId;
1176 validArgumentCount++;
1179 ptr = nextArg(ptr);
1180 if (ptr) {
1181 val = atoi(ptr);
1182 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1183 mac->auxChannelIndex = val;
1184 validArgumentCount++;
1187 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1188 ptr = nextArg(ptr);
1189 if (ptr) {
1190 val = atoi(ptr);
1191 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1192 mac->modeLogic = val;
1193 validArgumentCount++;
1196 ptr = nextArg(ptr);
1197 if (ptr) {
1198 val = atoi(ptr);
1199 const box_t *box = findBoxByPermanentId(val);
1200 if (box) {
1201 mac->linkedTo = box->boxId;
1202 validArgumentCount++;
1205 if (validArgumentCount == 4) { // for backwards compatibility
1206 mac->modeLogic = MODELOGIC_OR;
1207 mac->linkedTo = 0;
1208 } else if (validArgumentCount == 5) { // for backwards compatibility
1209 mac->linkedTo = 0;
1210 } else if (validArgumentCount != 6) {
1211 memset(mac, 0, sizeof(modeActivationCondition_t));
1213 analyzeModeActivationConditions();
1214 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1216 findBoxByBoxId(mac->modeId)->permanentId,
1217 mac->auxChannelIndex,
1218 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1219 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1220 mac->modeLogic,
1221 findBoxByBoxId(mac->linkedTo)->permanentId
1223 } else {
1224 cliShowArgumentRangeError("INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1229 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1231 const char *format = "serial %d %d %ld %ld %ld %ld";
1232 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1233 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
1234 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1235 continue;
1237 bool equalsDefault = false;
1238 if (serialConfigDefault) {
1239 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1240 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1241 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1242 serialConfigDefault->portConfigs[i].identifier,
1243 serialConfigDefault->portConfigs[i].functionMask,
1244 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1245 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1246 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1247 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1250 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1251 serialConfig->portConfigs[i].identifier,
1252 serialConfig->portConfigs[i].functionMask,
1253 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1254 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1255 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1256 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1261 static void cliSerial(char *cmdline)
1263 const char *format = "serial %d %d %ld %ld %ld %ld";
1264 if (isEmpty(cmdline)) {
1265 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1266 return;
1268 serialPortConfig_t portConfig;
1269 memset(&portConfig, 0 , sizeof(portConfig));
1272 uint8_t validArgumentCount = 0;
1274 const char *ptr = cmdline;
1276 int val = atoi(ptr++);
1277 serialPortConfig_t *currentConfig = serialFindPortConfigurationMutable(val);
1279 if (currentConfig) {
1280 portConfig.identifier = val;
1281 validArgumentCount++;
1284 ptr = nextArg(ptr);
1285 if (ptr) {
1286 val = strtoul(ptr, NULL, 10);
1287 portConfig.functionMask = val;
1288 validArgumentCount++;
1291 for (int i = 0; i < 4; i ++) {
1292 ptr = nextArg(ptr);
1293 if (!ptr) {
1294 break;
1297 val = atoi(ptr);
1299 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1300 if (baudRates[baudRateIndex] != (uint32_t) val) {
1301 break;
1304 switch (i) {
1305 case 0:
1306 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1307 continue;
1309 portConfig.msp_baudrateIndex = baudRateIndex;
1310 break;
1311 case 1:
1312 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1313 continue;
1315 portConfig.gps_baudrateIndex = baudRateIndex;
1316 break;
1317 case 2:
1318 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1319 continue;
1321 portConfig.telemetry_baudrateIndex = baudRateIndex;
1322 break;
1323 case 3:
1324 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1325 continue;
1327 portConfig.blackbox_baudrateIndex = baudRateIndex;
1328 break;
1331 validArgumentCount++;
1334 if (validArgumentCount < 6) {
1335 cliShowParseError();
1336 return;
1339 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1341 cliDumpPrintLinef(0, false, format,
1342 portConfig.identifier,
1343 portConfig.functionMask,
1344 baudRates[portConfig.msp_baudrateIndex],
1345 baudRates[portConfig.gps_baudrateIndex],
1346 baudRates[portConfig.telemetry_baudrateIndex],
1347 baudRates[portConfig.blackbox_baudrateIndex]
1352 #if defined(USE_SERIAL_PASSTHROUGH)
1353 static void cbCtrlLine(void *context, uint16_t ctrl)
1355 #ifdef USE_PINIO
1356 int contextValue = (int)(long)context;
1357 if (contextValue) {
1358 pinioSet(contextValue - 1, !(ctrl & CTRL_LINE_STATE_DTR));
1359 } else
1360 #endif /* USE_PINIO */
1361 UNUSED(context);
1363 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1364 systemReset();
1368 static int cliParseSerialMode(const char *tok)
1370 int mode = 0;
1372 if (strcasestr(tok, "rx")) {
1373 mode |= MODE_RX;
1375 if (strcasestr(tok, "tx")) {
1376 mode |= MODE_TX;
1379 return mode;
1382 static void cliSerialPassthrough(char *cmdline)
1384 if (isEmpty(cmdline)) {
1385 cliShowParseError();
1386 return;
1389 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, NULL}, {cliPort->identifier, 0, 0, cliPort} };
1390 bool enableBaudCb = false;
1391 int port1PinioDtr = 0;
1392 bool port1ResetOnDtr = false;
1393 bool escSensorPassthrough = false;
1394 char *saveptr;
1395 char* tok = strtok_r(cmdline, " ", &saveptr);
1396 int index = 0;
1398 while (tok != NULL) {
1399 switch (index) {
1400 case 0:
1401 if (strcasestr(tok, "esc_sensor")) {
1402 escSensorPassthrough = true;
1403 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1404 ports[0].id = portConfig->identifier;
1405 } else {
1406 ports[0].id = atoi(tok);
1408 break;
1409 case 1:
1410 ports[0].baud = atoi(tok);
1411 break;
1412 case 2:
1413 ports[0].mode = cliParseSerialMode(tok);
1414 break;
1415 case 3:
1416 if (strncasecmp(tok, "reset", strlen(tok)) == 0) {
1417 port1ResetOnDtr = true;
1418 #ifdef USE_PINIO
1419 } else if (strncasecmp(tok, "none", strlen(tok)) == 0) {
1420 port1PinioDtr = 0;
1421 } else {
1422 port1PinioDtr = atoi(tok);
1423 if (port1PinioDtr < 0 || port1PinioDtr > PINIO_COUNT) {
1424 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1425 return ;
1427 #endif /* USE_PINIO */
1429 break;
1430 case 4:
1431 ports[1].id = atoi(tok);
1432 ports[1].port = NULL;
1433 break;
1434 case 5:
1435 ports[1].baud = atoi(tok);
1436 break;
1437 case 6:
1438 ports[1].mode = cliParseSerialMode(tok);
1439 break;
1441 index++;
1442 tok = strtok_r(NULL, " ", &saveptr);
1445 // Port checks
1446 if (ports[0].id == ports[1].id) {
1447 cliPrintLinef("Port1 and port2 are same");
1448 return ;
1451 for (int i = 0; i < 2; i++) {
1452 if (findSerialPortIndexByIdentifier(ports[i].id) == -1) {
1453 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1454 return ;
1455 } else {
1456 cliPrintLinef("Port%d: %d ", i + 1, ports[i].id);
1460 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1461 enableBaudCb = true;
1464 for (int i = 0; i < 2; i++) {
1465 serialPort_t **port = &(ports[i].port);
1466 if (*port != NULL) {
1467 continue;
1470 int portIndex = i + 1;
1471 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(ports[i].id);
1472 if (!portUsage || portUsage->serialPort == NULL) {
1473 bool isUseDefaultBaud = false;
1474 if (ports[i].baud == 0) {
1475 // Set default baud
1476 ports[i].baud = 57600;
1477 isUseDefaultBaud = true;
1480 if (!ports[i].mode) {
1481 ports[i].mode = MODE_RXTX;
1484 *port = openSerialPort(ports[i].id, FUNCTION_NONE, NULL, NULL,
1485 ports[i].baud, ports[i].mode,
1486 SERIAL_NOT_INVERTED);
1487 if (!*port) {
1488 cliPrintLinef("Port%d could not be opened.", portIndex);
1489 return;
1492 if (isUseDefaultBaud) {
1493 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex, ports[i].baud);
1494 } else {
1495 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex, ports[i].baud);
1497 } else {
1498 *port = portUsage->serialPort;
1499 // If the user supplied a mode, override the port's mode, otherwise
1500 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1501 // Set the baud rate if specified
1502 if (ports[i].baud) {
1503 cliPrintf("Port%d is already open, setting baud = %d.\n\r", portIndex, ports[i].baud);
1504 serialSetBaudRate(*port, ports[i].baud);
1505 } else {
1506 cliPrintf("Port%d is already open, baud = %d.\n\r", portIndex, (*port)->baudRate);
1509 if (ports[i].mode && (*port)->mode != ports[i].mode) {
1510 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1511 portIndex, (*port)->mode, ports[i].mode);
1512 serialSetMode(*port, ports[i].mode);
1515 // If this port has a rx callback associated we need to remove it now.
1516 // Otherwise no data will be pushed in the serial port buffer!
1517 if ((*port)->rxCallback) {
1518 (*port)->rxCallback = NULL;
1523 // If no baud rate is specified allow to be set via USB
1524 if (enableBaudCb) {
1525 cliPrintLine("Port1 baud rate change over USB enabled.");
1526 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1527 // baud rate over USB without setting it using the serialpassthrough command
1528 serialSetBaudRateCb(ports[0].port, serialSetBaudRate, ports[1].port);
1531 char *resetMessage = "";
1532 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1533 resetMessage = "or drop DTR ";
1536 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1538 if ((ports[1].id == SERIAL_PORT_USB_VCP) && (port1ResetOnDtr
1539 #ifdef USE_PINIO
1540 || port1PinioDtr
1541 #endif /* USE_PINIO */
1542 )) {
1543 // Register control line state callback
1544 serialSetCtrlLineStateCb(ports[0].port, cbCtrlLine, (void *)(intptr_t)(port1PinioDtr));
1547 // XXX Review ESC pass through under refactored motor handling
1548 #ifdef USE_PWM_OUTPUT
1549 if (escSensorPassthrough) {
1550 // pwmDisableMotors();
1551 motorDisable();
1552 delay(5);
1553 unsigned motorsCount = getMotorCount();
1554 for (unsigned i = 0; i < motorsCount; i++) {
1555 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1556 if (tag) {
1557 const timerHardware_t *timerHardware = timerGetByTag(tag);
1558 if (timerHardware) {
1559 IO_t io = IOGetByTag(tag);
1560 IOInit(io, OWNER_MOTOR, 0);
1561 IOConfigGPIO(io, IOCFG_OUT_PP);
1562 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1563 IOLo(io);
1564 } else {
1565 IOHi(io);
1571 #endif
1573 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1575 #endif
1577 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1579 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1580 // print out adjustment ranges channel settings
1581 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1582 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1583 const adjustmentRange_t *ar = &adjustmentRanges[i];
1584 bool equalsDefault = false;
1585 if (defaultAdjustmentRanges) {
1586 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1587 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1588 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1589 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1591 arDefault->auxChannelIndex,
1592 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1593 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1594 arDefault->adjustmentConfig,
1595 arDefault->auxSwitchChannelIndex,
1596 arDefault->adjustmentCenter,
1597 arDefault->adjustmentScale
1600 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1602 ar->auxChannelIndex,
1603 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1604 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1605 ar->adjustmentConfig,
1606 ar->auxSwitchChannelIndex,
1607 ar->adjustmentCenter,
1608 ar->adjustmentScale
1613 static void cliAdjustmentRange(char *cmdline)
1615 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1616 int i, val = 0;
1617 const char *ptr;
1619 if (isEmpty(cmdline)) {
1620 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1621 } else {
1622 ptr = cmdline;
1623 i = atoi(ptr++);
1624 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1625 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1626 uint8_t validArgumentCount = 0;
1628 ptr = nextArg(ptr);
1629 if (ptr) {
1630 val = atoi(ptr);
1631 // Was: slot
1632 // Keeping the parameter to retain backwards compatibility for the command format.
1633 validArgumentCount++;
1635 ptr = nextArg(ptr);
1636 if (ptr) {
1637 val = atoi(ptr);
1638 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1639 ar->auxChannelIndex = val;
1640 validArgumentCount++;
1644 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1646 ptr = nextArg(ptr);
1647 if (ptr) {
1648 val = atoi(ptr);
1649 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1650 ar->adjustmentConfig = val;
1651 validArgumentCount++;
1654 ptr = nextArg(ptr);
1655 if (ptr) {
1656 val = atoi(ptr);
1657 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1658 ar->auxSwitchChannelIndex = val;
1659 validArgumentCount++;
1663 if (validArgumentCount != 6) {
1664 memset(ar, 0, sizeof(adjustmentRange_t));
1665 cliShowParseError();
1666 return;
1669 // Optional arguments
1670 ar->adjustmentCenter = 0;
1671 ar->adjustmentScale = 0;
1673 ptr = nextArg(ptr);
1674 if (ptr) {
1675 val = atoi(ptr);
1676 ar->adjustmentCenter = val;
1677 validArgumentCount++;
1679 ptr = nextArg(ptr);
1680 if (ptr) {
1681 val = atoi(ptr);
1682 ar->adjustmentScale = val;
1683 validArgumentCount++;
1686 activeAdjustmentRangeReset();
1688 cliDumpPrintLinef(0, false, format,
1690 ar->auxChannelIndex,
1691 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1692 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1693 ar->adjustmentConfig,
1694 ar->auxSwitchChannelIndex,
1695 ar->adjustmentCenter,
1696 ar->adjustmentScale
1699 } else {
1700 cliShowArgumentRangeError("INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1705 #ifndef USE_QUAD_MIXER_ONLY
1706 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1708 const char *format = "mmix %d %s %s %s %s";
1709 char buf0[FTOA_BUFFER_LENGTH];
1710 char buf1[FTOA_BUFFER_LENGTH];
1711 char buf2[FTOA_BUFFER_LENGTH];
1712 char buf3[FTOA_BUFFER_LENGTH];
1713 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1714 if (customMotorMixer[i].throttle == 0.0f)
1715 break;
1716 const float thr = customMotorMixer[i].throttle;
1717 const float roll = customMotorMixer[i].roll;
1718 const float pitch = customMotorMixer[i].pitch;
1719 const float yaw = customMotorMixer[i].yaw;
1720 bool equalsDefault = false;
1721 if (defaultCustomMotorMixer) {
1722 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1723 const float rollDefault = defaultCustomMotorMixer[i].roll;
1724 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1725 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1726 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1728 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1729 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1731 ftoa(thrDefault, buf0),
1732 ftoa(rollDefault, buf1),
1733 ftoa(pitchDefault, buf2),
1734 ftoa(yawDefault, buf3));
1736 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1738 ftoa(thr, buf0),
1739 ftoa(roll, buf1),
1740 ftoa(pitch, buf2),
1741 ftoa(yaw, buf3));
1744 #endif // USE_QUAD_MIXER_ONLY
1746 static void cliMotorMix(char *cmdline)
1748 #ifdef USE_QUAD_MIXER_ONLY
1749 UNUSED(cmdline);
1750 #else
1751 int check = 0;
1752 uint8_t len;
1753 const char *ptr;
1755 if (isEmpty(cmdline)) {
1756 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1757 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1758 // erase custom mixer
1759 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1760 customMotorMixerMutable(i)->throttle = 0.0f;
1762 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1763 ptr = nextArg(cmdline);
1764 if (ptr) {
1765 len = strlen(ptr);
1766 for (uint32_t i = 0; ; i++) {
1767 if (mixerNames[i] == NULL) {
1768 cliPrintErrorLinef("INVALID NAME");
1769 break;
1771 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1772 mixerLoadMix(i, customMotorMixerMutable(0));
1773 cliPrintLinef("Loaded %s", mixerNames[i]);
1774 cliMotorMix("");
1775 break;
1779 } else {
1780 ptr = cmdline;
1781 uint32_t i = atoi(ptr); // get motor number
1782 if (i < MAX_SUPPORTED_MOTORS) {
1783 ptr = nextArg(ptr);
1784 if (ptr) {
1785 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1786 check++;
1788 ptr = nextArg(ptr);
1789 if (ptr) {
1790 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1791 check++;
1793 ptr = nextArg(ptr);
1794 if (ptr) {
1795 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1796 check++;
1798 ptr = nextArg(ptr);
1799 if (ptr) {
1800 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1801 check++;
1803 if (check != 4) {
1804 cliShowParseError();
1805 } else {
1806 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1808 } else {
1809 cliShowArgumentRangeError("INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1812 #endif
1815 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1817 const char *format = "rxrange %u %u %u";
1818 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1819 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1820 bool equalsDefault = false;
1821 if (defaultChannelRangeConfigs) {
1822 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1823 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1824 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1826 defaultChannelRangeConfigs[i].min,
1827 defaultChannelRangeConfigs[i].max
1830 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1832 channelRangeConfigs[i].min,
1833 channelRangeConfigs[i].max
1838 static void cliRxRange(char *cmdline)
1840 const char *format = "rxrange %u %u %u";
1841 int i, validArgumentCount = 0;
1842 const char *ptr;
1844 if (isEmpty(cmdline)) {
1845 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1846 } else if (strcasecmp(cmdline, "reset") == 0) {
1847 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1848 } else {
1849 ptr = cmdline;
1850 i = atoi(ptr);
1851 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1852 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1854 ptr = nextArg(ptr);
1855 if (ptr) {
1856 rangeMin = atoi(ptr);
1857 validArgumentCount++;
1860 ptr = nextArg(ptr);
1861 if (ptr) {
1862 rangeMax = atoi(ptr);
1863 validArgumentCount++;
1866 if (validArgumentCount != 2) {
1867 cliShowParseError();
1868 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1869 cliShowParseError();
1870 } else {
1871 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1872 channelRangeConfig->min = rangeMin;
1873 channelRangeConfig->max = rangeMax;
1874 cliDumpPrintLinef(0, false, format,
1876 channelRangeConfig->min,
1877 channelRangeConfig->max
1881 } else {
1882 cliShowArgumentRangeError("CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1887 #ifdef USE_LED_STRIP_STATUS_MODE
1888 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1890 const char *format = "led %u %s";
1891 char ledConfigBuffer[20];
1892 char ledConfigDefaultBuffer[20];
1893 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1894 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1895 ledConfig_t ledConfig = ledConfigs[i];
1896 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1897 bool equalsDefault = false;
1898 if (defaultLedConfigs) {
1899 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1900 equalsDefault = ledConfig == ledConfigDefault;
1901 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1902 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1903 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1905 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1909 static void cliLed(char *cmdline)
1911 const char *format = "led %u %s";
1912 char ledConfigBuffer[20];
1913 int i;
1914 const char *ptr;
1916 if (isEmpty(cmdline)) {
1917 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
1918 } else {
1919 ptr = cmdline;
1920 i = atoi(ptr);
1921 if (i >= 0 && i < LED_MAX_STRIP_LENGTH) {
1922 ptr = nextArg(cmdline);
1923 if (parseLedStripConfig(i, ptr)) {
1924 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1925 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1926 } else {
1927 cliShowParseError();
1929 } else {
1930 cliShowArgumentRangeError("INDEX", 0, LED_MAX_STRIP_LENGTH - 1);
1935 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
1937 const char *format = "color %u %d,%u,%u";
1938 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1939 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1940 const hsvColor_t *color = &colors[i];
1941 bool equalsDefault = false;
1942 if (defaultColors) {
1943 const hsvColor_t *colorDefault = &defaultColors[i];
1944 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1945 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1946 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1948 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1952 static void cliColor(char *cmdline)
1954 const char *format = "color %u %d,%u,%u";
1955 if (isEmpty(cmdline)) {
1956 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
1957 } else {
1958 const char *ptr = cmdline;
1959 const int i = atoi(ptr);
1960 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1961 ptr = nextArg(cmdline);
1962 if (parseColor(i, ptr)) {
1963 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
1964 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1965 } else {
1966 cliShowParseError();
1968 } else {
1969 cliShowArgumentRangeError("INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1974 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
1976 const char *format = "mode_color %u %u %u";
1977 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1978 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1979 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1980 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
1981 bool equalsDefault = false;
1982 if (defaultLedStripConfig) {
1983 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1984 equalsDefault = colorIndex == colorIndexDefault;
1985 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1986 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1988 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1992 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1993 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
1994 bool equalsDefault = false;
1995 if (defaultLedStripConfig) {
1996 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1997 equalsDefault = colorIndex == colorIndexDefault;
1998 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1999 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
2001 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
2004 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
2005 bool equalsDefault = false;
2006 if (defaultLedStripConfig) {
2007 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
2008 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
2009 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2010 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
2012 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
2015 static void cliModeColor(char *cmdline)
2017 if (isEmpty(cmdline)) {
2018 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
2019 } else {
2020 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
2021 int args[ARGS_COUNT];
2022 int argNo = 0;
2023 char *saveptr;
2024 const char* ptr = strtok_r(cmdline, " ", &saveptr);
2025 while (ptr && argNo < ARGS_COUNT) {
2026 args[argNo++] = atoi(ptr);
2027 ptr = strtok_r(NULL, " ", &saveptr);
2030 if (ptr != NULL || argNo != ARGS_COUNT) {
2031 cliShowParseError();
2032 return;
2035 int modeIdx = args[MODE];
2036 int funIdx = args[FUNCTION];
2037 int color = args[COLOR];
2038 if (!setModeColor(modeIdx, funIdx, color)) {
2039 cliShowParseError();
2040 return;
2042 // values are validated
2043 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
2046 #endif
2048 #ifdef USE_SERVOS
2049 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
2051 // print out servo settings
2052 const char *format = "servo %u %d %d %d %d %d";
2053 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2054 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2055 const servoParam_t *servoConf = &servoParams[i];
2056 bool equalsDefault = false;
2057 if (defaultServoParams) {
2058 const servoParam_t *defaultServoConf = &defaultServoParams[i];
2059 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
2060 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2061 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2063 defaultServoConf->min,
2064 defaultServoConf->max,
2065 defaultServoConf->middle,
2066 defaultServoConf->rate,
2067 defaultServoConf->forwardFromChannel
2070 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2072 servoConf->min,
2073 servoConf->max,
2074 servoConf->middle,
2075 servoConf->rate,
2076 servoConf->forwardFromChannel
2079 // print servo directions
2080 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2081 const char *format = "smix reverse %d %d r";
2082 const servoParam_t *servoConf = &servoParams[i];
2083 const servoParam_t *servoConfDefault = &defaultServoParams[i];
2084 if (defaultServoParams) {
2085 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
2086 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2087 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
2088 if (servoConfDefault->reversedSources & (1 << channel)) {
2089 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
2091 if (servoConf->reversedSources & (1 << channel)) {
2092 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
2095 } else {
2096 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2097 if (servoConf->reversedSources & (1 << channel)) {
2098 cliDumpPrintLinef(dumpMask, true, format, i , channel);
2105 static void cliServo(char *cmdline)
2107 const char *format = "servo %u %d %d %d %d %d";
2108 enum { SERVO_ARGUMENT_COUNT = 6 };
2109 int16_t arguments[SERVO_ARGUMENT_COUNT];
2111 servoParam_t *servo;
2113 int i;
2114 char *ptr;
2116 if (isEmpty(cmdline)) {
2117 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2118 } else {
2119 int validArgumentCount = 0;
2121 ptr = cmdline;
2123 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2125 // If command line doesn't fit the format, don't modify the config
2126 while (*ptr) {
2127 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2128 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2129 cliShowParseError();
2130 return;
2133 arguments[validArgumentCount++] = atoi(ptr);
2135 do {
2136 ptr++;
2137 } while (*ptr >= '0' && *ptr <= '9');
2138 } else if (*ptr == ' ') {
2139 ptr++;
2140 } else {
2141 cliShowParseError();
2142 return;
2146 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2148 i = arguments[INDEX];
2150 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2151 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2152 cliShowParseError();
2153 return;
2156 servo = servoParamsMutable(i);
2158 if (
2159 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
2160 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
2161 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2162 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
2163 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2164 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2166 cliShowParseError();
2167 return;
2170 servo->min = arguments[MIN];
2171 servo->max = arguments[MAX];
2172 servo->middle = arguments[MIDDLE];
2173 servo->rate = arguments[RATE];
2174 servo->forwardFromChannel = arguments[FORWARD];
2176 cliDumpPrintLinef(0, false, format,
2178 servo->min,
2179 servo->max,
2180 servo->middle,
2181 servo->rate,
2182 servo->forwardFromChannel
2187 #endif
2189 #ifdef USE_SERVOS
2190 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2192 const char *format = "smix %d %d %d %d %d %d %d %d";
2193 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2194 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2195 const servoMixer_t customServoMixer = customServoMixers[i];
2196 if (customServoMixer.rate == 0) {
2197 break;
2200 bool equalsDefault = false;
2201 if (defaultCustomServoMixers) {
2202 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2203 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2205 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2206 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2208 customServoMixerDefault.targetChannel,
2209 customServoMixerDefault.inputSource,
2210 customServoMixerDefault.rate,
2211 customServoMixerDefault.speed,
2212 customServoMixerDefault.min,
2213 customServoMixerDefault.max,
2214 customServoMixerDefault.box
2217 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2219 customServoMixer.targetChannel,
2220 customServoMixer.inputSource,
2221 customServoMixer.rate,
2222 customServoMixer.speed,
2223 customServoMixer.min,
2224 customServoMixer.max,
2225 customServoMixer.box
2230 static void cliServoMix(char *cmdline)
2232 int args[8], check = 0;
2233 int len = strlen(cmdline);
2235 if (len == 0) {
2236 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2237 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2238 // erase custom mixer
2239 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2240 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2241 servoParamsMutable(i)->reversedSources = 0;
2243 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2244 const char *ptr = nextArg(cmdline);
2245 if (ptr) {
2246 len = strlen(ptr);
2247 for (uint32_t i = 0; ; i++) {
2248 if (mixerNames[i] == NULL) {
2249 cliPrintErrorLinef("INVALID NAME");
2250 break;
2252 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2253 servoMixerLoadMix(i);
2254 cliPrintLinef("Loaded %s", mixerNames[i]);
2255 cliServoMix("");
2256 break;
2260 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2261 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2262 char *ptr = strchr(cmdline, ' ');
2264 if (ptr == NULL) {
2265 cliPrintf("s");
2266 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2267 cliPrintf("\ti%d", inputSource);
2268 cliPrintLinefeed();
2270 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2271 cliPrintf("%d", servoIndex);
2272 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2273 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2275 cliPrintLinefeed();
2277 return;
2280 char *saveptr;
2281 ptr = strtok_r(ptr, " ", &saveptr);
2282 while (ptr != NULL && check < ARGS_COUNT - 1) {
2283 args[check++] = atoi(ptr);
2284 ptr = strtok_r(NULL, " ", &saveptr);
2287 if (ptr == NULL || check != ARGS_COUNT - 1) {
2288 cliShowParseError();
2289 return;
2292 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2293 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2294 && (*ptr == 'r' || *ptr == 'n')) {
2295 if (*ptr == 'r') {
2296 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2297 } else {
2298 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2300 } else {
2301 cliShowParseError();
2302 return;
2305 cliServoMix("reverse");
2306 } else {
2307 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2308 char *saveptr;
2309 char *ptr = strtok_r(cmdline, " ", &saveptr);
2310 while (ptr != NULL && check < ARGS_COUNT) {
2311 args[check++] = atoi(ptr);
2312 ptr = strtok_r(NULL, " ", &saveptr);
2315 if (ptr != NULL || check != ARGS_COUNT) {
2316 cliShowParseError();
2317 return;
2320 int32_t i = args[RULE];
2321 if (i >= 0 && i < MAX_SERVO_RULES &&
2322 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2323 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2324 args[RATE] >= -100 && args[RATE] <= 100 &&
2325 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2326 args[MIN] >= 0 && args[MIN] <= 100 &&
2327 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2328 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2329 customServoMixersMutable(i)->targetChannel = args[TARGET];
2330 customServoMixersMutable(i)->inputSource = args[INPUT];
2331 customServoMixersMutable(i)->rate = args[RATE];
2332 customServoMixersMutable(i)->speed = args[SPEED];
2333 customServoMixersMutable(i)->min = args[MIN];
2334 customServoMixersMutable(i)->max = args[MAX];
2335 customServoMixersMutable(i)->box = args[BOX];
2336 cliServoMix("");
2337 } else {
2338 cliShowParseError();
2342 #endif
2344 #ifdef USE_SDCARD
2346 static void cliWriteBytes(const uint8_t *buffer, int count)
2348 while (count > 0) {
2349 cliWrite(*buffer);
2350 buffer++;
2351 count--;
2355 static void cliSdInfo(char *cmdline)
2357 UNUSED(cmdline);
2359 cliPrint("SD card: ");
2361 if (sdcardConfig()->mode == SDCARD_MODE_NONE) {
2362 cliPrintLine("Not configured");
2364 return;
2367 if (!sdcard_isInserted()) {
2368 cliPrintLine("None inserted");
2369 return;
2372 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2373 cliPrintLine("Startup failed");
2374 return;
2377 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2379 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2380 metadata->manufacturerID,
2381 metadata->numBlocks / 2, /* One block is half a kB */
2382 metadata->productionMonth,
2383 metadata->productionYear,
2384 metadata->productRevisionMajor,
2385 metadata->productRevisionMinor
2388 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2390 cliPrint("'\r\n" "Filesystem: ");
2392 switch (afatfs_getFilesystemState()) {
2393 case AFATFS_FILESYSTEM_STATE_READY:
2394 cliPrint("Ready");
2395 break;
2396 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2397 cliPrint("Initializing");
2398 break;
2399 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2400 case AFATFS_FILESYSTEM_STATE_FATAL:
2401 cliPrint("Fatal");
2403 switch (afatfs_getLastError()) {
2404 case AFATFS_ERROR_BAD_MBR:
2405 cliPrint(" - no FAT MBR partitions");
2406 break;
2407 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2408 cliPrint(" - bad FAT header");
2409 break;
2410 case AFATFS_ERROR_GENERIC:
2411 case AFATFS_ERROR_NONE:
2412 ; // Nothing more detailed to print
2413 break;
2415 break;
2417 cliPrintLinefeed();
2420 #endif
2422 #ifdef USE_FLASH_CHIP
2424 static void cliFlashInfo(char *cmdline)
2426 const flashGeometry_t *layout = flashGetGeometry();
2428 UNUSED(cmdline);
2430 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2431 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2433 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2434 const flashPartition_t *partition;
2435 if (index == 0) {
2436 cliPrintLine("Paritions:");
2438 partition = flashPartitionFindByIndex(index);
2439 if (!partition) {
2440 break;
2442 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2444 #ifdef USE_FLASHFS
2445 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2447 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2448 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2449 flashfsGetOffset()
2451 #endif
2455 static void cliFlashErase(char *cmdline)
2457 UNUSED(cmdline);
2459 if (!flashfsIsSupported()) {
2460 return;
2463 #ifndef MINIMAL_CLI
2464 uint32_t i = 0;
2465 cliPrintLine("Erasing, please wait ... ");
2466 #else
2467 cliPrintLine("Erasing,");
2468 #endif
2470 cliWriterFlush();
2471 flashfsEraseCompletely();
2473 while (!flashfsIsReady()) {
2474 #ifndef MINIMAL_CLI
2475 cliPrintf(".");
2476 if (i++ > 120) {
2477 i=0;
2478 cliPrintLinefeed();
2481 cliWriterFlush();
2482 #endif
2483 delay(100);
2485 beeper(BEEPER_BLACKBOX_ERASE);
2486 cliPrintLinefeed();
2487 cliPrintLine("Done.");
2490 #ifdef USE_FLASH_TOOLS
2492 static void cliFlashVerify(char *cmdline)
2494 UNUSED(cmdline);
2496 cliPrintLine("Verifying");
2497 if (flashfsVerifyEntireFlash()) {
2498 cliPrintLine("Success");
2499 } else {
2500 cliPrintLine("Failed");
2504 static void cliFlashWrite(char *cmdline)
2506 const uint32_t address = atoi(cmdline);
2507 const char *text = strchr(cmdline, ' ');
2509 if (!text) {
2510 cliShowParseError();
2511 } else {
2512 flashfsSeekAbs(address);
2513 flashfsWrite((uint8_t*)text, strlen(text), true);
2514 flashfsFlushSync();
2516 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2520 static void cliFlashRead(char *cmdline)
2522 uint32_t address = atoi(cmdline);
2524 const char *nextArg = strchr(cmdline, ' ');
2526 if (!nextArg) {
2527 cliShowParseError();
2528 } else {
2529 uint32_t length = atoi(nextArg);
2531 cliPrintLinef("Reading %u bytes at %u:", length, address);
2533 uint8_t buffer[32];
2534 while (length > 0) {
2535 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2537 for (int i = 0; i < bytesRead; i++) {
2538 cliWrite(buffer[i]);
2541 length -= bytesRead;
2542 address += bytesRead;
2544 if (bytesRead == 0) {
2545 //Assume we reached the end of the volume or something fatal happened
2546 break;
2549 cliPrintLinefeed();
2553 #endif
2554 #endif
2556 #ifdef USE_VTX_CONTROL
2557 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2559 // print out vtx channel settings
2560 const char *format = "vtx %u %u %u %u %u %u %u";
2561 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2562 bool equalsDefault = false;
2563 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2564 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2565 if (vtxConfigDefault) {
2566 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2567 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2568 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2569 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2571 cacDefault->auxChannelIndex,
2572 cacDefault->band,
2573 cacDefault->channel,
2574 cacDefault->power,
2575 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2576 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2579 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2581 cac->auxChannelIndex,
2582 cac->band,
2583 cac->channel,
2584 cac->power,
2585 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2586 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2591 static void cliVtx(char *cmdline)
2593 const char *format = "vtx %u %u %u %u %u %u %u";
2594 int i, val = 0;
2595 const char *ptr;
2597 if (isEmpty(cmdline)) {
2598 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2599 } else {
2600 #ifdef USE_VTX_TABLE
2601 const uint8_t maxBandIndex = vtxTableConfig()->bands;
2602 const uint8_t maxChannelIndex = vtxTableConfig()->channels;
2603 const uint8_t maxPowerIndex = vtxTableConfig()->powerLevels;
2604 #else
2605 const uint8_t maxBandIndex = VTX_TABLE_MAX_BANDS;
2606 const uint8_t maxChannelIndex = VTX_TABLE_MAX_CHANNELS;
2607 const uint8_t maxPowerIndex = VTX_TABLE_MAX_POWER_LEVELS;
2608 #endif
2609 ptr = cmdline;
2610 i = atoi(ptr++);
2611 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2612 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2613 uint8_t validArgumentCount = 0;
2614 ptr = nextArg(ptr);
2615 if (ptr) {
2616 val = atoi(ptr);
2617 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2618 cac->auxChannelIndex = val;
2619 validArgumentCount++;
2622 ptr = nextArg(ptr);
2623 if (ptr) {
2624 val = atoi(ptr);
2625 if (val >= 0 && val <= maxBandIndex) {
2626 cac->band = val;
2627 validArgumentCount++;
2630 ptr = nextArg(ptr);
2631 if (ptr) {
2632 val = atoi(ptr);
2633 if (val >= 0 && val <= maxChannelIndex) {
2634 cac->channel = val;
2635 validArgumentCount++;
2638 ptr = nextArg(ptr);
2639 if (ptr) {
2640 val = atoi(ptr);
2641 if (val >= 0 && val <= maxPowerIndex) {
2642 cac->power= val;
2643 validArgumentCount++;
2646 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2648 if (validArgumentCount != 6) {
2649 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2650 cliShowParseError();
2651 } else {
2652 cliDumpPrintLinef(0, false, format,
2654 cac->auxChannelIndex,
2655 cac->band,
2656 cac->channel,
2657 cac->power,
2658 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2659 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2662 } else {
2663 cliShowArgumentRangeError("INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2668 #endif // VTX_CONTROL
2670 #ifdef USE_VTX_TABLE
2672 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2674 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2675 char freqtmp[5 + 1];
2676 freqbuf[0] = 0;
2677 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2678 for (int channel = 0; channel < channels; channel++) {
2679 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2680 strcat(freqbuf, freqtmp);
2682 return freqbuf;
2685 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2687 char *fmt = "vtxtable band %d %s %c%s";
2688 bool equalsDefault = false;
2690 if (defaultConfig) {
2691 equalsDefault = true;
2692 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2693 equalsDefault = false;
2695 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2696 equalsDefault = false;
2698 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2699 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2700 equalsDefault = false;
2703 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2704 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2705 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2708 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2709 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2710 return headingStr;
2713 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2715 // (max 4 digit + 1 space) per level
2716 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2717 char pwrtmp[5 + 1];
2718 pwrbuf[0] = 0;
2719 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2720 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2721 strcat(pwrbuf, pwrtmp);
2723 return pwrbuf;
2726 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2728 char *fmt = "vtxtable powervalues%s";
2729 bool equalsDefault = false;
2730 if (defaultConfig) {
2731 equalsDefault = true;
2732 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2733 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2734 equalsDefault = false;
2737 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2738 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2739 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2742 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2743 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2744 return headingStr;
2747 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2749 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2750 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2751 pwrbuf[0] = 0;
2752 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2753 strcat(pwrbuf, " ");
2754 strcpy(pwrtmp, labels[pwrindex]);
2755 // trim trailing space
2756 char *sp;
2757 while ((sp = strchr(pwrtmp, ' '))) {
2758 *sp = 0;
2760 strcat(pwrbuf, pwrtmp);
2762 return pwrbuf;
2765 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2767 char *fmt = "vtxtable powerlabels%s";
2768 bool equalsDefault = false;
2769 if (defaultConfig) {
2770 equalsDefault = true;
2771 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2772 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2773 equalsDefault = false;
2776 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2777 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2778 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2781 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2782 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2783 return headingStr;
2786 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2788 bool equalsDefault;
2789 char *fmt;
2791 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2793 // bands
2794 equalsDefault = false;
2795 fmt = "vtxtable bands %d";
2796 if (defaultConfig) {
2797 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2798 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2799 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2801 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2803 // channels
2804 equalsDefault = false;
2805 fmt = "vtxtable channels %d";
2806 if (defaultConfig) {
2807 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2808 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2809 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2811 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2813 // band
2815 for (int band = 0; band < currentConfig->bands; band++) {
2816 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2819 // powerlevels
2821 equalsDefault = false;
2822 fmt = "vtxtable powerlevels %d";
2823 if (defaultConfig) {
2824 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2825 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2826 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2828 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2830 // powervalues
2832 // powerlabels
2833 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2834 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2837 static void cliVtxTable(char *cmdline)
2839 char *tok;
2840 char *saveptr;
2842 // Band number or nothing
2843 tok = strtok_r(cmdline, " ", &saveptr);
2845 if (!tok) {
2846 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2847 return;
2850 if (strcasecmp(tok, "bands") == 0) {
2851 tok = strtok_r(NULL, " ", &saveptr);
2852 int bands = atoi(tok);
2853 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2854 cliPrintErrorLinef("INVALID BAND COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_BANDS);
2855 return;
2857 if (bands < vtxTableConfigMutable()->bands) {
2858 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2859 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2862 vtxTableConfigMutable()->bands = bands;
2864 } else if (strcasecmp(tok, "channels") == 0) {
2865 tok = strtok_r(NULL, " ", &saveptr);
2867 int channels = atoi(tok);
2868 if (channels < 0 || channels > VTX_TABLE_MAX_CHANNELS) {
2869 cliPrintErrorLinef("INVALID CHANNEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_CHANNELS);
2870 return;
2872 if (channels < vtxTableConfigMutable()->channels) {
2873 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2874 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2877 vtxTableConfigMutable()->channels = channels;
2879 } else if (strcasecmp(tok, "powerlevels") == 0) {
2880 // Number of power levels
2881 tok = strtok_r(NULL, " ", &saveptr);
2882 if (tok) {
2883 int levels = atoi(tok);
2884 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2885 cliPrintErrorLinef("INVALID POWER LEVEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_POWER_LEVELS);
2886 } else {
2887 if (levels < vtxTableConfigMutable()->powerLevels) {
2888 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2889 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2891 vtxTableConfigMutable()->powerLevels = levels;
2893 } else {
2894 // XXX Show current level count?
2896 return;
2898 } else if (strcasecmp(tok, "powervalues") == 0) {
2899 // Power values
2900 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2901 int count;
2902 int levels = vtxTableConfigMutable()->powerLevels;
2904 memset(power, 0, sizeof(power));
2906 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2907 int value = atoi(tok);
2908 power[count] = value;
2911 // Check remaining tokens
2913 if (count < levels) {
2914 cliPrintErrorLinef("NOT ENOUGH VALUES (EXPECTED %d)", levels);
2915 return;
2916 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2917 cliPrintErrorLinef("TOO MANY VALUES (EXPECTED %d)", levels);
2918 return;
2921 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
2922 vtxTableConfigMutable()->powerValues[i] = power[i];
2925 } else if (strcasecmp(tok, "powerlabels") == 0) {
2926 // Power labels
2927 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
2928 int levels = vtxTableConfigMutable()->powerLevels;
2929 int count;
2930 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2931 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
2932 for (unsigned i = 0; i < strlen(label[count]); i++) {
2933 label[count][i] = toupper(label[count][i]);
2937 // Check remaining tokens
2939 if (count < levels) {
2940 cliPrintErrorLinef("NOT ENOUGH LABELS (EXPECTED %d)", levels);
2941 return;
2942 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2943 cliPrintErrorLinef("TOO MANY LABELS (EXPECTED %d)", levels);
2944 return;
2947 for (int i = 0; i < count; i++) {
2948 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
2950 } else if (strcasecmp(tok, "band") == 0) {
2952 int bands = vtxTableConfigMutable()->bands;
2954 tok = strtok_r(NULL, " ", &saveptr);
2955 if (!tok) {
2956 return;
2959 int band = atoi(tok);
2960 --band;
2962 if (band < 0 || band >= bands) {
2963 cliPrintErrorLinef("INVALID BAND NUMBER %s (EXPECTED 1-%d)", tok, bands);
2964 return;
2967 // Band name
2968 tok = strtok_r(NULL, " ", &saveptr);
2970 if (!tok) {
2971 return;
2974 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
2975 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
2976 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
2977 for (unsigned i = 0; i < strlen(bandname); i++) {
2978 bandname[i] = toupper(bandname[i]);
2981 // Band letter
2982 tok = strtok_r(NULL, " ", &saveptr);
2984 if (!tok) {
2985 return;
2988 char bandletter = toupper(tok[0]);
2990 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
2991 int channel = 0;
2992 int channels = vtxTableConfigMutable()->channels;
2993 bool isFactory = false;
2995 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
2996 if (channel == 0 && !isdigit(tok[0])) {
2997 channel -= 1;
2998 if (strcasecmp(tok, "FACTORY") == 0) {
2999 isFactory = true;
3000 } else if (strcasecmp(tok, "CUSTOM") == 0) {
3001 isFactory = false;
3002 } else {
3003 cliPrintErrorLinef("INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
3004 return;
3007 int freq = atoi(tok);
3008 if (freq < 0) {
3009 cliPrintErrorLinef("INVALID FREQUENCY %s", tok);
3010 return;
3012 bandfreq[channel] = freq;
3015 if (channel < channels) {
3016 cliPrintErrorLinef("NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
3017 return;
3018 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3019 cliPrintErrorLinef("TOO MANY FREQUENCIES (EXPECTED %d)", channels);
3020 return;
3023 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
3024 vtxTableConfigMutable()->bandLetters[band] = bandletter;
3026 for (int i = 0; i < channel; i++) {
3027 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
3029 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
3030 } else {
3031 // Bad subcommand
3032 cliPrintErrorLinef("INVALID SUBCOMMAND %s", tok);
3036 static void cliVtxInfo(char *cmdline)
3038 UNUSED(cmdline);
3040 // Display the available power levels
3041 uint16_t levels[VTX_TABLE_MAX_POWER_LEVELS];
3042 uint16_t powers[VTX_TABLE_MAX_POWER_LEVELS];
3043 vtxDevice_t *vtxDevice = vtxCommonDevice();
3044 if (vtxDevice) {
3045 uint8_t level_count = vtxCommonGetVTXPowerLevels(vtxDevice, levels, powers);
3047 if (level_count) {
3048 for (int i = 0; i < level_count; i++) {
3049 cliPrintLinef("level %d dBm, power %d mW", levels[i], powers[i]);
3051 } else {
3052 cliPrintErrorLinef("NO POWER VALUES DEFINED");
3054 } else {
3055 cliPrintErrorLinef("NO VTX");
3058 #endif // USE_VTX_TABLE
3060 static void printName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
3062 const bool equalsDefault = strlen(pilotConfig->name) == 0;
3063 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->name);
3066 #if defined(USE_BOARD_INFO)
3068 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
3070 static void printBoardName(dumpFlags_t dumpMask)
3072 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
3073 cliPrintLinef("board_name %s", getBoardName());
3077 static void cliBoardName(char *cmdline)
3079 const unsigned int len = strlen(cmdline);
3080 const char *boardName = getBoardName();
3081 if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
3082 cliPrintErrorLinef(ERROR_MESSAGE, "BOARD_NAME", boardName);
3083 } else {
3084 if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
3085 boardInformationUpdated = true;
3087 cliPrintHashLine("Set board_name.");
3089 printBoardName(DUMP_ALL);
3093 static void printManufacturerId(dumpFlags_t dumpMask)
3095 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
3096 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3100 static void cliManufacturerId(char *cmdline)
3102 const unsigned int len = strlen(cmdline);
3103 const char *manufacturerId = getManufacturerId();
3104 if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
3105 cliPrintErrorLinef(ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
3106 } else {
3107 if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
3108 boardInformationUpdated = true;
3110 cliPrintHashLine("Set manufacturer_id.");
3112 printManufacturerId(DUMP_ALL);
3116 #if defined(USE_SIGNATURE)
3117 static void writeSignature(char *signatureStr, uint8_t *signature)
3119 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3120 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
3124 static void cliSignature(char *cmdline)
3126 const int len = strlen(cmdline);
3128 uint8_t signature[SIGNATURE_LENGTH] = {0};
3129 if (len > 0) {
3130 if (len != 2 * SIGNATURE_LENGTH) {
3131 cliPrintErrorLinef("INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
3133 return;
3136 #define BLOCK_SIZE 2
3137 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3138 char temp[BLOCK_SIZE + 1];
3139 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3140 temp[BLOCK_SIZE] = '\0';
3141 char *end;
3142 unsigned result = strtoul(temp, &end, 16);
3143 if (end == &temp[BLOCK_SIZE]) {
3144 signature[i] = result;
3145 } else {
3146 cliPrintErrorLinef("INVALID CHARACTER FOUND: %c", end[0]);
3148 return;
3151 #undef BLOCK_SIZE
3154 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3155 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3156 writeSignature(signatureStr, getSignature());
3157 cliPrintErrorLinef(ERROR_MESSAGE, "SIGNATURE", signatureStr);
3158 } else {
3159 if (len > 0 && !configIsInCopy && setSignature(signature)) {
3160 signatureUpdated = true;
3162 writeSignature(signatureStr, getSignature());
3164 cliPrintHashLine("Set signature.");
3165 } else if (signatureUpdated || signatureIsSet()) {
3166 writeSignature(signatureStr, getSignature());
3169 cliPrintLinef("signature %s", signatureStr);
3172 #endif
3174 #undef ERROR_MESSAGE
3176 #endif // USE_BOARD_INFO
3178 static void cliMcuId(char *cmdline)
3180 UNUSED(cmdline);
3182 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3185 static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
3187 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3188 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
3189 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3190 const char *format = "feature -%s";
3191 const bool equalsDefault = (~defaultMask | mask) & (1 << i);
3192 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3193 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
3194 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3197 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
3198 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3199 const char *format = "feature %s";
3200 if (defaultMask & (1 << i)) {
3201 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
3203 if (mask & (1 << i)) {
3204 const bool equalsDefault = (defaultMask | ~mask) & (1 << i);
3205 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3206 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3212 static void cliFeature(char *cmdline)
3214 uint32_t len = strlen(cmdline);
3215 const uint32_t mask = featureConfig()->enabledFeatures;
3216 if (len == 0) {
3217 cliPrint("Enabled: ");
3218 for (uint32_t i = 0; ; i++) {
3219 if (featureNames[i] == NULL) {
3220 break;
3222 if (mask & (1 << i)) {
3223 cliPrintf("%s ", featureNames[i]);
3226 cliPrintLinefeed();
3227 } else if (strncasecmp(cmdline, "list", len) == 0) {
3228 cliPrint("Available:");
3229 for (uint32_t i = 0; ; i++) {
3230 if (featureNames[i] == NULL)
3231 break;
3232 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
3233 cliPrintf(" %s", featureNames[i]);
3235 cliPrintLinefeed();
3236 return;
3237 } else {
3238 uint32_t feature;
3240 bool remove = false;
3241 if (cmdline[0] == '-') {
3242 // remove feature
3243 remove = true;
3244 cmdline++; // skip over -
3245 len--;
3248 for (uint32_t i = 0; ; i++) {
3249 if (featureNames[i] == NULL) {
3250 cliPrintErrorLinef("INVALID NAME");
3251 break;
3254 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
3255 feature = 1 << i;
3256 #ifndef USE_GPS
3257 if (feature & FEATURE_GPS) {
3258 cliPrintLine("unavailable");
3259 break;
3261 #endif
3262 #ifndef USE_RANGEFINDER
3263 if (feature & FEATURE_RANGEFINDER) {
3264 cliPrintLine("unavailable");
3265 break;
3267 #endif
3268 if (remove) {
3269 featureConfigClear(feature);
3270 cliPrint("Disabled");
3271 } else {
3272 featureConfigSet(feature);
3273 cliPrint("Enabled");
3275 cliPrintLinef(" %s", featureNames[i]);
3276 break;
3282 #if defined(USE_BEEPER)
3283 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3285 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3286 const uint8_t beeperCount = beeperTableEntryCount();
3287 for (int32_t i = 0; i < beeperCount - 1; i++) {
3288 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3289 const char *formatOff = "%s -%s";
3290 const char *formatOn = "%s %s";
3291 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3292 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3293 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3294 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3295 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3300 static void processBeeperCommand(char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3302 uint32_t len = strlen(cmdline);
3303 uint8_t beeperCount = beeperTableEntryCount();
3305 if (len == 0) {
3306 cliPrintf("Disabled:");
3307 for (int32_t i = 0; ; i++) {
3308 if (i == beeperCount - 1) {
3309 if (*offFlags == 0)
3310 cliPrint(" none");
3311 break;
3314 if (beeperModeMaskForTableIndex(i) & *offFlags)
3315 cliPrintf(" %s", beeperNameForTableIndex(i));
3317 cliPrintLinefeed();
3318 } else if (strncasecmp(cmdline, "list", len) == 0) {
3319 cliPrint("Available:");
3320 for (uint32_t i = 0; i < beeperCount; i++) {
3321 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3322 cliPrintf(" %s", beeperNameForTableIndex(i));
3325 cliPrintLinefeed();
3326 } else {
3327 bool remove = false;
3328 if (cmdline[0] == '-') {
3329 remove = true; // this is for beeper OFF condition
3330 cmdline++;
3331 len--;
3334 for (uint32_t i = 0; ; i++) {
3335 if (i == beeperCount) {
3336 cliPrintErrorLinef("INVALID NAME");
3337 break;
3339 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3340 if (remove) { // beeper off
3341 if (i == BEEPER_ALL - 1) {
3342 *offFlags = allowedFlags;
3343 } else {
3344 *offFlags |= beeperModeMaskForTableIndex(i);
3346 cliPrint("Disabled");
3348 else { // beeper on
3349 if (i == BEEPER_ALL - 1) {
3350 *offFlags = 0;
3351 } else {
3352 *offFlags &= ~beeperModeMaskForTableIndex(i);
3354 cliPrint("Enabled");
3356 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3357 break;
3363 #if defined(USE_DSHOT)
3364 static void cliBeacon(char *cmdline)
3366 processBeeperCommand(cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3368 #endif
3370 static void cliBeeper(char *cmdline)
3372 processBeeperCommand(cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3374 #endif
3376 #if defined(USE_RX_BIND)
3377 void cliRxBind(char *cmdline){
3378 UNUSED(cmdline);
3379 if (!startRxBind()) {
3380 cliPrint("Not supported.");
3381 } else {
3382 cliPrint("Binding...");
3385 #endif
3387 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3389 bool equalsDefault = true;
3390 char buf[16];
3391 char bufDefault[16];
3392 uint32_t i;
3394 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3395 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3396 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3397 if (defaultRxConfig) {
3398 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3399 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3402 buf[i] = '\0';
3404 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3405 const char *formatMap = "map %s";
3406 if (defaultRxConfig) {
3407 bufDefault[i] = '\0';
3408 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3410 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3414 static void cliMap(char *cmdline)
3416 uint32_t i;
3417 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3419 uint32_t len = strlen(cmdline);
3420 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3422 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3423 buf[i] = toupper((unsigned char)cmdline[i]);
3425 buf[i] = '\0';
3427 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3428 buf[i] = toupper((unsigned char)cmdline[i]);
3430 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3431 continue;
3433 cliShowParseError();
3434 return;
3436 parseRcChannels(buf, rxConfigMutable());
3437 } else if (len > 0) {
3438 cliShowParseError();
3439 return;
3442 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3443 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3446 buf[i] = '\0';
3447 cliPrintLinef("map %s", buf);
3450 static char *skipSpace(char *buffer)
3452 while (*(buffer) == ' ') {
3453 buffer++;
3456 return buffer;
3459 static char *checkCommand(char *cmdline, const char *command)
3461 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3462 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3463 return skipSpace(cmdline + strlen(command) + 1);
3464 } else {
3465 return 0;
3469 static void cliRebootEx(rebootTarget_e rebootTarget)
3471 cliPrint("\r\nRebooting");
3472 cliWriterFlush();
3473 waitForSerialPortToFinishTransmitting(cliPort);
3474 motorShutdown();
3476 switch (rebootTarget) {
3477 case REBOOT_TARGET_BOOTLOADER_ROM:
3478 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3480 break;
3481 #if defined(USE_FLASH_BOOT_LOADER)
3482 case REBOOT_TARGET_BOOTLOADER_FLASH:
3483 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3485 break;
3486 #endif
3487 case REBOOT_TARGET_FIRMWARE:
3488 default:
3489 systemReset();
3491 break;
3495 static void cliReboot(void)
3497 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3500 static void cliBootloader(char *cmdline)
3502 rebootTarget_e rebootTarget;
3503 if (
3504 #if !defined(USE_FLASH_BOOT_LOADER)
3505 isEmpty(cmdline) ||
3506 #endif
3507 strncasecmp(cmdline, "rom", 3) == 0) {
3508 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3510 cliPrintHashLine("restarting in ROM bootloader mode");
3511 #if defined(USE_FLASH_BOOT_LOADER)
3512 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3513 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3515 cliPrintHashLine("restarting in flash bootloader mode");
3516 #endif
3517 } else {
3518 cliPrintErrorLinef("Invalid option");
3520 return;
3523 cliRebootEx(rebootTarget);
3526 static void cliExit(char *cmdline)
3528 UNUSED(cmdline);
3530 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3531 cliWriterFlush();
3533 *cliBuffer = '\0';
3534 bufferIndex = 0;
3535 cliMode = false;
3536 // incase a motor was left running during motortest, clear it here
3537 mixerResetDisarmedMotors();
3538 cliReboot();
3541 #ifdef USE_GPS
3542 static void cliGpsPassthrough(char *cmdline)
3544 UNUSED(cmdline);
3546 gpsEnablePassthrough(cliPort);
3548 #endif
3550 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3551 static void cliPrintGyroRegisters(uint8_t whichSensor)
3553 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3554 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3555 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3558 static void cliDumpGyroRegisters(char *cmdline)
3560 #ifdef USE_MULTI_GYRO
3561 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3562 cliPrintLinef("\r\n# Gyro 1");
3563 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3565 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3566 cliPrintLinef("\r\n# Gyro 2");
3567 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3569 #else
3570 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3571 #endif
3572 UNUSED(cmdline);
3574 #endif
3577 static int parseOutputIndex(char *pch, bool allowAllEscs) {
3578 int outputIndex = atoi(pch);
3579 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3580 cliPrintLinef("Using output %d.", outputIndex);
3581 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3582 cliPrintLinef("Using all outputs.");
3583 } else {
3584 cliPrintErrorLinef("INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3586 return -1;
3589 return outputIndex;
3592 #if defined(USE_DSHOT)
3593 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3595 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3596 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3597 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3599 enum {
3600 ESC_INFO_KISS_V1,
3601 ESC_INFO_KISS_V2,
3602 ESC_INFO_BLHELI32
3605 #define ESC_INFO_VERSION_POSITION 12
3607 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
3609 bool escInfoReceived = false;
3610 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3611 uint8_t escInfoVersion;
3612 uint8_t frameLength;
3613 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3614 escInfoVersion = ESC_INFO_BLHELI32;
3615 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3616 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3617 escInfoVersion = ESC_INFO_KISS_V2;
3618 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3619 } else {
3620 escInfoVersion = ESC_INFO_KISS_V1;
3621 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3624 if (bytesRead == frameLength) {
3625 escInfoReceived = true;
3627 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3628 uint8_t firmwareVersion = 0;
3629 uint8_t firmwareSubVersion = 0;
3630 uint8_t escType = 0;
3631 switch (escInfoVersion) {
3632 case ESC_INFO_KISS_V1:
3633 firmwareVersion = escInfoBuffer[12];
3634 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3635 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3637 break;
3638 case ESC_INFO_KISS_V2:
3639 firmwareVersion = escInfoBuffer[13];
3640 firmwareSubVersion = escInfoBuffer[14];
3641 escType = escInfoBuffer[15];
3643 break;
3644 case ESC_INFO_BLHELI32:
3645 firmwareVersion = escInfoBuffer[13];
3646 firmwareSubVersion = escInfoBuffer[14];
3647 escType = escInfoBuffer[15];
3649 break;
3652 cliPrint("ESC Type: ");
3653 switch (escInfoVersion) {
3654 case ESC_INFO_KISS_V1:
3655 case ESC_INFO_KISS_V2:
3656 switch (escType) {
3657 case 1:
3658 cliPrintLine("KISS8A");
3660 break;
3661 case 2:
3662 cliPrintLine("KISS16A");
3664 break;
3665 case 3:
3666 cliPrintLine("KISS24A");
3668 break;
3669 case 5:
3670 cliPrintLine("KISS Ultralite");
3672 break;
3673 default:
3674 cliPrintLine("unknown");
3676 break;
3679 break;
3680 case ESC_INFO_BLHELI32:
3682 char *escType = (char *)(escInfoBuffer + 31);
3683 escType[32] = 0;
3684 cliPrintLine(escType);
3687 break;
3690 cliPrint("MCU Serial No: 0x");
3691 for (int i = 0; i < 12; i++) {
3692 if (i && (i % 3 == 0)) {
3693 cliPrint("-");
3695 cliPrintf("%02x", escInfoBuffer[i]);
3697 cliPrintLinefeed();
3699 switch (escInfoVersion) {
3700 case ESC_INFO_KISS_V1:
3701 case ESC_INFO_KISS_V2:
3702 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3704 break;
3705 case ESC_INFO_BLHELI32:
3706 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3708 break;
3710 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3711 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3712 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3713 if (escInfoVersion == ESC_INFO_BLHELI32) {
3714 uint8_t setting = escInfoBuffer[18];
3715 cliPrint("Low voltage Limit: ");
3716 switch (setting) {
3717 case 0:
3718 cliPrintLine("off");
3720 break;
3721 case 255:
3722 cliPrintLine("unsupported");
3724 break;
3725 default:
3726 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3728 break;
3731 setting = escInfoBuffer[19];
3732 cliPrint("Current Limit: ");
3733 switch (setting) {
3734 case 0:
3735 cliPrintLine("off");
3737 break;
3738 case 255:
3739 cliPrintLine("unsupported");
3741 break;
3742 default:
3743 cliPrintLinef("%d", setting);
3745 break;
3748 for (int i = 0; i < 4; i++) {
3749 setting = escInfoBuffer[i + 20];
3750 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3754 } else {
3755 cliPrintErrorLinef("CHECKSUM ERROR.");
3760 if (!escInfoReceived) {
3761 cliPrintLine("No Info.");
3765 static void executeEscInfoCommand(uint8_t escIndex)
3767 cliPrintLinef("Info for ESC %d:", escIndex);
3769 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3771 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3773 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, true);
3775 delay(10);
3777 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
3779 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3782 // XXX Review dshotprog command under refactored motor handling
3784 static void cliDshotProg(char *cmdline)
3786 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
3787 cliShowParseError();
3789 return;
3792 char *saveptr;
3793 char *pch = strtok_r(cmdline, " ", &saveptr);
3794 int pos = 0;
3795 int escIndex = 0;
3796 bool firstCommand = true;
3797 while (pch != NULL) {
3798 switch (pos) {
3799 case 0:
3800 escIndex = parseOutputIndex(pch, true);
3801 if (escIndex == -1) {
3802 return;
3805 break;
3806 default:
3808 int command = atoi(pch);
3809 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3810 if (firstCommand) {
3811 // pwmDisableMotors();
3812 motorDisable();
3814 if (command == DSHOT_CMD_ESC_INFO) {
3815 delay(5); // Wait for potential ESC telemetry transmission to finish
3816 } else {
3817 delay(1);
3820 firstCommand = false;
3823 if (command != DSHOT_CMD_ESC_INFO) {
3824 dshotCommandWrite(escIndex, getMotorCount(), command, true);
3825 } else {
3826 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3827 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3828 if (escIndex != ALL_MOTORS) {
3829 executeEscInfoCommand(escIndex);
3830 } else {
3831 for (uint8_t i = 0; i < getMotorCount(); i++) {
3832 executeEscInfoCommand(i);
3835 } else
3836 #endif
3838 cliPrintLine("Not supported.");
3842 cliPrintLinef("Command Sent: %d", command);
3844 } else {
3845 cliPrintErrorLinef("INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3849 break;
3852 pos++;
3853 pch = strtok_r(NULL, " ", &saveptr);
3856 motorEnable();
3858 #endif // USE_DSHOT
3860 #ifdef USE_ESCSERIAL
3861 static void cliEscPassthrough(char *cmdline)
3863 if (isEmpty(cmdline)) {
3864 cliShowParseError();
3866 return;
3869 char *saveptr;
3870 char *pch = strtok_r(cmdline, " ", &saveptr);
3871 int pos = 0;
3872 uint8_t mode = 0;
3873 int escIndex = 0;
3874 while (pch != NULL) {
3875 switch (pos) {
3876 case 0:
3877 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3878 mode = PROTOCOL_SIMONK;
3879 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3880 mode = PROTOCOL_BLHELI;
3881 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3882 mode = PROTOCOL_KISS;
3883 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3884 mode = PROTOCOL_KISSALL;
3885 } else {
3886 cliShowParseError();
3888 return;
3890 break;
3891 case 1:
3892 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
3893 if (escIndex == -1) {
3894 return;
3897 break;
3898 default:
3899 cliShowParseError();
3901 return;
3903 break;
3906 pos++;
3907 pch = strtok_r(NULL, " ", &saveptr);
3910 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
3911 cliPrintErrorLinef("Error starting ESC connection");
3914 #endif
3916 #ifndef USE_QUAD_MIXER_ONLY
3917 static void cliMixer(char *cmdline)
3919 int len;
3921 len = strlen(cmdline);
3923 if (len == 0) {
3924 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3925 return;
3926 } else if (strncasecmp(cmdline, "list", len) == 0) {
3927 cliPrint("Available:");
3928 for (uint32_t i = 0; ; i++) {
3929 if (mixerNames[i] == NULL)
3930 break;
3931 cliPrintf(" %s", mixerNames[i]);
3933 cliPrintLinefeed();
3934 return;
3937 for (uint32_t i = 0; ; i++) {
3938 if (mixerNames[i] == NULL) {
3939 cliPrintErrorLinef("INVALID NAME");
3940 return;
3942 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
3943 mixerConfigMutable()->mixerMode = i + 1;
3944 break;
3948 cliMixer("");
3950 #endif
3952 static void cliMotor(char *cmdline)
3954 if (isEmpty(cmdline)) {
3955 cliShowParseError();
3957 return;
3960 int motorIndex = 0;
3961 int motorValue = 0;
3963 char *saveptr;
3964 char *pch = strtok_r(cmdline, " ", &saveptr);
3965 int index = 0;
3966 while (pch != NULL) {
3967 switch (index) {
3968 case 0:
3969 motorIndex = parseOutputIndex(pch, true);
3970 if (motorIndex == -1) {
3971 return;
3974 break;
3975 case 1:
3976 motorValue = atoi(pch);
3978 break;
3980 index++;
3981 pch = strtok_r(NULL, " ", &saveptr);
3984 if (index == 2) {
3985 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
3986 cliShowArgumentRangeError("VALUE", 1000, 2000);
3987 } else {
3988 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
3990 if (motorIndex != ALL_MOTORS) {
3991 motor_disarmed[motorIndex] = motorOutputValue;
3993 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
3994 } else {
3995 for (int i = 0; i < getMotorCount(); i++) {
3996 motor_disarmed[i] = motorOutputValue;
3999 cliPrintLinef("all motors: %d", motorOutputValue);
4002 } else {
4003 cliShowParseError();
4007 #ifndef MINIMAL_CLI
4008 static void cliPlaySound(char *cmdline)
4010 int i;
4011 const char *name;
4012 static int lastSoundIdx = -1;
4014 if (isEmpty(cmdline)) {
4015 i = lastSoundIdx + 1; //next sound index
4016 if ((name=beeperNameForTableIndex(i)) == NULL) {
4017 while (true) { //no name for index; try next one
4018 if (++i >= beeperTableEntryCount())
4019 i = 0; //if end then wrap around to first entry
4020 if ((name=beeperNameForTableIndex(i)) != NULL)
4021 break; //if name OK then play sound below
4022 if (i == lastSoundIdx + 1) { //prevent infinite loop
4023 cliPrintErrorLinef("ERROR PLAYING SOUND");
4024 return;
4028 } else { //index value was given
4029 i = atoi(cmdline);
4030 if ((name=beeperNameForTableIndex(i)) == NULL) {
4031 cliPrintLinef("No sound for index %d", i);
4032 return;
4035 lastSoundIdx = i;
4036 beeperSilence();
4037 cliPrintLinef("Playing sound %d: %s", i, name);
4038 beeper(beeperModeForTableIndex(i));
4040 #endif
4042 static void cliProfile(char *cmdline)
4044 if (isEmpty(cmdline)) {
4045 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4046 return;
4047 } else {
4048 const int i = atoi(cmdline);
4049 if (i >= 0 && i < PID_PROFILE_COUNT) {
4050 changePidProfile(i);
4051 cliProfile("");
4052 } else {
4053 cliPrintErrorLinef("PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
4058 static void cliRateProfile(char *cmdline)
4060 if (isEmpty(cmdline)) {
4061 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4062 return;
4063 } else {
4064 const int i = atoi(cmdline);
4065 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
4066 changeControlRateProfile(i);
4067 cliRateProfile("");
4068 } else {
4069 cliPrintErrorLinef("RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
4074 static void cliDumpPidProfile(uint8_t pidProfileIndex, dumpFlags_t dumpMask)
4076 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4077 // Faulty values
4078 return;
4081 pidProfileIndexToUse = pidProfileIndex;
4083 cliPrintLinefeed();
4084 cliProfile("");
4086 char profileStr[10];
4087 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4088 dumpAllValues(PROFILE_VALUE, dumpMask, profileStr);
4090 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4093 static void cliDumpRateProfile(uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4095 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4096 // Faulty values
4097 return;
4100 rateProfileIndexToUse = rateProfileIndex;
4102 cliPrintLinefeed();
4103 cliRateProfile("");
4105 char rateProfileStr[14];
4106 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4107 dumpAllValues(PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4109 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4112 #ifdef USE_CLI_BATCH
4113 static void cliPrintCommandBatchWarning(const char *warning)
4115 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4116 if (warning) {
4117 cliPrintErrorLinef(warning);
4121 static void resetCommandBatch(void)
4123 commandBatchActive = false;
4124 commandBatchError = false;
4127 static void cliBatch(char *cmdline)
4129 if (strncasecmp(cmdline, "start", 5) == 0) {
4130 if (!commandBatchActive) {
4131 commandBatchActive = true;
4132 commandBatchError = false;
4134 cliPrintLine("Command batch started");
4135 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4136 if (commandBatchActive && commandBatchError) {
4137 cliPrintCommandBatchWarning(NULL);
4138 } else {
4139 cliPrintLine("Command batch ended");
4141 resetCommandBatch();
4142 } else {
4143 cliPrintErrorLinef("Invalid option");
4146 #endif
4148 static bool prepareSave(void)
4150 #if defined(USE_CUSTOM_DEFAULTS)
4151 if (processingCustomDefaults) {
4152 return true;
4154 #endif
4156 #ifdef USE_CLI_BATCH
4157 if (commandBatchActive && commandBatchError) {
4158 return false;
4160 #endif
4162 #if defined(USE_BOARD_INFO)
4163 if (boardInformationUpdated) {
4164 persistBoardInformation();
4166 #if defined(USE_SIGNATURE)
4167 if (signatureUpdated) {
4168 persistSignature();
4170 #endif
4171 #endif // USE_BOARD_INFO
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(true);
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(false);
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 const char *getMcuTypeById(mcuTypeId_e id)
4575 if (id < MCU_TYPE_UNKNOWN) {
4576 return mcuTypeNames[id];
4577 } else {
4578 return "UNKNOWN";
4582 static void cliStatus(char *cmdline)
4584 UNUSED(cmdline);
4586 // MCU type, clock, vrefint, core temperature
4588 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock / 1000000));
4590 #ifdef STM32F4
4591 // Only F4 is capable of switching between HSE/HSI (for now)
4592 int sysclkSource = SystemSYSCLKSource();
4594 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4595 const char *PLLSource[] = { "-HSI", "-HSE" };
4597 int pllSource;
4599 if (sysclkSource >= 2) {
4600 pllSource = SystemPLLSource();
4603 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4604 #endif
4606 #ifdef USE_ADC_INTERNAL
4607 uint16_t vrefintMv = getVrefMv();
4608 int16_t coretemp = getCoreTemperatureCelsius();
4609 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4610 #else
4611 cliPrintLinefeed();
4612 #endif
4614 // Stack and config sizes and usages
4616 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4617 #ifdef STACK_CHECK
4618 cliPrintf(", Stack used: %d", stackUsedSize());
4619 #endif
4620 cliPrintLinefeed();
4622 cliPrintLinef("Config size: %d, Max available config: %d", getEEPROMConfigSize(), getEEPROMStorageSize());
4624 // Sensors
4626 #if defined(USE_SENSOR_NAMES)
4627 const uint32_t detectedSensorsMask = sensorsMask();
4628 for (uint32_t i = 0; ; i++) {
4629 if (sensorTypeNames[i] == NULL) {
4630 break;
4632 const uint32_t mask = (1 << i);
4633 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4634 const uint8_t sensorHardwareIndex = detectedSensors[i];
4635 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4636 if (i) {
4637 cliPrint(", ");
4639 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4640 #if defined(USE_ACC)
4641 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4642 cliPrintf(".%c", acc.dev.revisionCode);
4644 #endif
4647 cliPrintLinefeed();
4648 #endif /* USE_SENSOR_NAMES */
4650 #if defined(USE_OSD)
4651 osdDisplayPortDevice_e displayPortDevice;
4652 osdGetDisplayPort(&displayPortDevice);
4654 cliPrintLinef("OSD: %s", lookupTableOsdDisplayPortDevice[displayPortDevice]);
4655 #endif
4657 // Uptime and wall clock
4659 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4661 #ifdef USE_RTC_TIME
4662 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4663 dateTime_t dt;
4664 if (rtcGetDateTime(&dt)) {
4665 dateTimeFormatLocal(buf, &dt);
4666 cliPrintf(", Current Time: %s", buf);
4668 #endif
4669 cliPrintLinefeed();
4671 // Run status
4673 const int gyroRate = getTaskDeltaTimeUs(TASK_GYRO) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_GYRO)));
4674 int rxRate = getCurrentRxRefreshRate();
4675 if (rxRate != 0) {
4676 rxRate = (int)(1000000.0f / ((float)rxRate));
4678 const int systemRate = getTaskDeltaTimeUs(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_SYSTEM)));
4679 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4680 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE), getTaskDeltaTimeUs(TASK_GYRO), gyroRate, rxRate, systemRate);
4682 // Battery meter
4684 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4686 // Other devices and status
4688 #ifdef USE_I2C
4689 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4690 #else
4691 const uint16_t i2cErrorCounter = 0;
4692 #endif
4693 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4695 #ifdef USE_SDCARD
4696 cliSdInfo(NULL);
4697 #endif
4699 cliPrint("Arming disable flags:");
4700 armingDisableFlags_e flags = getArmingDisableFlags();
4701 while (flags) {
4702 const int bitpos = ffs(flags) - 1;
4703 flags &= ~(1 << bitpos);
4704 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4706 cliPrintLinefeed();
4709 #if defined(USE_TASK_STATISTICS)
4710 static void cliTasks(char *cmdline)
4712 UNUSED(cmdline);
4713 int maxLoadSum = 0;
4714 int averageLoadSum = 0;
4716 #ifndef MINIMAL_CLI
4717 if (systemConfig()->task_statistics) {
4718 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4719 } else {
4720 cliPrintLine("Task list");
4722 #endif
4723 for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4724 taskInfo_t taskInfo;
4725 getTaskInfo(taskId, &taskInfo);
4726 if (taskInfo.isEnabled) {
4727 int taskFrequency = taskInfo.averageDeltaTimeUs == 0 ? 0 : lrintf(1e6f / taskInfo.averageDeltaTimeUs);
4728 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4729 const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 :(taskInfo.maxExecutionTimeUs * taskFrequency + 5000) / 1000;
4730 const int averageLoad = taskInfo.averageExecutionTimeUs == 0 ? 0 : (taskInfo.averageExecutionTimeUs * taskFrequency + 5000) / 1000;
4731 if (taskId != TASK_SERIAL) {
4732 maxLoadSum += maxLoad;
4733 averageLoadSum += averageLoad;
4735 if (systemConfig()->task_statistics) {
4736 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4737 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTimeUs,
4738 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTimeUs / 1000);
4739 } else {
4740 cliPrintLinef("%6d", taskFrequency);
4743 schedulerResetTaskMaxExecutionTime(taskId);
4746 if (systemConfig()->task_statistics) {
4747 cfCheckFuncInfo_t checkFuncInfo;
4748 getCheckFuncInfo(&checkFuncInfo);
4749 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTimeUs, checkFuncInfo.averageExecutionTimeUs, checkFuncInfo.totalExecutionTimeUs / 1000);
4750 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
4751 schedulerResetCheckFunctionMaxExecutionTime();
4754 #endif
4756 static void cliVersion(char *cmdline)
4758 UNUSED(cmdline);
4760 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4761 FC_FIRMWARE_NAME,
4762 targetName,
4763 systemConfig()->boardIdentifier,
4764 FC_VERSION_STRING,
4765 buildDate,
4766 buildTime,
4767 shortGitRevision,
4768 MSP_API_VERSION_STRING
4771 #ifdef FEATURE_CUT_LEVEL
4772 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL);
4773 #else
4774 cliPrintLinefeed();
4775 #endif
4777 #ifdef USE_UNIFIED_TARGET
4778 cliPrint("# ");
4779 #ifdef USE_BOARD_INFO
4780 if (strlen(getManufacturerId())) {
4781 cliPrintf("manufacturer_id: %s ", getManufacturerId());
4783 if (strlen(getBoardName())) {
4784 cliPrintf("board_name: %s ", getBoardName());
4786 #endif // USE_BOARD_INFO
4788 #ifdef USE_CUSTOM_DEFAULTS
4789 cliPrintf("custom defaults: %s", hasCustomDefaults() ? "YES" : "NO");
4790 #endif // USE_CUSTOM_DEFAULTS
4792 cliPrintLinefeed();
4793 #endif // USE_UNIFIED_TARGET
4796 #ifdef USE_RC_SMOOTHING_FILTER
4797 static void cliRcSmoothing(char *cmdline)
4799 UNUSED(cmdline);
4800 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
4801 cliPrint("# RC Smoothing Type: ");
4802 if (rxConfig()->rc_smoothing_type == RC_SMOOTHING_TYPE_FILTER) {
4803 cliPrintLine("FILTER");
4804 if (rcSmoothingAutoCalculate()) {
4805 const uint16_t avgRxFrameUs = rcSmoothingData->averageFrameTimeUs;
4806 cliPrint("# Detected RX frame rate: ");
4807 if (avgRxFrameUs == 0) {
4808 cliPrintLine("NO SIGNAL");
4809 } else {
4810 cliPrintLinef("%d.%03dms", avgRxFrameUs / 1000, avgRxFrameUs % 1000);
4813 cliPrintLinef("# Input filter type: %s", lookupTables[TABLE_RC_SMOOTHING_INPUT_TYPE].values[rcSmoothingData->inputFilterType]);
4814 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingData->inputCutoffFrequency);
4815 if (rcSmoothingData->inputCutoffSetting == 0) {
4816 cliPrintLine("(auto)");
4817 } else {
4818 cliPrintLine("(manual)");
4820 cliPrintf("# Derivative filter type: %s", lookupTables[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE].values[rcSmoothingData->derivativeFilterType]);
4821 if (rcSmoothingData->derivativeFilterTypeSetting == RC_SMOOTHING_DERIVATIVE_AUTO) {
4822 cliPrintLine(" (auto)");
4823 } else {
4824 cliPrintLinefeed();
4826 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingData->derivativeCutoffFrequency);
4827 if (rcSmoothingData->derivativeFilterType == RC_SMOOTHING_DERIVATIVE_OFF) {
4828 cliPrintLine("off)");
4829 } else {
4830 if (rcSmoothingData->derivativeCutoffSetting == 0) {
4831 cliPrintLine("auto)");
4832 } else {
4833 cliPrintLine("manual)");
4836 } else {
4837 cliPrintLine("INTERPOLATION");
4840 #endif // USE_RC_SMOOTHING_FILTER
4842 #if defined(USE_RESOURCE_MGMT)
4844 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
4846 typedef struct {
4847 const uint8_t owner;
4848 pgn_t pgn;
4849 uint8_t stride;
4850 uint8_t offset;
4851 const uint8_t maxIndex;
4852 } cliResourceValue_t;
4854 // Handy macros for keeping the table tidy.
4855 // DEFS : Single entry
4856 // DEFA : Array of uint8_t (stride = 1)
4857 // DEFW : Wider stride case; array of structs.
4859 #define DEFS(owner, pgn, type, member) \
4860 { owner, pgn, 0, offsetof(type, member), 0 }
4862 #define DEFA(owner, pgn, type, member, max) \
4863 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
4865 #define DEFW(owner, pgn, type, member, max) \
4866 { owner, pgn, sizeof(type), offsetof(type, member), max }
4868 const cliResourceValue_t resourceTable[] = {
4869 #ifdef USE_BEEPER
4870 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
4871 #endif
4872 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
4873 #ifdef USE_SERVOS
4874 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
4875 #endif
4876 #if defined(USE_PPM)
4877 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
4878 #endif
4879 #if defined(USE_PWM)
4880 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
4881 #endif
4882 #ifdef USE_RANGEFINDER_HCSR04
4883 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
4884 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
4885 #endif
4886 #ifdef USE_LED_STRIP
4887 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
4888 #endif
4889 #ifdef USE_UART
4890 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
4891 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
4892 #endif
4893 #ifdef USE_INVERTER
4894 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
4895 #endif
4896 #ifdef USE_I2C
4897 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
4898 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
4899 #endif
4900 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
4901 #ifdef USE_SPEKTRUM_BIND
4902 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
4903 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
4904 #endif
4905 #ifdef USE_TRANSPONDER
4906 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
4907 #endif
4908 #ifdef USE_SPI
4909 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
4910 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
4911 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
4912 #endif
4913 #ifdef USE_ESCSERIAL
4914 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
4915 #endif
4916 #ifdef USE_CAMERA_CONTROL
4917 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
4918 #endif
4919 #ifdef USE_ADC
4920 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
4921 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
4922 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
4923 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
4924 #endif
4925 #ifdef USE_BARO
4926 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
4927 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
4928 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
4929 #endif
4930 #ifdef USE_MAG
4931 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
4932 #ifdef USE_MAG_DATA_READY_SIGNAL
4933 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
4934 #endif
4935 #endif
4936 #ifdef USE_SDCARD_SPI
4937 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
4938 #endif
4939 #ifdef USE_SDCARD
4940 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
4941 #endif
4942 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
4943 DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
4944 DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
4945 DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
4946 DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
4947 DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
4948 DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
4949 #endif
4950 #ifdef USE_PINIO
4951 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
4952 #endif
4953 #if defined(USE_USB_MSC)
4954 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
4955 #endif
4956 #ifdef USE_FLASH_CHIP
4957 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
4958 #endif
4959 #ifdef USE_MAX7456
4960 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
4961 #endif
4962 #ifdef USE_RX_SPI
4963 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
4964 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
4965 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
4966 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
4967 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
4968 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
4969 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
4970 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
4971 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
4972 #endif
4973 #endif
4974 #endif
4975 #ifdef USE_GYRO_EXTI
4976 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
4977 #endif
4978 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
4979 #ifdef USE_USB_DETECT
4980 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
4981 #endif
4982 #ifdef USE_VTX_RTC6705
4983 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
4984 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
4985 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
4986 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
4987 #endif
4988 #ifdef USE_PIN_PULL_UP_DOWN
4989 DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
4990 DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
4991 #endif
4994 #undef DEFS
4995 #undef DEFA
4996 #undef DEFW
4998 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
5000 const pgRegistry_t* rec = pgFind(value.pgn);
5001 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
5004 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
5006 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5007 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
5008 const char* owner = ownerNames[resourceTable[i].owner];
5009 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
5010 const void *currentConfig;
5011 const void *defaultConfig;
5012 if (isReadingConfigFromCopy()) {
5013 currentConfig = pg->copy;
5014 defaultConfig = pg->address;
5015 } else {
5016 currentConfig = pg->address;
5017 defaultConfig = NULL;
5020 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
5021 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5022 ioTag_t ioTagDefault = NULL;
5023 if (defaultConfig) {
5024 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5027 const bool equalsDefault = ioTag == ioTagDefault;
5028 const char *format = "resource %s %d %c%02d";
5029 const char *formatUnassigned = "resource %s %d NONE";
5030 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5031 if (ioTagDefault) {
5032 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
5033 } else if (defaultConfig) {
5034 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5036 if (ioTag) {
5037 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
5038 } else if (!(dumpMask & HIDE_UNUSED)) {
5039 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5045 static void printResourceOwner(uint8_t owner, uint8_t index)
5047 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
5049 if (resourceTable[owner].maxIndex > 0) {
5050 cliPrintf(" %d", RESOURCE_INDEX(index));
5054 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
5056 if (!newTag) {
5057 return;
5060 const char * format = "\r\nNOTE: %c%02d already assigned to ";
5061 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
5062 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
5063 ioTag_t *tag = getIoTag(resourceTable[r], i);
5064 if (*tag == newTag) {
5065 bool cleared = false;
5066 if (r == resourceIndex) {
5067 if (i == index) {
5068 continue;
5070 *tag = IO_TAG_NONE;
5071 cleared = true;
5074 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
5076 printResourceOwner(r, i);
5078 if (cleared) {
5079 cliPrintf(". ");
5080 printResourceOwner(r, i);
5081 cliPrintf(" disabled");
5084 cliPrintLine(".");
5090 static bool strToPin(char *pch, ioTag_t *tag)
5092 if (strcasecmp(pch, "NONE") == 0) {
5093 *tag = IO_TAG_NONE;
5094 return true;
5095 } else {
5096 unsigned pin = 0;
5097 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
5099 if (port < 8) {
5100 pch++;
5101 pin = atoi(pch);
5102 if (pin < 16) {
5103 *tag = DEFIO_TAG_MAKE(port, pin);
5104 return true;
5108 return false;
5111 #ifdef USE_DMA
5112 static void showDma(void)
5114 cliPrintLinefeed();
5116 #ifdef MINIMAL_CLI
5117 cliPrintLine("DMA:");
5118 #else
5119 cliPrintLine("Currently active DMA:");
5120 cliRepeat('-', 20);
5121 #endif
5122 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
5123 const resourceOwner_t *owner = dmaGetOwner(i);
5125 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
5126 if (owner->resourceIndex > 0) {
5127 cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
5128 } else {
5129 cliPrintLinef(" %s", ownerNames[owner->owner]);
5133 #endif
5135 #ifdef USE_DMA_SPEC
5137 typedef struct dmaoptEntry_s {
5138 char *device;
5139 dmaPeripheral_e peripheral;
5140 pgn_t pgn;
5141 uint8_t stride;
5142 uint8_t offset;
5143 uint8_t maxIndex;
5144 uint32_t presenceMask;
5145 } dmaoptEntry_t;
5147 #define MASK_IGNORED (0)
5149 // Handy macros for keeping the table tidy.
5150 // DEFS : Single entry
5151 // DEFA : Array of uint8_t (stride = 1)
5152 // DEFW : Wider stride case; array of structs.
5154 #define DEFS(device, peripheral, pgn, type, member) \
5155 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5157 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5158 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5160 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5161 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
5163 dmaoptEntry_t dmaoptEntryTable[] = {
5164 DEFW("SPI_TX", DMA_PERIPH_SPI_TX, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5165 DEFW("SPI_RX", DMA_PERIPH_SPI_RX, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5166 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
5167 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
5168 DEFW("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5169 DEFW("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5170 #if defined(STM32H7) || defined(STM32G4)
5171 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
5172 #endif
5175 #undef DEFS
5176 #undef DEFA
5177 #undef DEFW
5179 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5180 #define DMA_OPT_STRING_BUFSIZE 5
5182 #if defined(STM32H7) || defined(STM32G4)
5183 #define DMA_CHANREQ_STRING "Request"
5184 #else
5185 #define DMA_CHANREQ_STRING "Channel"
5186 #endif
5188 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
5189 #define DMA_STCH_STRING "Stream"
5190 #else
5191 #define DMA_STCH_STRING "Channel"
5192 #endif
5194 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5196 static void optToString(int optval, char *buf)
5198 if (optval == DMA_OPT_UNUSED) {
5199 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
5200 } else {
5201 tfp_sprintf(buf, "%d", optval);
5205 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5207 if (dmaopt != DMA_OPT_UNUSED) {
5208 printValue(dumpMask, equalsDefault,
5209 "dma %s %d %d",
5210 entry->device, DMA_OPT_UI_INDEX(index), dmaopt);
5212 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5213 dmaCode_t dmaCode = 0;
5214 if (dmaChannelSpec) {
5215 dmaCode = dmaChannelSpec->code;
5217 printValue(dumpMask, equalsDefault,
5218 "# %s %d: " DMASPEC_FORMAT_STRING,
5219 entry->device, DMA_OPT_UI_INDEX(index), DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5220 } else if (!(dumpMask & HIDE_UNUSED)) {
5221 printValue(dumpMask, equalsDefault,
5222 "dma %s %d NONE",
5223 entry->device, DMA_OPT_UI_INDEX(index));
5227 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5229 const pgRegistry_t* pg = pgFind(entry->pgn);
5230 const void *currentConfig;
5231 const void *defaultConfig;
5233 if (isReadingConfigFromCopy()) {
5234 currentConfig = pg->copy;
5235 defaultConfig = pg->address;
5236 } else {
5237 currentConfig = pg->address;
5238 defaultConfig = NULL;
5241 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5242 dmaoptValue_t defaultOpt;
5244 if (defaultConfig) {
5245 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5246 } else {
5247 defaultOpt = DMA_OPT_UNUSED;
5250 bool equalsDefault = currentOpt == defaultOpt;
5251 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5253 if (defaultConfig) {
5254 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5257 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5258 return headingStr;
5261 #if defined(USE_TIMER_MGMT)
5262 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5264 const char *format = "dma pin %c%02d %d";
5266 if (dmaopt != DMA_OPT_UNUSED) {
5267 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5268 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5269 dmaopt
5272 if (printDetails) {
5273 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5274 dmaCode_t dmaCode = 0;
5275 if (dmaChannelSpec) {
5276 dmaCode = dmaChannelSpec->code;
5277 printValue(dumpMask, false,
5278 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5279 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5280 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5284 } else if (!(dumpMask & HIDE_UNUSED)) {
5285 printValue(dumpMask, equalsDefault,
5286 "dma pin %c%02d NONE",
5287 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5292 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5294 const ioTag_t ioTag = currentConfig[index].ioTag;
5296 if (!ioTag) {
5297 return headingStr;
5300 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5301 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5303 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5304 bool equalsDefault = defaultDmaopt == dmaopt;
5305 if (defaultConfig) {
5306 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5307 if (defaultConfig[i].ioTag == ioTag) {
5308 defaultDmaopt = defaultConfig[i].dmaopt;
5310 // 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.
5311 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5313 tagsInUse[index] = true;
5315 break;
5320 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5322 if (defaultConfig) {
5323 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5326 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5327 return headingStr;
5329 #endif
5331 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5333 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5334 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5335 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5336 for (int index = 0; index < entry->maxIndex; index++) {
5337 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5341 #if defined(USE_TIMER_MGMT)
5342 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5343 const timerIOConfig_t *currentConfig;
5344 const timerIOConfig_t *defaultConfig;
5346 if (isReadingConfigFromCopy()) {
5347 currentConfig = (timerIOConfig_t *)pg->copy;
5348 defaultConfig = (timerIOConfig_t *)pg->address;
5349 } else {
5350 currentConfig = (timerIOConfig_t *)pg->address;
5351 defaultConfig = NULL;
5354 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5355 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5356 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5359 if (defaultConfig) {
5360 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5361 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5362 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5363 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5364 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5366 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5370 #endif
5373 static void cliDmaopt(char *cmdline)
5375 char *pch = NULL;
5376 char *saveptr;
5378 // Peripheral name or command option
5379 pch = strtok_r(cmdline, " ", &saveptr);
5380 if (!pch) {
5381 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5383 return;
5384 } else if (strcasecmp(pch, "list") == 0) {
5385 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5387 return;
5390 dmaoptEntry_t *entry = NULL;
5391 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5392 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5393 entry = &dmaoptEntryTable[i];
5397 if (!entry && strcasecmp(pch, "pin") != 0) {
5398 cliPrintErrorLinef("BAD DEVICE: %s", pch);
5399 return;
5402 // Index
5403 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5405 int index = 0;
5406 dmaoptValue_t *optaddr = NULL;
5408 ioTag_t ioTag = IO_TAG_NONE;
5409 #if defined(USE_TIMER_MGMT)
5410 timerIOConfig_t *timerIoConfig = NULL;
5411 #endif
5412 const timerHardware_t *timer = NULL;
5413 pch = strtok_r(NULL, " ", &saveptr);
5414 if (entry) {
5415 index = atoi(pch) - 1;
5416 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5417 cliPrintErrorLinef("BAD INDEX: '%s'", pch ? pch : "");
5418 return;
5421 const pgRegistry_t* pg = pgFind(entry->pgn);
5422 const void *currentConfig;
5423 if (isWritingConfigToCopy()) {
5424 currentConfig = pg->copy;
5425 } else {
5426 currentConfig = pg->address;
5428 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5429 orgval = *optaddr;
5430 } else {
5431 // It's a pin
5432 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5433 cliPrintErrorLinef("INVALID PIN: '%s'", pch ? pch : "");
5435 return;
5438 orgval = dmaoptByTag(ioTag);
5439 #if defined(USE_TIMER_MGMT)
5440 timerIoConfig = timerIoConfigByTag(ioTag);
5441 #endif
5442 timer = timerGetByTag(ioTag);
5445 // opt or list
5446 pch = strtok_r(NULL, " ", &saveptr);
5447 if (!pch) {
5448 if (entry) {
5449 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5451 #if defined(USE_TIMER_MGMT)
5452 else {
5453 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5455 #endif
5457 return;
5458 } else if (strcasecmp(pch, "list") == 0) {
5459 // Show possible opts
5460 const dmaChannelSpec_t *dmaChannelSpec;
5461 if (entry) {
5462 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5463 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5465 } else {
5466 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5467 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5471 return;
5472 } else if (pch) {
5473 int optval;
5474 if (strcasecmp(pch, "none") == 0) {
5475 optval = DMA_OPT_UNUSED;
5476 } else {
5477 optval = atoi(pch);
5479 if (entry) {
5480 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5481 cliPrintErrorLinef("INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5483 return;
5485 } else {
5486 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5487 cliPrintErrorLinef("INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5489 return;
5494 char optvalString[DMA_OPT_STRING_BUFSIZE];
5495 optToString(optval, optvalString);
5497 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5498 optToString(orgval, orgvalString);
5500 if (optval != orgval) {
5501 if (entry) {
5502 *optaddr = optval;
5504 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5505 } else {
5506 #if defined(USE_TIMER_MGMT)
5507 timerIoConfig->dmaopt = optval;
5508 #endif
5510 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5512 } else {
5513 if (entry) {
5514 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5515 } else {
5516 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5521 #endif // USE_DMA_SPEC
5523 #ifdef USE_DMA
5524 static void cliDma(char* cmdline)
5526 int len = strlen(cmdline);
5527 if (len && strncasecmp(cmdline, "show", len) == 0) {
5528 showDma();
5530 return;
5533 #if defined(USE_DMA_SPEC)
5534 cliDmaopt(cmdline);
5535 #else
5536 cliShowParseError();
5537 #endif
5539 #endif
5540 #endif // USE_RESOURCE_MGMT
5542 #ifdef USE_TIMER_MGMT
5543 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5545 const char *format = "timer %c%02d AF%d";
5546 const char *emptyFormat = "timer %c%02d NONE";
5548 if (timerIndex > 0) {
5549 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5550 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5551 IO_GPIOPortIdxByTag(ioTag) + 'A',
5552 IO_GPIOPinIdxByTag(ioTag),
5553 timer->alternateFunction
5555 if (printDetails) {
5556 printValue(dumpMask, false,
5557 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5558 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5559 timerGetTIMNumber(timer->tim),
5560 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5561 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5562 timer->alternateFunction
5565 } else {
5566 printValue(dumpMask, equalsDefault, emptyFormat,
5567 IO_GPIOPortIdxByTag(ioTag) + 'A',
5568 IO_GPIOPinIdxByTag(ioTag)
5573 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5575 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5576 const timerIOConfig_t *currentConfig;
5577 const timerIOConfig_t *defaultConfig;
5579 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5580 if (isReadingConfigFromCopy()) {
5581 currentConfig = (timerIOConfig_t *)pg->copy;
5582 defaultConfig = (timerIOConfig_t *)pg->address;
5583 } else {
5584 currentConfig = (timerIOConfig_t *)pg->address;
5585 defaultConfig = NULL;
5588 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5589 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5590 const ioTag_t ioTag = currentConfig[i].ioTag;
5592 if (!ioTag) {
5593 continue;
5596 const uint8_t timerIndex = currentConfig[i].index;
5598 uint8_t defaultTimerIndex = 0;
5599 if (defaultConfig) {
5600 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5601 if (defaultConfig[i].ioTag == ioTag) {
5602 defaultTimerIndex = defaultConfig[i].index;
5603 tagsInUse[i] = true;
5605 break;
5610 const bool equalsDefault = defaultTimerIndex == timerIndex;
5611 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5612 if (defaultConfig && defaultTimerIndex) {
5613 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5616 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5619 if (defaultConfig) {
5620 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5621 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5622 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5623 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5625 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5631 #define TIMER_INDEX_UNDEFINED -1
5632 #define TIMER_AF_STRING_BUFSIZE 5
5634 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5636 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5637 if (!timer) {
5638 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5639 } else {
5640 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5644 static void showTimers(void)
5646 cliPrintLinefeed();
5648 #ifdef MINIMAL_CLI
5649 cliPrintLine("Timers:");
5650 #else
5651 cliPrintLine("Currently active Timers:");
5652 cliRepeat('-', 23);
5653 #endif
5655 int8_t timerNumber;
5656 for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
5657 cliPrintf("TIM%d:", timerNumber);
5658 bool timerUsed = false;
5659 for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
5660 const resourceOwner_t *timerOwner = timerGetOwner(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
5661 if (timerOwner->owner) {
5662 if (!timerUsed) {
5663 timerUsed = true;
5665 cliPrintLinefeed();
5668 if (timerOwner->resourceIndex > 0) {
5669 cliPrintLinef(" CH%d: %s %d", timerIndex + 1, ownerNames[timerOwner->owner], timerOwner->resourceIndex);
5670 } else {
5671 cliPrintLinef(" CH%d: %s", timerIndex + 1, ownerNames[timerOwner->owner]);
5676 if (!timerUsed) {
5677 cliPrintLine(" FREE");
5682 static void cliTimer(char *cmdline)
5684 int len = strlen(cmdline);
5686 if (len == 0) {
5687 printTimer(DUMP_MASTER, NULL);
5689 return;
5690 } else if (strncasecmp(cmdline, "list", len) == 0) {
5691 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5693 return;
5694 } else if (strncasecmp(cmdline, "show", len) == 0) {
5695 showTimers();
5697 return;
5700 char *pch = NULL;
5701 char *saveptr;
5703 ioTag_t ioTag = IO_TAG_NONE;
5704 pch = strtok_r(cmdline, " ", &saveptr);
5705 if (!pch || !strToPin(pch, &ioTag)) {
5706 cliShowParseError();
5708 return;
5709 } else if (!IOGetByTag(ioTag)) {
5710 cliPrintErrorLinef("PIN NOT USED ON BOARD.");
5712 return;
5715 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5716 bool isExistingTimerOpt = false;
5717 /* find existing entry, or go for next available */
5718 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5719 if (timerIOConfig(i)->ioTag == ioTag) {
5720 timerIOIndex = i;
5721 isExistingTimerOpt = true;
5723 break;
5726 /* first available empty slot */
5727 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5728 timerIOIndex = i;
5732 if (timerIOIndex < 0) {
5733 cliPrintErrorLinef("PIN TIMER MAP FULL.");
5735 return;
5738 pch = strtok_r(NULL, " ", &saveptr);
5739 if (pch) {
5740 int timerIndex = TIMER_INDEX_UNDEFINED;
5741 if (strcasecmp(pch, "list") == 0) {
5742 /* output the list of available options */
5743 const timerHardware_t *timer;
5744 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5745 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5746 timer->alternateFunction,
5747 timerGetTIMNumber(timer->tim),
5748 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5749 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
5753 return;
5754 } else if (strncasecmp(pch, "af", 2) == 0) {
5755 unsigned alternateFunction = atoi(&pch[2]);
5757 const timerHardware_t *timer;
5758 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5759 if (timer->alternateFunction == alternateFunction) {
5760 timerIndex = index;
5762 break;
5766 if (!timer) {
5767 cliPrintErrorLinef("INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5769 return;
5771 } else if (strcasecmp(pch, "none") != 0) {
5772 cliPrintErrorLinef("INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5774 return;
5777 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
5778 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
5779 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
5780 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
5782 char optvalString[DMA_OPT_STRING_BUFSIZE];
5783 alternateFunctionToString(ioTag, timerIndex, optvalString);
5785 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5786 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
5788 if (timerIndex == oldTimerIndex) {
5789 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
5790 } else {
5791 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5794 return;
5795 } else {
5796 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
5798 return;
5801 #endif
5803 #if defined(USE_RESOURCE_MGMT)
5804 static void cliResource(char *cmdline)
5806 char *pch = NULL;
5807 char *saveptr;
5809 pch = strtok_r(cmdline, " ", &saveptr);
5810 if (!pch) {
5811 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
5813 return;
5814 } else if (strcasecmp(pch, "show") == 0) {
5815 #ifdef MINIMAL_CLI
5816 cliPrintLine("IO");
5817 #else
5818 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
5819 cliRepeat('-', 20);
5820 #endif
5821 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
5822 const char* owner;
5823 owner = ownerNames[ioRecs[i].owner];
5825 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
5826 if (ioRecs[i].index > 0) {
5827 cliPrintf(" %d", ioRecs[i].index);
5829 cliPrintLinefeed();
5832 pch = strtok_r(NULL, " ", &saveptr);
5833 if (strcasecmp(pch, "all") == 0) {
5834 #if defined(USE_TIMER_MGMT)
5835 cliTimer("show");
5836 #endif
5837 #if defined(USE_DMA)
5838 cliDma("show");
5839 #endif
5842 return;
5845 unsigned resourceIndex = 0;
5846 for (; ; resourceIndex++) {
5847 if (resourceIndex >= ARRAYLEN(resourceTable)) {
5848 cliPrintErrorLinef("INVALID RESOURCE NAME: '%s'", pch);
5849 return;
5852 const char *resourceName = ownerNames[resourceTable[resourceIndex].owner];
5853 if (strncasecmp(pch, resourceName, strlen(resourceName)) == 0) {
5854 break;
5858 pch = strtok_r(NULL, " ", &saveptr);
5859 int index = atoi(pch);
5861 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
5862 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
5863 cliShowArgumentRangeError("INDEX", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
5864 return;
5866 index -= 1;
5868 pch = strtok_r(NULL, " ", &saveptr);
5871 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
5873 if (strlen(pch) > 0) {
5874 if (strToPin(pch, tag)) {
5875 if (*tag == IO_TAG_NONE) {
5876 #ifdef MINIMAL_CLI
5877 cliPrintLine("Freed");
5878 #else
5879 cliPrintLine("Resource is freed");
5880 #endif
5881 return;
5882 } else {
5883 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
5884 if (rec) {
5885 resourceCheck(resourceIndex, index, *tag);
5886 #ifdef MINIMAL_CLI
5887 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5888 #else
5889 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5890 #endif
5891 } else {
5892 cliShowParseError();
5894 return;
5899 cliShowParseError();
5901 #endif
5903 #ifdef USE_DSHOT_TELEMETRY
5904 static void cliDshotTelemetryInfo(char *cmdline)
5906 UNUSED(cmdline);
5908 if (useDshotTelemetry) {
5909 cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
5910 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
5911 uint32_t directionChangeCycles = dshotDMAHandlerCycleCounters.changeDirectionCompletedAt - dshotDMAHandlerCycleCounters.irqAt;
5912 uint32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
5913 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
5914 cliPrintLinefeed();
5916 #ifdef USE_DSHOT_TELEMETRY_STATS
5917 cliPrintLine("Motor eRPM RPM Hz Invalid");
5918 cliPrintLine("===== ======= ====== ===== =======");
5919 #else
5920 cliPrintLine("Motor eRPM RPM Hz");
5921 cliPrintLine("===== ======= ====== =====");
5922 #endif
5923 for (uint8_t i = 0; i < getMotorCount(); i++) {
5924 cliPrintf("%5d %7d %6d %5d ", i,
5925 (int)getDshotTelemetry(i) * 100,
5926 (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount,
5927 (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount / 60);
5928 #ifdef USE_DSHOT_TELEMETRY_STATS
5929 if (isDshotMotorTelemetryActive(i)) {
5930 const int calcPercent = getDshotTelemetryMotorInvalidPercent(i);
5931 cliPrintLinef("%3d.%02d%%", calcPercent / 100, calcPercent % 100);
5932 } else {
5933 cliPrintLine("NO DATA");
5935 #else
5936 cliPrintLinefeed();
5937 #endif
5939 cliPrintLinefeed();
5941 const int len = MAX_GCR_EDGES;
5942 #ifdef DEBUG_BBDECODE
5943 extern uint16_t bbBuffer[134];
5944 for (int i = 0; i < 134; i++) {
5945 cliPrintf("%u ", (int)bbBuffer[i]);
5947 cliPrintLinefeed();
5948 #endif
5949 for (int i = 0; i < len; i++) {
5950 cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
5952 cliPrintLinefeed();
5953 for (int i = 1; i < len; i++) {
5954 cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
5956 cliPrintLinefeed();
5957 } else {
5958 cliPrintLine("Dshot telemetry not enabled");
5961 #endif
5963 static void printConfig(char *cmdline, bool doDiff)
5965 dumpFlags_t dumpMask = DUMP_MASTER;
5966 char *options;
5967 if ((options = checkCommand(cmdline, "master"))) {
5968 dumpMask = DUMP_MASTER; // only
5969 } else if ((options = checkCommand(cmdline, "profile"))) {
5970 dumpMask = DUMP_PROFILE; // only
5971 } else if ((options = checkCommand(cmdline, "rates"))) {
5972 dumpMask = DUMP_RATES; // only
5973 } else if ((options = checkCommand(cmdline, "hardware"))) {
5974 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
5975 } else if ((options = checkCommand(cmdline, "all"))) {
5976 dumpMask = DUMP_ALL; // all profiles and rates
5977 } else {
5978 options = cmdline;
5981 if (doDiff) {
5982 dumpMask = dumpMask | DO_DIFF;
5985 if (checkCommand(options, "defaults")) {
5986 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
5987 } else if (checkCommand(options, "bare")) {
5988 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
5991 backupAndResetConfigs((dumpMask & BARE) == 0);
5993 #ifdef USE_CLI_BATCH
5994 bool batchModeEnabled = false;
5995 #endif
5996 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
5997 cliPrintHashLine("version");
5998 cliVersion(NULL);
6000 if (!(dumpMask & BARE)) {
6001 #ifdef USE_CLI_BATCH
6002 cliPrintHashLine("start the command batch");
6003 cliPrintLine("batch start");
6004 batchModeEnabled = true;
6005 #endif
6007 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
6008 cliPrintHashLine("reset configuration to default settings");
6009 cliPrintLine("defaults nosave");
6013 #if defined(USE_BOARD_INFO)
6014 cliPrintLinefeed();
6015 printBoardName(dumpMask);
6016 printManufacturerId(dumpMask);
6017 #endif
6019 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
6020 cliMcuId(NULL);
6021 #if defined(USE_SIGNATURE)
6022 cliSignature("");
6023 #endif
6026 if (!(dumpMask & HARDWARE_ONLY)) {
6027 printName(dumpMask, &pilotConfig_Copy);
6030 #ifdef USE_RESOURCE_MGMT
6031 printResource(dumpMask, "resources");
6032 #if defined(USE_TIMER_MGMT)
6033 printTimer(dumpMask, "timer");
6034 #endif
6035 #ifdef USE_DMA_SPEC
6036 printDmaopt(dumpMask, "dma");
6037 #endif
6038 #endif
6040 if (!(dumpMask & HARDWARE_ONLY)) {
6041 #ifndef USE_QUAD_MIXER_ONLY
6042 const char *mixerHeadingStr = "mixer";
6043 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
6044 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
6045 const char *formatMixer = "mixer %s";
6046 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
6047 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
6049 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
6051 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
6053 #ifdef USE_SERVOS
6054 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
6056 const char *servoMixHeadingStr = "servo mixer";
6057 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
6058 cliPrintHashLine(servoMixHeadingStr);
6059 cliPrintLine("smix reset\r\n");
6060 servoMixHeadingStr = NULL;
6062 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
6063 #endif
6064 #endif
6066 printFeature(dumpMask, featureConfig_Copy.enabledFeatures, featureConfig()->enabledFeatures, "feature");
6068 #if defined(USE_BEEPER)
6069 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
6071 #if defined(USE_DSHOT)
6072 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
6073 #endif
6074 #endif // USE_BEEPER
6076 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
6078 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
6080 #ifdef USE_LED_STRIP_STATUS_MODE
6081 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
6083 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
6085 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
6086 #endif
6088 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
6090 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
6092 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
6094 #ifdef USE_VTX_TABLE
6095 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
6096 #endif
6098 #ifdef USE_VTX_CONTROL
6099 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
6100 #endif
6102 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
6105 if (dumpMask & HARDWARE_ONLY) {
6106 dumpAllValues(HARDWARE_VALUE, dumpMask, "master");
6107 } else {
6108 dumpAllValues(MASTER_VALUE, dumpMask, "master");
6110 if (dumpMask & DUMP_ALL) {
6111 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
6112 cliDumpPidProfile(pidProfileIndex, dumpMask);
6115 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
6117 if (!(dumpMask & BARE)) {
6118 cliPrintHashLine("restore original profile selection");
6120 cliProfile("");
6123 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
6125 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
6126 cliDumpRateProfile(rateIndex, dumpMask);
6129 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
6131 if (!(dumpMask & BARE)) {
6132 cliPrintHashLine("restore original rateprofile selection");
6134 cliRateProfile("");
6136 cliPrintHashLine("save configuration");
6137 cliPrint("save");
6138 #ifdef USE_CLI_BATCH
6139 batchModeEnabled = false;
6140 #endif
6143 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
6144 } else {
6145 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
6147 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
6150 } else if (dumpMask & DUMP_PROFILE) {
6151 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
6152 } else if (dumpMask & DUMP_RATES) {
6153 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
6156 #ifdef USE_CLI_BATCH
6157 if (batchModeEnabled) {
6158 cliPrintHashLine("end the command batch");
6159 cliPrintLine("batch end");
6161 #endif
6163 // restore configs from copies
6164 restoreConfigs();
6167 static void cliDump(char *cmdline)
6169 printConfig(cmdline, false);
6172 static void cliDiff(char *cmdline)
6174 printConfig(cmdline, true);
6177 #if defined(USE_USB_MSC)
6178 static void cliMsc(char *cmdline)
6180 if (mscCheckFilesystemReady()) {
6181 #ifdef USE_RTC_TIME
6182 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
6183 if (!isEmpty(cmdline)) {
6184 timezoneOffsetMinutes = atoi(cmdline);
6185 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
6186 cliPrintErrorLinef("INVALID TIMEZONE OFFSET");
6187 return;
6190 #else
6191 int timezoneOffsetMinutes = 0;
6192 UNUSED(cmdline);
6193 #endif
6194 cliPrintHashLine("Restarting in mass storage mode");
6195 cliPrint("\r\nRebooting");
6196 cliWriterFlush();
6197 waitForSerialPortToFinishTransmitting(cliPort);
6198 motorShutdown();
6200 systemResetToMsc(timezoneOffsetMinutes);
6201 } else {
6202 cliPrintHashLine("Storage not present or failed to initialize!");
6205 #endif
6208 typedef struct {
6209 const char *name;
6210 #ifndef MINIMAL_CLI
6211 const char *description;
6212 const char *args;
6213 #endif
6214 void (*func)(char *cmdline);
6215 } clicmd_t;
6217 #ifndef MINIMAL_CLI
6218 #define CLI_COMMAND_DEF(name, description, args, method) \
6220 name , \
6221 description , \
6222 args , \
6223 method \
6225 #else
6226 #define CLI_COMMAND_DEF(name, description, args, method) \
6228 name, \
6229 method \
6231 #endif
6233 static void cliHelp(char *cmdline);
6235 // should be sorted a..z for bsearch()
6236 const clicmd_t cmdTable[] = {
6237 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
6238 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
6239 #ifdef USE_CLI_BATCH
6240 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
6241 #endif
6242 #if defined(USE_BEEPER)
6243 #if defined(USE_DSHOT)
6244 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6245 "\t<->[name]", cliBeacon),
6246 #endif
6247 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6248 "\t<->[name]", cliBeeper),
6249 #endif // USE_BEEPER
6250 #if defined(USE_RX_BIND)
6251 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
6252 #endif
6253 #if defined(USE_FLASH_BOOT_LOADER)
6254 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6255 #else
6256 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6257 #endif
6258 #if defined(USE_BOARD_INFO)
6259 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6260 #endif
6261 #ifdef USE_LED_STRIP_STATUS_MODE
6262 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6263 #endif
6264 #if defined(USE_CUSTOM_DEFAULTS)
6265 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave|bare|show]", cliDefaults),
6266 #else
6267 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave|show]", cliDefaults),
6268 #endif
6269 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6270 #ifdef USE_RESOURCE_MGMT
6272 #ifdef USE_DMA
6273 #ifdef USE_DMA_SPEC
6274 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6275 #else
6276 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6277 #endif
6278 #endif
6280 #endif
6281 #ifdef USE_DSHOT_TELEMETRY
6282 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6283 #endif
6284 #ifdef USE_DSHOT
6285 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6286 #endif
6287 CLI_COMMAND_DEF("dump", "dump configuration",
6288 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6289 #ifdef USE_ESCSERIAL
6290 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6291 #endif
6292 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
6293 CLI_COMMAND_DEF("feature", "configure features",
6294 "list\r\n"
6295 "\t<->[name]", cliFeature),
6296 #ifdef USE_FLASHFS
6297 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6298 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6299 #ifdef USE_FLASH_TOOLS
6300 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6301 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6302 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6303 #endif
6304 #endif
6305 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6306 #ifdef USE_GPS
6307 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6308 #endif
6309 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6310 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6311 #endif
6312 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp),
6313 #ifdef USE_LED_STRIP_STATUS_MODE
6314 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6315 #endif
6316 #if defined(USE_BOARD_INFO)
6317 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6318 #endif
6319 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6320 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6321 #ifndef USE_QUAD_MIXER_ONLY
6322 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6323 #endif
6324 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6325 #ifdef USE_LED_STRIP_STATUS_MODE
6326 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6327 #endif
6328 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6329 #ifdef USE_USB_MSC
6330 #ifdef USE_RTC_TIME
6331 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6332 #else
6333 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6334 #endif
6335 #endif
6336 #ifndef MINIMAL_CLI
6337 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6338 #endif
6339 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6340 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6341 #ifdef USE_RC_SMOOTHING_FILTER
6342 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6343 #endif // USE_RC_SMOOTHING_FILTER
6344 #ifdef USE_RESOURCE_MGMT
6345 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6346 #endif
6347 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6348 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6349 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
6350 #ifdef USE_SDCARD
6351 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6352 #endif
6353 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6354 #if defined(USE_SERIAL_PASSTHROUGH)
6355 #if defined(USE_PINIO)
6356 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),
6357 #else
6358 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6359 #endif
6360 #endif
6361 #ifdef USE_SERVOS
6362 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6363 #endif
6364 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6365 #if defined(USE_SIGNATURE)
6366 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6367 #endif
6368 #ifdef USE_SERVOS
6369 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6370 "\treset\r\n"
6371 "\tload <mixer>\r\n"
6372 "\treverse <servo> <source> r|n", cliServoMix),
6373 #endif
6374 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6375 #if defined(USE_TASK_STATISTICS)
6376 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6377 #endif
6378 #ifdef USE_TIMER_MGMT
6379 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6380 #endif
6381 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6382 #ifdef USE_VTX_CONTROL
6383 #ifdef MINIMAL_CLI
6384 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6385 #else
6386 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6387 #endif
6388 #endif
6389 #ifdef USE_VTX_TABLE
6390 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL, cliVtxInfo),
6391 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6392 #endif
6395 static void cliHelp(char *cmdline)
6397 bool anyMatches = false;
6399 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6400 bool printEntry = false;
6401 if (isEmpty(cmdline)) {
6402 printEntry = true;
6403 } else {
6404 if (strcasestr(cmdTable[i].name, cmdline)
6405 #ifndef MINIMAL_CLI
6406 || strcasestr(cmdTable[i].description, cmdline)
6407 #endif
6409 printEntry = true;
6413 if (printEntry) {
6414 anyMatches = true;
6415 cliPrint(cmdTable[i].name);
6416 #ifndef MINIMAL_CLI
6417 if (cmdTable[i].description) {
6418 cliPrintf(" - %s", cmdTable[i].description);
6420 if (cmdTable[i].args) {
6421 cliPrintf("\r\n\t%s", cmdTable[i].args);
6423 #endif
6424 cliPrintLinefeed();
6427 if (!isEmpty(cmdline) && !anyMatches) {
6428 cliPrintErrorLinef("NO MATCHES FOR '%s'", cmdline);
6432 static void processCharacter(const char c)
6434 if (bufferIndex && (c == '\n' || c == '\r')) {
6435 // enter pressed
6436 cliPrintLinefeed();
6438 #if defined(USE_CUSTOM_DEFAULTS) && defined(DEBUG_CUSTOM_DEFAULTS)
6439 if (processingCustomDefaults) {
6440 cliPrint("d: ");
6442 #endif
6444 // Strip comment starting with # from line
6445 char *p = cliBuffer;
6446 p = strchr(p, '#');
6447 if (NULL != p) {
6448 bufferIndex = (uint32_t)(p - cliBuffer);
6451 // Strip trailing whitespace
6452 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6453 bufferIndex--;
6456 // Process non-empty lines
6457 if (bufferIndex > 0) {
6458 cliBuffer[bufferIndex] = 0; // null terminate
6460 const clicmd_t *cmd;
6461 char *options;
6462 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6463 if ((options = checkCommand(cliBuffer, cmd->name))) {
6464 break;
6467 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6468 cmd->func(options);
6469 } else {
6470 cliPrintError("UNKNOWN COMMAND, TRY 'HELP'");
6472 bufferIndex = 0;
6475 memset(cliBuffer, 0, sizeof(cliBuffer));
6477 // 'exit' will reset this flag, so we don't need to print prompt again
6478 if (!cliMode) {
6479 return;
6482 cliPrompt();
6483 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6484 if (!bufferIndex && c == ' ')
6485 return; // Ignore leading spaces
6486 cliBuffer[bufferIndex++] = c;
6487 cliWrite(c);
6491 static void processCharacterInteractive(const char c)
6493 if (c == '\t' || c == '?') {
6494 // do tab completion
6495 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6496 uint32_t i = bufferIndex;
6497 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6498 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) {
6499 continue;
6501 if (!pstart) {
6502 pstart = cmd;
6504 pend = cmd;
6506 if (pstart) { /* Buffer matches one or more commands */
6507 for (; ; bufferIndex++) {
6508 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6509 break;
6510 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6511 /* Unambiguous -- append a space */
6512 cliBuffer[bufferIndex++] = ' ';
6513 cliBuffer[bufferIndex] = '\0';
6514 break;
6516 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6519 if (!bufferIndex || pstart != pend) {
6520 /* Print list of ambiguous matches */
6521 cliPrint("\r\033[K");
6522 for (cmd = pstart; cmd <= pend; cmd++) {
6523 cliPrint(cmd->name);
6524 cliWrite('\t');
6526 cliPrompt();
6527 i = 0; /* Redraw prompt */
6529 for (; i < bufferIndex; i++)
6530 cliWrite(cliBuffer[i]);
6531 } else if (!bufferIndex && c == 4) { // CTRL-D
6532 cliExit(cliBuffer);
6533 return;
6534 } else if (c == 12) { // NewPage / CTRL-L
6535 // clear screen
6536 cliPrint("\033[2J\033[1;1H");
6537 cliPrompt();
6538 } else if (c == 127) {
6539 // backspace
6540 if (bufferIndex) {
6541 cliBuffer[--bufferIndex] = 0;
6542 cliPrint("\010 \010");
6544 } else {
6545 processCharacter(c);
6549 void cliProcess(void)
6551 if (!cliWriter) {
6552 return;
6555 // Flush the buffer to get rid of any MSP data polls sent by configurator after CLI was invoked
6556 cliWriterFlush();
6558 while (serialRxBytesWaiting(cliPort)) {
6559 uint8_t c = serialRead(cliPort);
6561 processCharacterInteractive(c);
6565 #if defined(USE_CUSTOM_DEFAULTS)
6566 static bool cliProcessCustomDefaults(bool quiet)
6568 char *customDefaultsPtr = customDefaultsStart;
6569 if (processingCustomDefaults || !isCustomDefaults(customDefaultsPtr)) {
6570 return false;
6573 bufWriter_t *cliWriterTemp = NULL;
6574 if (quiet
6575 #if !defined(DEBUG_CUSTOM_DEFAULTS)
6576 || true
6577 #endif
6579 cliWriterTemp = cliWriter;
6580 cliWriter = NULL;
6582 if (quiet) {
6583 cliErrorWriter = NULL;
6586 memcpy(cliBufferTemp, cliBuffer, sizeof(cliBuffer));
6587 uint32_t bufferIndexTemp = bufferIndex;
6588 bufferIndex = 0;
6589 processingCustomDefaults = true;
6591 while (*customDefaultsPtr && *customDefaultsPtr != 0xFF && customDefaultsPtr < customDefaultsEnd) {
6592 processCharacter(*customDefaultsPtr++);
6595 // Process a newline at the very end so that the last command gets executed,
6596 // even when the file did not contain a trailing newline
6597 processCharacter('\r');
6599 processingCustomDefaults = false;
6601 if (cliWriterTemp) {
6602 cliWriter = cliWriterTemp;
6603 cliErrorWriter = cliWriter;
6606 memcpy(cliBuffer, cliBufferTemp, sizeof(cliBuffer));
6607 bufferIndex = bufferIndexTemp;
6609 systemConfigMutable()->configurationState = CONFIGURATION_STATE_DEFAULTS_CUSTOM;
6611 return true;
6613 #endif
6615 void cliEnter(serialPort_t *serialPort)
6617 cliMode = true;
6618 cliPort = serialPort;
6619 setPrintfSerialPort(cliPort);
6620 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6621 cliErrorWriter = cliWriter;
6623 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
6625 #ifndef MINIMAL_CLI
6626 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6627 #else
6628 cliPrintLine("\r\nCLI");
6629 #endif
6630 setArmingDisabled(ARMING_DISABLED_CLI);
6632 cliPrompt();
6634 #ifdef USE_CLI_BATCH
6635 resetCommandBatch();
6636 #endif
6639 #endif // USE_CLI