Refactor Feedforward Angle and RC Smoothing - mashup of 12578 and 12594 (#12605)
[betaflight.git] / src / main / cli / cli.c
blob5de1cf1cd9f18fda9f58b2652a3b19193e3b53da
1 /*
2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
8 * any later version.
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <math.h>
27 #include <ctype.h>
29 #include "platform.h"
31 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
32 // signal that we're in cli mode
33 bool cliMode = false;
35 #ifdef USE_CLI
37 #include "blackbox/blackbox.h"
39 #include "build/build_config.h"
40 #include "build/debug.h"
41 #include "build/version.h"
43 #include "cli/settings.h"
45 #include "cms/cms.h"
47 #include "common/axis.h"
48 #include "common/color.h"
49 #include "common/maths.h"
50 #include "common/printf.h"
51 #include "common/printf_serial.h"
52 #include "common/strtol.h"
53 #include "common/time.h"
54 #include "common/typeconversion.h"
55 #include "common/utils.h"
57 #include "config/config.h"
58 #include "config/config_eeprom.h"
59 #include "config/feature.h"
60 #include "config/simplified_tuning.h"
62 #include "drivers/accgyro/accgyro.h"
63 #include "drivers/adc.h"
64 #include "drivers/buf_writer.h"
65 #include "drivers/bus_i2c.h"
66 #include "drivers/bus_spi.h"
67 #include "drivers/dma.h"
68 #include "drivers/dma_reqmap.h"
69 #include "drivers/dshot.h"
70 #include "drivers/dshot_command.h"
71 #include "drivers/dshot_dpwm.h"
72 #include "drivers/pwm_output_dshot_shared.h"
73 #include "drivers/camera_control.h"
74 #include "drivers/compass/compass.h"
75 #include "drivers/display.h"
76 #include "drivers/dma.h"
77 #include "drivers/flash.h"
78 #include "drivers/inverter.h"
79 #include "drivers/io.h"
80 #include "drivers/io_impl.h"
81 #include "drivers/light_led.h"
82 #include "drivers/motor.h"
83 #include "drivers/rangefinder/rangefinder_hcsr04.h"
84 #include "drivers/resource.h"
85 #include "drivers/sdcard.h"
86 #include "drivers/sensor.h"
87 #include "drivers/serial.h"
88 #include "drivers/serial_escserial.h"
89 #include "drivers/sound_beeper.h"
90 #include "drivers/stack_check.h"
91 #include "drivers/system.h"
92 #include "drivers/time.h"
93 #include "drivers/timer.h"
94 #include "drivers/transponder_ir.h"
95 #include "drivers/usb_msc.h"
96 #include "drivers/vtx_common.h"
97 #include "drivers/vtx_table.h"
99 #include "fc/board_info.h"
100 #include "fc/controlrate_profile.h"
101 #include "fc/core.h"
102 #include "fc/rc.h"
103 #include "fc/rc_adjustments.h"
104 #include "fc/rc_controls.h"
105 #include "fc/runtime_config.h"
107 #include "flight/failsafe.h"
108 #include "flight/imu.h"
109 #include "flight/mixer.h"
110 #include "flight/pid.h"
111 #include "flight/position.h"
112 #include "flight/servos.h"
114 #include "io/asyncfatfs/asyncfatfs.h"
115 #include "io/beeper.h"
116 #include "io/flashfs.h"
117 #include "io/gimbal.h"
118 #include "io/gps.h"
119 #include "io/ledstrip.h"
120 #include "io/serial.h"
121 #include "io/transponder_ir.h"
122 #include "io/usb_msc.h"
123 #include "io/vtx_control.h"
124 #include "io/vtx.h"
126 #include "msp/msp.h"
127 #include "msp/msp_box.h"
128 #include "msp/msp_protocol.h"
130 #include "osd/osd.h"
132 #include "pg/adc.h"
133 #include "pg/beeper.h"
134 #include "pg/beeper_dev.h"
135 #include "pg/board.h"
136 #include "pg/bus_i2c.h"
137 #include "pg/bus_spi.h"
138 #include "pg/gyrodev.h"
139 #include "pg/max7456.h"
140 #include "pg/mco.h"
141 #include "pg/motor.h"
142 #include "pg/pinio.h"
143 #include "pg/pin_pull_up_down.h"
144 #include "pg/pg.h"
145 #include "pg/pg_ids.h"
146 #include "pg/rx.h"
147 #include "pg/rx_pwm.h"
148 #include "pg/rx_spi_cc2500.h"
149 #include "pg/rx_spi_expresslrs.h"
150 #include "pg/serial_uart.h"
151 #include "pg/sdio.h"
152 #include "pg/timerio.h"
153 #include "pg/timerup.h"
154 #include "pg/usb.h"
155 #include "pg/vtx_table.h"
157 #include "rx/rx_bind.h"
158 #include "rx/rx_spi.h"
160 #include "scheduler/scheduler.h"
162 #include "sensors/acceleration.h"
163 #include "sensors/adcinternal.h"
164 #include "sensors/barometer.h"
165 #include "sensors/battery.h"
166 #include "sensors/boardalignment.h"
167 #include "sensors/compass.h"
168 #include "sensors/gyro.h"
169 #include "sensors/gyro_init.h"
170 #include "sensors/sensors.h"
172 #include "telemetry/frsky_hub.h"
173 #include "telemetry/telemetry.h"
175 #include "cli.h"
177 static serialPort_t *cliPort = NULL;
179 // Space required to set array parameters
180 #define CLI_IN_BUFFER_SIZE 256
181 #define CLI_OUT_BUFFER_SIZE 64
183 static bufWriter_t cliWriterDesc;
184 static bufWriter_t *cliWriter = NULL;
185 static bufWriter_t *cliErrorWriter = NULL;
186 static uint8_t cliWriteBuffer[CLI_OUT_BUFFER_SIZE];
188 static char cliBuffer[CLI_IN_BUFFER_SIZE];
189 static uint32_t bufferIndex = 0;
191 static bool configIsInCopy = false;
193 #define CURRENT_PROFILE_INDEX -1
194 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
195 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
197 #ifdef USE_CLI_BATCH
198 static bool commandBatchActive = false;
199 static bool commandBatchError = false;
200 #endif
202 #if defined(USE_BOARD_INFO)
203 static bool boardInformationUpdated = false;
204 #if defined(USE_SIGNATURE)
205 static bool signatureUpdated = false;
206 #endif
207 #endif // USE_BOARD_INFO
209 static const char* const emptyName = "-";
210 static const char* const emptyString = "";
212 #define MAX_CHANGESET_ID_LENGTH 8
213 #define MAX_DATE_LENGTH 20
215 #define ERROR_INVALID_NAME "INVALID NAME: %s"
216 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
218 #ifndef USE_QUAD_MIXER_ONLY
219 // sync this with mixerMode_e
220 static const char * const mixerNames[] = {
221 "TRI", "QUADP", "QUADX", "BI",
222 "GIMBAL", "Y6", "HEX6",
223 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
224 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
225 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
226 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
228 #endif
230 // sync this with features_e
231 static const char * const featureNames[] = {
232 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
233 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
234 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
235 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
236 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
237 "", "", "RX_SPI", "", "ESC_SENSOR", "ANTI_GRAVITY", "", NULL
240 // sync this with rxFailsafeChannelMode_e
241 static const char rxFailsafeModeCharacters[] = "ahs";
243 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
244 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
245 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
248 #if defined(USE_SENSOR_NAMES)
249 // sync this with sensors_e
250 static const char *const sensorTypeNames[] = {
251 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
254 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
256 static const char * const *sensorHardwareNames[] = {
257 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
259 #endif // USE_SENSOR_NAMES
261 // Needs to be aligned with mcuTypeId_e
262 static const char *mcuTypeNames[] = {
263 "SIMULATOR",
264 "F40X",
265 "F411",
266 "F446",
267 "F722",
268 "F745",
269 "F746",
270 "F765",
271 "H750",
272 "H743 (Rev Unknown)",
273 "H743 (Rev.Y)",
274 "H743 (Rev.X)",
275 "H743 (Rev.V)",
276 "H7A3",
277 "H723/H725",
278 "G474",
279 "H730",
280 "AT32F435"
283 static const char *configurationStates[] = { "UNCONFIGURED", "CUSTOM DEFAULTS", "CONFIGURED" };
285 typedef enum dumpFlags_e {
286 DUMP_MASTER = (1 << 0),
287 DUMP_PROFILE = (1 << 1),
288 DUMP_RATES = (1 << 2),
289 DUMP_ALL = (1 << 3),
290 DO_DIFF = (1 << 4),
291 SHOW_DEFAULTS = (1 << 5),
292 HIDE_UNUSED = (1 << 6),
293 HARDWARE_ONLY = (1 << 7),
294 BARE = (1 << 8),
295 } dumpFlags_t;
297 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
299 typedef enum {
300 REBOOT_TARGET_FIRMWARE,
301 REBOOT_TARGET_BOOTLOADER_ROM,
302 REBOOT_TARGET_BOOTLOADER_FLASH,
303 } rebootTarget_e;
305 typedef struct serialPassthroughPort_s {
306 int id;
307 uint32_t baud;
308 unsigned mode;
309 serialPort_t *port;
310 } serialPassthroughPort_t;
312 static void cliWriterFlushInternal(bufWriter_t *writer)
314 if (writer) {
315 bufWriterFlush(writer);
319 static void cliPrintInternal(bufWriter_t *writer, const char *str)
321 if (writer) {
322 while (*str) {
323 bufWriterAppend(writer, *str++);
325 cliWriterFlushInternal(writer);
329 static void cliWriterFlush(void)
331 cliWriterFlushInternal(cliWriter);
334 void cliPrint(const char *str)
336 cliPrintInternal(cliWriter, str);
339 void cliPrintLinefeed(void)
341 cliPrint("\r\n");
344 void cliPrintLine(const char *str)
346 cliPrint(str);
347 cliPrintLinefeed();
350 #ifdef MINIMAL_CLI
351 #define cliPrintHashLine(str)
352 #else
353 static void cliPrintHashLine(const char *str)
355 cliPrint("\r\n# ");
356 cliPrintLine(str);
358 #endif
360 static void cliPutp(void *p, char ch)
362 bufWriterAppend(p, ch);
365 static void cliPrintfva(const char *format, va_list va)
367 if (cliWriter) {
368 tfp_format(cliWriter, cliPutp, format, va);
369 cliWriterFlush();
373 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
375 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
376 va_list va;
377 va_start(va, format);
378 cliPrintfva(format, va);
379 va_end(va);
380 cliPrintLinefeed();
381 return true;
382 } else {
383 return false;
387 static void cliWrite(uint8_t ch)
389 if (cliWriter) {
390 bufWriterAppend(cliWriter, ch);
394 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
396 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
397 cliWrite('#');
399 va_list va;
400 va_start(va, format);
401 cliPrintfva(format, va);
402 va_end(va);
403 cliPrintLinefeed();
404 return true;
405 } else {
406 return false;
410 void cliPrintf(const char *format, ...)
412 va_list va;
413 va_start(va, format);
414 cliPrintfva(format, va);
415 va_end(va);
419 void cliPrintLinef(const char *format, ...)
421 va_list va;
422 va_start(va, format);
423 cliPrintfva(format, va);
424 va_end(va);
425 cliPrintLinefeed();
428 static void cliPrintErrorVa(const char *cmdName, const char *format, va_list va)
430 if (cliErrorWriter) {
431 cliPrintInternal(cliErrorWriter, "###ERROR IN ");
432 cliPrintInternal(cliErrorWriter, cmdName);
433 cliPrintInternal(cliErrorWriter, ": ");
435 tfp_format(cliErrorWriter, cliPutp, format, va);
436 va_end(va);
438 cliPrintInternal(cliErrorWriter, "###");
441 #ifdef USE_CLI_BATCH
442 if (commandBatchActive) {
443 commandBatchError = true;
445 #endif
448 static void cliPrintError(const char *cmdName, const char *format, ...)
450 va_list va;
451 va_start(va, format);
452 cliPrintErrorVa(cmdName, format, va);
454 if (!cliWriter) {
455 // Supply our own linefeed in case we are printing inside a custom defaults operation
456 // TODO: Fix this by rewriting the entire CLI to have self contained line feeds
457 // instead of expecting the directly following command to supply the line feed.
458 cliPrintInternal(cliErrorWriter, "\r\n");
462 static void cliPrintErrorLinef(const char *cmdName, const char *format, ...)
464 va_list va;
465 va_start(va, format);
466 cliPrintErrorVa(cmdName, format, va);
467 cliPrintInternal(cliErrorWriter, "\r\n");
470 static void getMinMax(const clivalue_t *var, int *min, int *max)
472 switch (var->type & VALUE_TYPE_MASK) {
473 case VAR_UINT8:
474 case VAR_UINT16:
475 *min = var->config.minmaxUnsigned.min;
476 *max = var->config.minmaxUnsigned.max;
478 break;
479 default:
480 *min = var->config.minmax.min;
481 *max = var->config.minmax.max;
483 break;
487 static void printValuePointer(const char *cmdName, const clivalue_t *var, const void *valuePointer, bool full)
489 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
490 for (int i = 0; i < var->config.array.length; i++) {
491 switch (var->type & VALUE_TYPE_MASK) {
492 default:
493 case VAR_UINT8:
494 // uint8_t array
495 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
496 break;
498 case VAR_INT8:
499 // int8_t array
500 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
501 break;
503 case VAR_UINT16:
504 // uin16_t array
505 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
506 break;
508 case VAR_INT16:
509 // int16_t array
510 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
511 break;
513 case VAR_UINT32:
514 // uin32_t array
515 cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
516 break;
519 if (i < var->config.array.length - 1) {
520 cliPrint(",");
523 } else {
524 int value = 0;
526 switch (var->type & VALUE_TYPE_MASK) {
527 case VAR_UINT8:
528 value = *(uint8_t *)valuePointer;
530 break;
531 case VAR_INT8:
532 value = *(int8_t *)valuePointer;
534 break;
535 case VAR_UINT16:
536 value = *(uint16_t *)valuePointer;
538 break;
539 case VAR_INT16:
540 value = *(int16_t *)valuePointer;
542 break;
543 case VAR_UINT32:
544 value = *(uint32_t *)valuePointer;
546 break;
549 bool valueIsCorrupted = false;
550 switch (var->type & VALUE_MODE_MASK) {
551 case MODE_DIRECT:
552 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
553 cliPrintf("%u", (uint32_t)value);
554 if ((uint32_t)value > var->config.u32Max) {
555 valueIsCorrupted = true;
556 } else if (full) {
557 cliPrintf(" 0 %u", var->config.u32Max);
559 } else {
560 int min;
561 int max;
562 getMinMax(var, &min, &max);
564 cliPrintf("%d", value);
565 if ((value < min) || (value > max)) {
566 valueIsCorrupted = true;
567 } else if (full) {
568 cliPrintf(" %d %d", min, max);
571 break;
572 case MODE_LOOKUP:
573 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
574 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
575 } else {
576 valueIsCorrupted = true;
578 break;
579 case MODE_BITSET:
580 if (value & 1 << var->config.bitpos) {
581 cliPrintf("ON");
582 } else {
583 cliPrintf("OFF");
585 break;
586 case MODE_STRING:
587 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
588 break;
591 if (valueIsCorrupted) {
592 cliPrintLinefeed();
593 cliPrintError(cmdName, "CORRUPTED CONFIG: %s = %d", var->name, value);
599 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
601 bool result = true;
602 int elementCount = 1;
603 uint32_t mask = 0xffffffff;
605 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
606 elementCount = var->config.array.length;
608 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
609 mask = 1 << var->config.bitpos;
611 for (int i = 0; i < elementCount; i++) {
612 switch (var->type & VALUE_TYPE_MASK) {
613 case VAR_UINT8:
614 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
615 break;
617 case VAR_INT8:
618 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
619 break;
621 case VAR_UINT16:
622 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
623 break;
624 case VAR_INT16:
625 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
626 break;
627 case VAR_UINT32:
628 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
629 break;
633 return result;
636 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
638 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
639 cliPrintHashLine(headingStr);
640 return NULL;
641 } else {
642 return headingStr;
646 static void backupPgConfig(const pgRegistry_t *pg)
648 memcpy(pg->copy, pg->address, pg->size);
651 static void restorePgConfig(const pgRegistry_t *pg, uint16_t notToRestoreGroupId)
653 if (!notToRestoreGroupId || pgN(pg) != notToRestoreGroupId) {
654 memcpy(pg->address, pg->copy, pg->size);
658 static void backupConfigs(void)
660 if (configIsInCopy) {
661 return;
664 PG_FOREACH(pg) {
665 backupPgConfig(pg);
668 configIsInCopy = true;
671 static void restoreConfigs(uint16_t notToRestoreGroupId)
673 if (!configIsInCopy) {
674 return;
677 PG_FOREACH(pg) {
678 restorePgConfig(pg, notToRestoreGroupId);
681 configIsInCopy = false;
684 #if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
685 static bool isReadingConfigFromCopy(void)
687 return configIsInCopy;
689 #endif
691 static bool isWritingConfigToCopy(void)
693 return configIsInCopy;
696 static void backupAndResetConfigs(void)
698 backupConfigs();
699 // reset all configs to defaults to do differencing
700 resetConfig();
703 static uint8_t getPidProfileIndexToUse(void)
705 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
708 static uint8_t getRateProfileIndexToUse(void)
710 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
714 static uint16_t getValueOffset(const clivalue_t *value)
716 switch (value->type & VALUE_SECTION_MASK) {
717 case MASTER_VALUE:
718 case HARDWARE_VALUE:
719 return value->offset;
720 case PROFILE_VALUE:
721 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
722 case PROFILE_RATE_VALUE:
723 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
725 return 0;
728 STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
730 const pgRegistry_t* rec = pgFind(value->pgn);
731 if (isWritingConfigToCopy()) {
732 return CONST_CAST(void *, rec->copy + getValueOffset(value));
733 } else {
734 return CONST_CAST(void *, rec->address + getValueOffset(value));
738 static const char *dumpPgValue(const char *cmdName, const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
740 const pgRegistry_t *pg = pgFind(value->pgn);
741 #ifdef DEBUG
742 if (!pg) {
743 cliPrintLinef("VALUE %s ERROR", value->name);
744 return headingStr; // if it's not found, the pgn shouldn't be in the value table!
746 #endif
748 const char *format = "set %s = ";
749 const char *defaultFormat = "#set %s = ";
750 const int valueOffset = getValueOffset(value);
751 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
753 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
754 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
755 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
756 cliPrintf(defaultFormat, value->name);
757 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
758 cliPrintLinefeed();
760 cliPrintf(format, value->name);
761 printValuePointer(cmdName, value, pg->copy + valueOffset, false);
762 cliPrintLinefeed();
764 return headingStr;
767 static void dumpAllValues(const char *cmdName, uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
769 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
771 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
772 const clivalue_t *value = &valueTable[i];
773 cliWriterFlush();
774 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
775 headingStr = dumpPgValue(cmdName, value, dumpMask, headingStr);
780 static void cliPrintVar(const char *cmdName, const clivalue_t *var, bool full)
782 const void *ptr = cliGetValuePointer(var);
784 printValuePointer(cmdName, var, ptr, full);
787 static void cliPrintVarRange(const clivalue_t *var)
789 switch (var->type & VALUE_MODE_MASK) {
790 case (MODE_DIRECT): {
791 switch (var->type & VALUE_TYPE_MASK) {
792 case VAR_UINT32:
793 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
795 break;
796 case VAR_UINT8:
797 case VAR_UINT16:
798 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
800 break;
801 default:
802 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
804 break;
807 break;
808 case (MODE_LOOKUP): {
809 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
810 cliPrint("Allowed values: ");
811 bool firstEntry = true;
812 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
813 if (tableEntry->values[i]) {
814 if (!firstEntry) {
815 cliPrint(", ");
817 cliPrintf("%s", tableEntry->values[i]);
818 firstEntry = false;
821 cliPrintLinefeed();
823 break;
824 case (MODE_ARRAY): {
825 cliPrintLinef("Array length: %d", var->config.array.length);
827 break;
828 case (MODE_STRING): {
829 cliPrintLinef("String length: %d - %d", var->config.string.minlength, var->config.string.maxlength);
831 break;
832 case (MODE_BITSET): {
833 cliPrintLinef("Allowed values: OFF, ON");
835 break;
839 static void cliSetVar(const clivalue_t *var, const uint32_t value)
841 void *ptr = cliGetValuePointer(var);
842 uint32_t workValue;
843 uint32_t mask;
845 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
846 switch (var->type & VALUE_TYPE_MASK) {
847 case VAR_UINT8:
848 mask = (1 << var->config.bitpos) & 0xff;
849 if (value) {
850 workValue = *(uint8_t *)ptr | mask;
851 } else {
852 workValue = *(uint8_t *)ptr & ~mask;
854 *(uint8_t *)ptr = workValue;
855 break;
857 case VAR_UINT16:
858 mask = (1 << var->config.bitpos) & 0xffff;
859 if (value) {
860 workValue = *(uint16_t *)ptr | mask;
861 } else {
862 workValue = *(uint16_t *)ptr & ~mask;
864 *(uint16_t *)ptr = workValue;
865 break;
867 case VAR_UINT32:
868 mask = 1 << var->config.bitpos;
869 if (value) {
870 workValue = *(uint32_t *)ptr | mask;
871 } else {
872 workValue = *(uint32_t *)ptr & ~mask;
874 *(uint32_t *)ptr = workValue;
875 break;
877 } else {
878 switch (var->type & VALUE_TYPE_MASK) {
879 case VAR_UINT8:
880 *(uint8_t *)ptr = value;
881 break;
883 case VAR_INT8:
884 *(int8_t *)ptr = value;
885 break;
887 case VAR_UINT16:
888 *(uint16_t *)ptr = value;
889 break;
891 case VAR_INT16:
892 *(int16_t *)ptr = value;
893 break;
895 case VAR_UINT32:
896 *(uint32_t *)ptr = value;
897 break;
902 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
903 static void cliRepeat(char ch, uint8_t len)
905 if (cliWriter) {
906 for (int i = 0; i < len; i++) {
907 bufWriterAppend(cliWriter, ch);
909 cliPrintLinefeed();
912 #endif
914 static void cliPrompt(void)
916 cliPrint("\r\n# ");
919 static void cliShowParseError(const char *cmdName)
921 cliPrintErrorLinef(cmdName, "PARSING FAILED");
924 static void cliShowInvalidArgumentCountError(const char *cmdName)
926 cliPrintErrorLinef(cmdName, "INVALID ARGUMENT COUNT", cmdName);
929 static void cliShowArgumentRangeError(const char *cmdName, char *name, int min, int max)
931 if (name) {
932 cliPrintErrorLinef(cmdName, "%s NOT BETWEEN %d AND %d", name, min, max);
933 } else {
934 cliPrintErrorLinef(cmdName, "ARGUMENT OUT OF RANGE");
938 static const char *nextArg(const char *currentArg)
940 const char *ptr = strchr(currentArg, ' ');
941 while (ptr && *ptr == ' ') {
942 ptr++;
945 return ptr;
948 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
950 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
951 ptr = nextArg(ptr);
952 if (ptr) {
953 int val = atoi(ptr);
954 val = CHANNEL_VALUE_TO_STEP(val);
955 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
956 if (argIndex == 0) {
957 range->startStep = val;
958 } else {
959 range->endStep = val;
961 (*validArgumentCount)++;
966 return ptr;
969 // Check if a string's length is zero
970 static bool isEmpty(const char *string)
972 return (string == NULL || *string == '\0') ? true : false;
975 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
977 // print out rxConfig failsafe settings
978 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
979 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
980 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
981 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
982 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
983 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
984 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
985 if (requireValue) {
986 const char *format = "rxfail %u %c %d";
987 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
988 channel,
989 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
990 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
992 cliDumpPrintLinef(dumpMask, equalsDefault, format,
993 channel,
994 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
995 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
997 } else {
998 const char *format = "rxfail %u %c";
999 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1000 channel,
1001 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
1003 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1004 channel,
1005 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
1011 static void cliRxFailsafe(const char *cmdName, char *cmdline)
1013 uint8_t channel;
1014 char buf[3];
1016 if (isEmpty(cmdline)) {
1017 // print out rxConfig failsafe settings
1018 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
1019 cliRxFailsafe(cmdName, itoa(channel, buf, 10));
1021 } else {
1022 const char *ptr = cmdline;
1023 channel = atoi(ptr++);
1024 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
1026 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
1028 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
1029 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
1030 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
1032 ptr = nextArg(ptr);
1033 if (ptr) {
1034 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
1035 if (p) {
1036 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
1037 mode = rxFailsafeModesTable[type][requestedMode];
1038 } else {
1039 mode = RX_FAILSAFE_MODE_INVALID;
1041 if (mode == RX_FAILSAFE_MODE_INVALID) {
1042 cliShowParseError(cmdName);
1043 return;
1046 requireValue = mode == RX_FAILSAFE_MODE_SET;
1048 ptr = nextArg(ptr);
1049 if (ptr) {
1050 if (!requireValue) {
1051 cliShowParseError(cmdName);
1052 return;
1054 uint16_t value = atoi(ptr);
1055 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
1056 if (value > MAX_RXFAIL_RANGE_STEP) {
1057 cliPrintErrorLinef(cmdName, "value out of range: %d", value);
1058 return;
1061 channelFailsafeConfig->step = value;
1062 } else if (requireValue) {
1063 cliShowInvalidArgumentCountError(cmdName);
1064 return;
1066 channelFailsafeConfig->mode = mode;
1069 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
1071 // double use of cliPrintf below
1072 // 1. acknowledge interpretation on command,
1073 // 2. query current setting on single item,
1075 if (requireValue) {
1076 cliPrintLinef("rxfail %u %c %d",
1077 channel,
1078 modeCharacter,
1079 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
1081 } else {
1082 cliPrintLinef("rxfail %u %c",
1083 channel,
1084 modeCharacter
1087 } else {
1088 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
1093 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
1095 const char *format = "aux %u %u %u %u %u %u %u";
1096 // print out aux channel settings
1097 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1098 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
1099 const modeActivationCondition_t *mac = &modeActivationConditions[i];
1100 bool equalsDefault = false;
1101 if (defaultModeActivationConditions) {
1102 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
1103 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
1104 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1105 const box_t *box = findBoxByBoxId(macDefault->modeId);
1106 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
1107 if (box) {
1108 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1110 box->permanentId,
1111 macDefault->auxChannelIndex,
1112 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1113 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1114 macDefault->modeLogic,
1115 linkedTo ? linkedTo->permanentId : 0
1119 const box_t *box = findBoxByBoxId(mac->modeId);
1120 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1121 if (box) {
1122 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1124 box->permanentId,
1125 mac->auxChannelIndex,
1126 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1127 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1128 mac->modeLogic,
1129 linkedTo ? linkedTo->permanentId : 0
1135 static void cliAux(const char *cmdName, char *cmdline)
1137 int i, val = 0;
1138 const char *ptr;
1140 if (isEmpty(cmdline)) {
1141 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1142 } else {
1143 ptr = cmdline;
1144 i = atoi(ptr++);
1145 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1146 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1147 uint8_t validArgumentCount = 0;
1148 ptr = nextArg(ptr);
1149 if (ptr) {
1150 val = atoi(ptr);
1151 const box_t *box = findBoxByPermanentId(val);
1152 if (box) {
1153 mac->modeId = box->boxId;
1154 validArgumentCount++;
1157 ptr = nextArg(ptr);
1158 if (ptr) {
1159 val = atoi(ptr);
1160 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1161 mac->auxChannelIndex = val;
1162 validArgumentCount++;
1165 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1166 ptr = nextArg(ptr);
1167 if (ptr) {
1168 val = atoi(ptr);
1169 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1170 mac->modeLogic = val;
1171 validArgumentCount++;
1174 ptr = nextArg(ptr);
1175 if (ptr) {
1176 val = atoi(ptr);
1177 const box_t *box = findBoxByPermanentId(val);
1178 if (box) {
1179 mac->linkedTo = box->boxId;
1180 validArgumentCount++;
1183 if (validArgumentCount == 4) { // for backwards compatibility
1184 mac->modeLogic = MODELOGIC_OR;
1185 mac->linkedTo = 0;
1186 } else if (validArgumentCount == 5) { // for backwards compatibility
1187 mac->linkedTo = 0;
1188 } else if (validArgumentCount != 6) {
1189 memset(mac, 0, sizeof(modeActivationCondition_t));
1191 analyzeModeActivationConditions();
1192 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1194 findBoxByBoxId(mac->modeId)->permanentId,
1195 mac->auxChannelIndex,
1196 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1197 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1198 mac->modeLogic,
1199 findBoxByBoxId(mac->linkedTo)->permanentId
1201 } else {
1202 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1207 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1209 const char *format = "serial %d %d %ld %ld %ld %ld";
1210 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1211 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
1212 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1213 continue;
1215 bool equalsDefault = false;
1216 if (serialConfigDefault) {
1217 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1218 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1219 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1220 serialConfigDefault->portConfigs[i].identifier,
1221 serialConfigDefault->portConfigs[i].functionMask,
1222 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1223 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1224 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1225 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1228 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1229 serialConfig->portConfigs[i].identifier,
1230 serialConfig->portConfigs[i].functionMask,
1231 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1232 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1233 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1234 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1239 static void cliSerial(const char *cmdName, char *cmdline)
1241 const char *format = "serial %d %d %ld %ld %ld %ld";
1242 if (isEmpty(cmdline)) {
1243 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1244 return;
1246 serialPortConfig_t portConfig;
1247 memset(&portConfig, 0 , sizeof(portConfig));
1250 uint8_t validArgumentCount = 0;
1252 const char *ptr = cmdline;
1254 int val = atoi(ptr++);
1255 serialPortConfig_t *currentConfig = serialFindPortConfigurationMutable(val);
1257 if (currentConfig) {
1258 portConfig.identifier = val;
1259 validArgumentCount++;
1262 ptr = nextArg(ptr);
1263 if (ptr) {
1264 val = strtoul(ptr, NULL, 10);
1265 portConfig.functionMask = val;
1266 validArgumentCount++;
1269 for (int i = 0; i < 4; i ++) {
1270 ptr = nextArg(ptr);
1271 if (!ptr) {
1272 break;
1275 val = atoi(ptr);
1277 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1278 if (baudRates[baudRateIndex] != (uint32_t) val) {
1279 break;
1282 switch (i) {
1283 case 0:
1284 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1285 continue;
1287 portConfig.msp_baudrateIndex = baudRateIndex;
1288 break;
1289 case 1:
1290 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1291 continue;
1293 portConfig.gps_baudrateIndex = baudRateIndex;
1294 break;
1295 case 2:
1296 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1297 continue;
1299 portConfig.telemetry_baudrateIndex = baudRateIndex;
1300 break;
1301 case 3:
1302 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1303 continue;
1305 portConfig.blackbox_baudrateIndex = baudRateIndex;
1306 break;
1309 validArgumentCount++;
1312 if (validArgumentCount < 6) {
1313 cliShowInvalidArgumentCountError(cmdName);
1314 return;
1317 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1319 cliDumpPrintLinef(0, false, format,
1320 portConfig.identifier,
1321 portConfig.functionMask,
1322 baudRates[portConfig.msp_baudrateIndex],
1323 baudRates[portConfig.gps_baudrateIndex],
1324 baudRates[portConfig.telemetry_baudrateIndex],
1325 baudRates[portConfig.blackbox_baudrateIndex]
1330 #if defined(USE_SERIAL_PASSTHROUGH)
1331 static void cbCtrlLine(void *context, uint16_t ctrl)
1333 #ifdef USE_PINIO
1334 int contextValue = (int)(long)context;
1335 if (contextValue) {
1336 pinioSet(contextValue - 1, !(ctrl & CTRL_LINE_STATE_DTR));
1337 } else
1338 #endif /* USE_PINIO */
1339 UNUSED(context);
1341 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1342 systemReset();
1346 static int cliParseSerialMode(const char *tok)
1348 int mode = 0;
1350 if (strcasestr(tok, "rx")) {
1351 mode |= MODE_RX;
1353 if (strcasestr(tok, "tx")) {
1354 mode |= MODE_TX;
1357 return mode;
1360 static void cliSerialPassthrough(const char *cmdName, char *cmdline)
1362 if (isEmpty(cmdline)) {
1363 cliShowInvalidArgumentCountError(cmdName);
1364 return;
1367 serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, NULL}, {cliPort->identifier, 0, 0, cliPort} };
1368 bool enableBaudCb = false;
1369 int port1PinioDtr = 0;
1370 bool port1ResetOnDtr = false;
1371 #ifdef USE_PWM_OUTPUT
1372 bool escSensorPassthrough = false;
1373 #endif
1374 char *saveptr;
1375 char* tok = strtok_r(cmdline, " ", &saveptr);
1376 int index = 0;
1378 while (tok != NULL) {
1379 switch (index) {
1380 case 0:
1381 if (strcasestr(tok, "esc_sensor")) {
1382 #ifdef USE_PWM_OUTPUT
1383 escSensorPassthrough = true;
1384 #endif
1385 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
1386 ports[0].id = portConfig->identifier;
1387 } else {
1388 ports[0].id = atoi(tok);
1390 break;
1391 case 1:
1392 ports[0].baud = atoi(tok);
1393 break;
1394 case 2:
1395 ports[0].mode = cliParseSerialMode(tok);
1396 break;
1397 case 3:
1398 if (strncasecmp(tok, "reset", strlen(tok)) == 0) {
1399 port1ResetOnDtr = true;
1400 #ifdef USE_PINIO
1401 } else if (strncasecmp(tok, "none", strlen(tok)) == 0) {
1402 port1PinioDtr = 0;
1403 } else {
1404 port1PinioDtr = atoi(tok);
1405 if (port1PinioDtr < 0 || port1PinioDtr > PINIO_COUNT) {
1406 cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
1407 return ;
1409 #endif /* USE_PINIO */
1411 break;
1412 case 4:
1413 ports[1].id = atoi(tok);
1414 ports[1].port = NULL;
1415 break;
1416 case 5:
1417 ports[1].baud = atoi(tok);
1418 break;
1419 case 6:
1420 ports[1].mode = cliParseSerialMode(tok);
1421 break;
1423 index++;
1424 tok = strtok_r(NULL, " ", &saveptr);
1427 // Port checks
1428 if (ports[0].id == ports[1].id) {
1429 cliPrintLinef("Port1 and port2 are same");
1430 return ;
1433 for (int i = 0; i < 2; i++) {
1434 if (findSerialPortIndexByIdentifier(ports[i].id) == -1) {
1435 cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
1436 return ;
1437 } else {
1438 cliPrintLinef("Port%d: %d ", i + 1, ports[i].id);
1442 if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
1443 enableBaudCb = true;
1446 for (int i = 0; i < 2; i++) {
1447 serialPort_t **port = &(ports[i].port);
1448 if (*port != NULL) {
1449 continue;
1452 int portIndex = i + 1;
1453 serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(ports[i].id);
1454 if (!portUsage || portUsage->serialPort == NULL) {
1455 bool isUseDefaultBaud = false;
1456 if (ports[i].baud == 0) {
1457 // Set default baud
1458 ports[i].baud = 57600;
1459 isUseDefaultBaud = true;
1462 if (!ports[i].mode) {
1463 ports[i].mode = MODE_RXTX;
1466 *port = openSerialPort(ports[i].id, FUNCTION_NONE, NULL, NULL,
1467 ports[i].baud, ports[i].mode,
1468 SERIAL_NOT_INVERTED);
1469 if (!*port) {
1470 cliPrintLinef("Port%d could not be opened.", portIndex);
1471 return;
1474 if (isUseDefaultBaud) {
1475 cliPrintf("Port%d opened, default baud = %d.\r\n", portIndex, ports[i].baud);
1476 } else {
1477 cliPrintf("Port%d opened, baud = %d.\r\n", portIndex, ports[i].baud);
1479 } else {
1480 *port = portUsage->serialPort;
1481 // If the user supplied a mode, override the port's mode, otherwise
1482 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1483 // Set the baud rate if specified
1484 if (ports[i].baud) {
1485 cliPrintf("Port%d is already open, setting baud = %d.\r\n", portIndex, ports[i].baud);
1486 serialSetBaudRate(*port, ports[i].baud);
1487 } else {
1488 cliPrintf("Port%d is already open, baud = %d.\r\n", portIndex, (*port)->baudRate);
1491 if (ports[i].mode && (*port)->mode != ports[i].mode) {
1492 cliPrintf("Port%d mode changed from %d to %d.\r\n",
1493 portIndex, (*port)->mode, ports[i].mode);
1494 serialSetMode(*port, ports[i].mode);
1497 // If this port has a rx callback associated we need to remove it now.
1498 // Otherwise no data will be pushed in the serial port buffer!
1499 if ((*port)->rxCallback) {
1500 (*port)->rxCallback = NULL;
1505 // If no baud rate is specified allow to be set via USB
1506 if (enableBaudCb) {
1507 cliPrintLine("Port1 baud rate change over USB enabled.");
1508 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1509 // baud rate over USB without setting it using the serialpassthrough command
1510 serialSetBaudRateCb(ports[1].port, serialSetBaudRate, ports[0].port);
1513 char *resetMessage = "";
1514 if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
1515 resetMessage = "or drop DTR ";
1518 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1520 if ((ports[1].id == SERIAL_PORT_USB_VCP) && (port1ResetOnDtr
1521 #ifdef USE_PINIO
1522 || port1PinioDtr
1523 #endif /* USE_PINIO */
1524 )) {
1525 // Register control line state callback
1526 serialSetCtrlLineStateCb(ports[0].port, cbCtrlLine, (void *)(intptr_t)(port1PinioDtr));
1529 // XXX Review ESC pass through under refactored motor handling
1530 #ifdef USE_PWM_OUTPUT
1531 if (escSensorPassthrough) {
1532 // pwmDisableMotors();
1533 motorDisable();
1534 delay(5);
1535 for (unsigned i = 0; i < getMotorCount(); i++) {
1536 const ioTag_t tag = motorConfig()->dev.ioTags[i];
1537 if (tag) {
1538 const timerHardware_t *timerHardware = timerGetConfiguredByTag(tag);
1539 if (timerHardware) {
1540 IO_t io = IOGetByTag(tag);
1541 IOInit(io, OWNER_MOTOR, 0);
1542 IOConfigGPIO(io, IOCFG_OUT_PP);
1543 if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
1544 IOLo(io);
1545 } else {
1546 IOHi(io);
1552 #endif
1554 serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
1556 #endif
1558 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1560 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1561 // print out adjustment ranges channel settings
1562 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1563 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1564 const adjustmentRange_t *ar = &adjustmentRanges[i];
1565 bool equalsDefault = false;
1566 if (defaultAdjustmentRanges) {
1567 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1568 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1569 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1570 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1572 arDefault->auxChannelIndex,
1573 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1574 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1575 arDefault->adjustmentConfig,
1576 arDefault->auxSwitchChannelIndex,
1577 arDefault->adjustmentCenter,
1578 arDefault->adjustmentScale
1581 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1583 ar->auxChannelIndex,
1584 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1585 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1586 ar->adjustmentConfig,
1587 ar->auxSwitchChannelIndex,
1588 ar->adjustmentCenter,
1589 ar->adjustmentScale
1594 static void cliAdjustmentRange(const char *cmdName, char *cmdline)
1596 const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
1597 int i, val = 0;
1598 const char *ptr;
1600 if (isEmpty(cmdline)) {
1601 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1602 } else {
1603 ptr = cmdline;
1604 i = atoi(ptr++);
1605 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1606 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1607 uint8_t validArgumentCount = 0;
1609 ptr = nextArg(ptr);
1610 if (ptr) {
1611 val = atoi(ptr);
1612 // Was: slot
1613 // Keeping the parameter to retain backwards compatibility for the command format.
1614 validArgumentCount++;
1616 ptr = nextArg(ptr);
1617 if (ptr) {
1618 val = atoi(ptr);
1619 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1620 ar->auxChannelIndex = val;
1621 validArgumentCount++;
1625 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1627 ptr = nextArg(ptr);
1628 if (ptr) {
1629 val = atoi(ptr);
1630 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1631 ar->adjustmentConfig = val;
1632 validArgumentCount++;
1635 ptr = nextArg(ptr);
1636 if (ptr) {
1637 val = atoi(ptr);
1638 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1639 ar->auxSwitchChannelIndex = val;
1640 validArgumentCount++;
1644 if (validArgumentCount != 6) {
1645 memset(ar, 0, sizeof(adjustmentRange_t));
1646 cliShowInvalidArgumentCountError(cmdName);
1647 return;
1650 // Optional arguments
1651 ar->adjustmentCenter = 0;
1652 ar->adjustmentScale = 0;
1654 ptr = nextArg(ptr);
1655 if (ptr) {
1656 val = atoi(ptr);
1657 ar->adjustmentCenter = val;
1658 validArgumentCount++;
1660 ptr = nextArg(ptr);
1661 if (ptr) {
1662 val = atoi(ptr);
1663 ar->adjustmentScale = val;
1664 validArgumentCount++;
1667 activeAdjustmentRangeReset();
1669 cliDumpPrintLinef(0, false, format,
1671 ar->auxChannelIndex,
1672 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1673 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1674 ar->adjustmentConfig,
1675 ar->auxSwitchChannelIndex,
1676 ar->adjustmentCenter,
1677 ar->adjustmentScale
1680 } else {
1681 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1686 #ifndef USE_QUAD_MIXER_ONLY
1687 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1689 const char *format = "mmix %d %s %s %s %s";
1690 char buf0[FTOA_BUFFER_LENGTH];
1691 char buf1[FTOA_BUFFER_LENGTH];
1692 char buf2[FTOA_BUFFER_LENGTH];
1693 char buf3[FTOA_BUFFER_LENGTH];
1694 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1695 if (customMotorMixer[i].throttle == 0.0f)
1696 break;
1697 const float thr = customMotorMixer[i].throttle;
1698 const float roll = customMotorMixer[i].roll;
1699 const float pitch = customMotorMixer[i].pitch;
1700 const float yaw = customMotorMixer[i].yaw;
1701 bool equalsDefault = false;
1702 if (defaultCustomMotorMixer) {
1703 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1704 const float rollDefault = defaultCustomMotorMixer[i].roll;
1705 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1706 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1707 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1709 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1710 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1712 ftoa(thrDefault, buf0),
1713 ftoa(rollDefault, buf1),
1714 ftoa(pitchDefault, buf2),
1715 ftoa(yawDefault, buf3));
1717 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1719 ftoa(thr, buf0),
1720 ftoa(roll, buf1),
1721 ftoa(pitch, buf2),
1722 ftoa(yaw, buf3));
1725 #endif // USE_QUAD_MIXER_ONLY
1727 static void cliMotorMix(const char *cmdName, char *cmdline)
1729 #ifdef USE_QUAD_MIXER_ONLY
1730 UNUSED(cmdName);
1731 UNUSED(cmdline);
1732 #else
1733 int check = 0;
1734 uint8_t len;
1735 const char *ptr;
1737 if (isEmpty(cmdline)) {
1738 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1739 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1740 // erase custom mixer
1741 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1742 customMotorMixerMutable(i)->throttle = 0.0f;
1744 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1745 ptr = nextArg(cmdline);
1746 if (ptr) {
1747 len = strlen(ptr);
1748 for (uint32_t i = 0; ; i++) {
1749 if (mixerNames[i] == NULL) {
1750 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
1751 break;
1753 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1754 mixerLoadMix(i, customMotorMixerMutable(0));
1755 cliPrintLinef("Loaded %s", mixerNames[i]);
1756 cliMotorMix(cmdName, "");
1757 break;
1761 } else {
1762 ptr = cmdline;
1763 uint32_t i = atoi(ptr); // get motor number
1764 if (i < MAX_SUPPORTED_MOTORS) {
1765 ptr = nextArg(ptr);
1766 if (ptr) {
1767 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1768 check++;
1770 ptr = nextArg(ptr);
1771 if (ptr) {
1772 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1773 check++;
1775 ptr = nextArg(ptr);
1776 if (ptr) {
1777 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1778 check++;
1780 ptr = nextArg(ptr);
1781 if (ptr) {
1782 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1783 check++;
1785 if (check != 4) {
1786 cliShowInvalidArgumentCountError(cmdName);
1787 } else {
1788 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1790 } else {
1791 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1794 #endif
1797 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1799 const char *format = "rxrange %u %u %u";
1800 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1801 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1802 bool equalsDefault = false;
1803 if (defaultChannelRangeConfigs) {
1804 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1805 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1806 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1808 defaultChannelRangeConfigs[i].min,
1809 defaultChannelRangeConfigs[i].max
1812 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1814 channelRangeConfigs[i].min,
1815 channelRangeConfigs[i].max
1820 static void cliRxRange(const char *cmdName, char *cmdline)
1822 const char *format = "rxrange %u %u %u";
1823 int i, validArgumentCount = 0;
1824 const char *ptr;
1826 if (isEmpty(cmdline)) {
1827 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1828 } else if (strcasecmp(cmdline, "reset") == 0) {
1829 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1830 } else {
1831 ptr = cmdline;
1832 i = atoi(ptr);
1833 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1834 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1836 ptr = nextArg(ptr);
1837 if (ptr) {
1838 rangeMin = atoi(ptr);
1839 validArgumentCount++;
1842 ptr = nextArg(ptr);
1843 if (ptr) {
1844 rangeMax = atoi(ptr);
1845 validArgumentCount++;
1848 if (validArgumentCount != 2) {
1849 cliShowInvalidArgumentCountError(cmdName);
1850 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1851 cliShowArgumentRangeError(cmdName, "range min/max", PWM_PULSE_MIN, PWM_PULSE_MAX);
1852 } else {
1853 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1854 channelRangeConfig->min = rangeMin;
1855 channelRangeConfig->max = rangeMax;
1856 cliDumpPrintLinef(0, false, format,
1858 channelRangeConfig->min,
1859 channelRangeConfig->max
1863 } else {
1864 cliShowArgumentRangeError(cmdName, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1869 #ifdef USE_LED_STRIP_STATUS_MODE
1870 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1872 const char *format = "led %u %s";
1873 char ledConfigBuffer[20];
1874 char ledConfigDefaultBuffer[20];
1875 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1876 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1877 ledConfig_t ledConfig = ledConfigs[i];
1878 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1879 bool equalsDefault = false;
1880 if (defaultLedConfigs) {
1881 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1882 equalsDefault = ledConfig == ledConfigDefault;
1883 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1884 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1885 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1887 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1891 static void cliLed(const char *cmdName, char *cmdline)
1893 const char *format = "led %u %s";
1894 char ledConfigBuffer[20];
1895 int i;
1896 const char *ptr;
1898 if (isEmpty(cmdline)) {
1899 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
1900 } else {
1901 ptr = cmdline;
1902 i = atoi(ptr);
1903 if (i >= 0 && i < LED_MAX_STRIP_LENGTH) {
1904 ptr = nextArg(cmdline);
1905 if (parseLedStripConfig(i, ptr)) {
1906 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1907 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1908 } else {
1909 cliShowParseError(cmdName);
1911 } else {
1912 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_MAX_STRIP_LENGTH - 1);
1917 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
1919 const char *format = "color %u %d,%u,%u";
1920 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1921 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1922 const hsvColor_t *color = &colors[i];
1923 bool equalsDefault = false;
1924 if (defaultColors) {
1925 const hsvColor_t *colorDefault = &defaultColors[i];
1926 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1927 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1928 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1930 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1934 static void cliColor(const char *cmdName, char *cmdline)
1936 const char *format = "color %u %d,%u,%u";
1937 if (isEmpty(cmdline)) {
1938 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
1939 } else {
1940 const char *ptr = cmdline;
1941 const int i = atoi(ptr);
1942 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1943 ptr = nextArg(cmdline);
1944 if (parseColor(i, ptr)) {
1945 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
1946 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1947 } else {
1948 cliShowParseError(cmdName);
1950 } else {
1951 cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1956 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
1958 const char *format = "mode_color %u %u %u";
1959 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1960 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1961 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1962 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
1963 bool equalsDefault = false;
1964 if (defaultLedStripConfig) {
1965 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1966 equalsDefault = colorIndex == colorIndexDefault;
1967 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1968 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1970 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1974 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1975 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
1976 bool equalsDefault = false;
1977 if (defaultLedStripConfig) {
1978 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1979 equalsDefault = colorIndex == colorIndexDefault;
1980 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1981 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1983 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1986 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
1987 bool equalsDefault = false;
1988 if (defaultLedStripConfig) {
1989 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1990 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1991 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1992 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1994 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1997 static void cliModeColor(const char *cmdName, char *cmdline)
1999 if (isEmpty(cmdline)) {
2000 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
2001 } else {
2002 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
2003 int args[ARGS_COUNT];
2004 int argNo = 0;
2005 char *saveptr;
2006 const char* ptr = strtok_r(cmdline, " ", &saveptr);
2007 while (ptr && argNo < ARGS_COUNT) {
2008 args[argNo++] = atoi(ptr);
2009 ptr = strtok_r(NULL, " ", &saveptr);
2012 if (ptr != NULL || argNo != ARGS_COUNT) {
2013 cliShowInvalidArgumentCountError(cmdName);
2014 return;
2017 int modeIdx = args[MODE];
2018 int funIdx = args[FUNCTION];
2019 int color = args[COLOR];
2020 if (!setModeColor(modeIdx, funIdx, color)) {
2021 cliShowParseError(cmdName);
2022 return;
2024 // values are validated
2025 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
2028 #endif
2030 #ifdef USE_SERVOS
2031 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
2033 // print out servo settings
2034 const char *format = "servo %u %d %d %d %d %d";
2035 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2036 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2037 const servoParam_t *servoConf = &servoParams[i];
2038 bool equalsDefault = false;
2039 if (defaultServoParams) {
2040 const servoParam_t *defaultServoConf = &defaultServoParams[i];
2041 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
2042 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2043 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2045 defaultServoConf->min,
2046 defaultServoConf->max,
2047 defaultServoConf->middle,
2048 defaultServoConf->rate,
2049 defaultServoConf->forwardFromChannel
2052 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2054 servoConf->min,
2055 servoConf->max,
2056 servoConf->middle,
2057 servoConf->rate,
2058 servoConf->forwardFromChannel
2061 // print servo directions
2062 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2063 const char *format = "smix reverse %d %d r";
2064 const servoParam_t *servoConf = &servoParams[i];
2065 const servoParam_t *servoConfDefault = &defaultServoParams[i];
2066 if (defaultServoParams) {
2067 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
2068 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2069 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
2070 if (servoConfDefault->reversedSources & (1 << channel)) {
2071 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
2073 if (servoConf->reversedSources & (1 << channel)) {
2074 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
2077 } else {
2078 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
2079 if (servoConf->reversedSources & (1 << channel)) {
2080 cliDumpPrintLinef(dumpMask, true, format, i , channel);
2087 static void cliServo(const char *cmdName, char *cmdline)
2089 const char *format = "servo %u %d %d %d %d %d";
2090 enum { SERVO_ARGUMENT_COUNT = 6 };
2091 int16_t arguments[SERVO_ARGUMENT_COUNT];
2093 servoParam_t *servo;
2095 int i;
2096 char *ptr;
2098 if (isEmpty(cmdline)) {
2099 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
2100 } else {
2101 int validArgumentCount = 0;
2103 ptr = cmdline;
2105 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
2107 // If command line doesn't fit the format, don't modify the config
2108 while (*ptr) {
2109 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
2110 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
2111 cliShowInvalidArgumentCountError(cmdName);
2112 return;
2115 arguments[validArgumentCount++] = atoi(ptr);
2117 do {
2118 ptr++;
2119 } while (*ptr >= '0' && *ptr <= '9');
2120 } else if (*ptr == ' ') {
2121 ptr++;
2122 } else {
2123 cliShowParseError(cmdName);
2124 return;
2128 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
2130 i = arguments[INDEX];
2132 // Check we got the right number of args and the servo index is correct (don't validate the other values)
2133 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
2134 cliShowInvalidArgumentCountError(cmdName);
2135 return;
2138 servo = servoParamsMutable(i);
2140 if (
2141 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
2142 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
2143 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2144 arguments[MIN] > arguments[MAX] ||
2145 arguments[RATE] < -100 || arguments[RATE] > 100 ||
2146 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
2148 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2149 return;
2152 servo->min = arguments[MIN];
2153 servo->max = arguments[MAX];
2154 servo->middle = arguments[MIDDLE];
2155 servo->rate = arguments[RATE];
2156 servo->forwardFromChannel = arguments[FORWARD];
2158 cliDumpPrintLinef(0, false, format,
2160 servo->min,
2161 servo->max,
2162 servo->middle,
2163 servo->rate,
2164 servo->forwardFromChannel
2169 #endif
2171 #ifdef USE_SERVOS
2172 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
2174 const char *format = "smix %d %d %d %d %d %d %d %d";
2175 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2176 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2177 const servoMixer_t customServoMixer = customServoMixers[i];
2178 if (customServoMixer.rate == 0) {
2179 break;
2182 bool equalsDefault = false;
2183 if (defaultCustomServoMixers) {
2184 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2185 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2187 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2188 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2190 customServoMixerDefault.targetChannel,
2191 customServoMixerDefault.inputSource,
2192 customServoMixerDefault.rate,
2193 customServoMixerDefault.speed,
2194 customServoMixerDefault.min,
2195 customServoMixerDefault.max,
2196 customServoMixerDefault.box
2199 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2201 customServoMixer.targetChannel,
2202 customServoMixer.inputSource,
2203 customServoMixer.rate,
2204 customServoMixer.speed,
2205 customServoMixer.min,
2206 customServoMixer.max,
2207 customServoMixer.box
2212 static void cliServoMix(const char *cmdName, char *cmdline)
2214 int args[8], check = 0;
2215 int len = strlen(cmdline);
2217 if (len == 0) {
2218 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2219 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2220 // erase custom mixer
2221 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2222 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2223 servoParamsMutable(i)->reversedSources = 0;
2225 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2226 const char *ptr = nextArg(cmdline);
2227 if (ptr) {
2228 len = strlen(ptr);
2229 for (uint32_t i = 0; ; i++) {
2230 if (mixerNames[i] == NULL) {
2231 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
2232 break;
2234 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2235 servoMixerLoadMix(i);
2236 cliPrintLinef("Loaded %s", mixerNames[i]);
2237 cliServoMix(cmdName, "");
2238 break;
2242 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2243 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2244 char *ptr = strchr(cmdline, ' ');
2246 if (ptr == NULL) {
2247 cliPrintf("s");
2248 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2249 cliPrintf("\ti%d", inputSource);
2250 cliPrintLinefeed();
2252 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2253 cliPrintf("%d", servoIndex);
2254 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2255 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2257 cliPrintLinefeed();
2259 return;
2262 char *saveptr;
2263 ptr = strtok_r(ptr, " ", &saveptr);
2264 while (ptr != NULL && check < ARGS_COUNT - 1) {
2265 args[check++] = atoi(ptr);
2266 ptr = strtok_r(NULL, " ", &saveptr);
2269 if (ptr == NULL || check != ARGS_COUNT - 1) {
2270 cliShowInvalidArgumentCountError(cmdName);
2271 return;
2274 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2275 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2276 && (*ptr == 'r' || *ptr == 'n')) {
2277 if (*ptr == 'r') {
2278 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2279 } else {
2280 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2282 } else {
2283 cliShowArgumentRangeError(cmdName, "servo", 0, MAX_SUPPORTED_SERVOS);
2284 return;
2287 cliServoMix(cmdName, "reverse");
2288 } else {
2289 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2290 char *saveptr;
2291 char *ptr = strtok_r(cmdline, " ", &saveptr);
2292 while (ptr != NULL && check < ARGS_COUNT) {
2293 args[check++] = atoi(ptr);
2294 ptr = strtok_r(NULL, " ", &saveptr);
2297 if (ptr != NULL || check != ARGS_COUNT) {
2298 cliShowInvalidArgumentCountError(cmdName);
2299 return;
2302 int32_t i = args[RULE];
2303 if (i >= 0 && i < MAX_SERVO_RULES &&
2304 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2305 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2306 args[RATE] >= -100 && args[RATE] <= 100 &&
2307 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2308 args[MIN] >= 0 && args[MIN] <= 100 &&
2309 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2310 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2311 customServoMixersMutable(i)->targetChannel = args[TARGET];
2312 customServoMixersMutable(i)->inputSource = args[INPUT];
2313 customServoMixersMutable(i)->rate = args[RATE];
2314 customServoMixersMutable(i)->speed = args[SPEED];
2315 customServoMixersMutable(i)->min = args[MIN];
2316 customServoMixersMutable(i)->max = args[MAX];
2317 customServoMixersMutable(i)->box = args[BOX];
2318 cliServoMix(cmdName, "");
2319 } else {
2320 cliShowArgumentRangeError(cmdName, NULL, 0, 0);
2324 #endif
2326 #ifdef USE_SDCARD
2328 static void cliWriteBytes(const uint8_t *buffer, int count)
2330 while (count > 0) {
2331 cliWrite(*buffer);
2332 buffer++;
2333 count--;
2337 static void cliSdInfo(const char *cmdName, char *cmdline)
2339 UNUSED(cmdName);
2340 UNUSED(cmdline);
2342 cliPrint("SD card: ");
2344 if (sdcardConfig()->mode == SDCARD_MODE_NONE) {
2345 cliPrintLine("Not configured");
2347 return;
2350 if (!sdcard_isInserted()) {
2351 cliPrintLine("None inserted");
2352 return;
2355 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2356 cliPrintLine("Startup failed");
2357 return;
2360 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2362 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2363 metadata->manufacturerID,
2364 metadata->numBlocks / 2, /* One block is half a kB */
2365 metadata->productionMonth,
2366 metadata->productionYear,
2367 metadata->productRevisionMajor,
2368 metadata->productRevisionMinor
2371 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2373 cliPrint("'\r\n" "Filesystem: ");
2375 switch (afatfs_getFilesystemState()) {
2376 case AFATFS_FILESYSTEM_STATE_READY:
2377 cliPrint("Ready");
2378 break;
2379 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2380 cliPrint("Initializing");
2381 break;
2382 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2383 case AFATFS_FILESYSTEM_STATE_FATAL:
2384 cliPrint("Fatal");
2386 switch (afatfs_getLastError()) {
2387 case AFATFS_ERROR_BAD_MBR:
2388 cliPrint(" - no FAT MBR partitions");
2389 break;
2390 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2391 cliPrint(" - bad FAT header");
2392 break;
2393 case AFATFS_ERROR_GENERIC:
2394 case AFATFS_ERROR_NONE:
2395 ; // Nothing more detailed to print
2396 break;
2398 break;
2400 cliPrintLinefeed();
2403 #endif
2405 #ifdef USE_FLASH_CHIP
2406 static void cliFlashInfo(const char *cmdName, char *cmdline)
2408 UNUSED(cmdName);
2409 UNUSED(cmdline);
2411 const flashGeometry_t *layout = flashGetGeometry();
2413 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u JEDEC ID=0x%08x",
2414 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, layout->jedecId);
2416 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2417 const flashPartition_t *partition;
2418 if (index == 0) {
2419 cliPrintLine("Partitions:");
2421 partition = flashPartitionFindByIndex(index);
2422 if (!partition) {
2423 break;
2425 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2427 #ifdef USE_FLASHFS
2428 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2430 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2431 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2432 flashfsGetOffset()
2434 #endif
2436 #endif // USE_FLASH_CHIP
2438 #if defined(USE_FLASHFS) && defined(USE_FLASH_CHIP)
2439 static void cliFlashErase(const char *cmdName, char *cmdline)
2441 UNUSED(cmdName);
2442 UNUSED(cmdline);
2444 if (!flashfsIsSupported()) {
2445 return;
2448 #ifndef MINIMAL_CLI
2449 uint32_t i = 0;
2450 cliPrintLine("Erasing, please wait ... ");
2451 #else
2452 cliPrintLine("Erasing,");
2453 #endif
2455 cliWriterFlush();
2456 flashfsEraseCompletely();
2458 while (!flashfsIsReady()) {
2459 #ifndef MINIMAL_CLI
2460 cliPrintf(".");
2461 if (i++ > 120) {
2462 i=0;
2463 cliPrintLinefeed();
2466 cliWriterFlush();
2467 #endif
2468 delay(100);
2470 beeper(BEEPER_BLACKBOX_ERASE);
2471 cliPrintLinefeed();
2472 cliPrintLine("Done.");
2475 #ifdef USE_FLASH_TOOLS
2476 static void cliFlashVerify(const char *cmdName, char *cmdline)
2478 UNUSED(cmdline);
2480 cliPrintLine("Verifying");
2481 if (flashfsVerifyEntireFlash()) {
2482 cliPrintLine("Success");
2483 } else {
2484 cliPrintErrorLinef(cmdName, "Failed");
2488 static void cliFlashWrite(const char *cmdName, char *cmdline)
2490 const uint32_t address = atoi(cmdline);
2491 const char *text = strchr(cmdline, ' ');
2493 if (!text) {
2494 cliShowInvalidArgumentCountError(cmdName);
2495 } else {
2496 flashfsSeekAbs(address);
2497 flashfsWrite((uint8_t*)text, strlen(text), true);
2498 flashfsFlushSync();
2500 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2504 static void cliFlashRead(const char *cmdName, char *cmdline)
2506 uint32_t address = atoi(cmdline);
2508 const char *nextArg = strchr(cmdline, ' ');
2510 if (!nextArg) {
2511 cliShowInvalidArgumentCountError(cmdName);
2512 } else {
2513 uint32_t length = atoi(nextArg);
2515 cliPrintLinef("Reading %u bytes at %u:", length, address);
2517 uint8_t buffer[32];
2518 while (length > 0) {
2519 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2521 for (int i = 0; i < bytesRead; i++) {
2522 cliWrite(buffer[i]);
2525 length -= bytesRead;
2526 address += bytesRead;
2528 if (bytesRead == 0) {
2529 //Assume we reached the end of the volume or something fatal happened
2530 break;
2533 cliPrintLinefeed();
2536 #endif // USE_FLASH_TOOLS
2537 #endif // USE_FLASHFS
2539 #ifdef USE_VTX_CONTROL
2540 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2542 // print out vtx channel settings
2543 const char *format = "vtx %u %u %u %u %u %u %u";
2544 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2545 bool equalsDefault = false;
2546 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2547 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2548 if (vtxConfigDefault) {
2549 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2550 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2551 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2552 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2554 cacDefault->auxChannelIndex,
2555 cacDefault->band,
2556 cacDefault->channel,
2557 cacDefault->power,
2558 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2559 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2562 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2564 cac->auxChannelIndex,
2565 cac->band,
2566 cac->channel,
2567 cac->power,
2568 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2569 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2574 static void cliVtx(const char *cmdName, char *cmdline)
2576 const char *format = "vtx %u %u %u %u %u %u %u";
2577 int i, val = 0;
2578 const char *ptr;
2580 if (isEmpty(cmdline)) {
2581 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2582 } else {
2583 #ifdef USE_VTX_TABLE
2584 const uint8_t maxBandIndex = vtxTableConfig()->bands;
2585 const uint8_t maxChannelIndex = vtxTableConfig()->channels;
2586 const uint8_t maxPowerIndex = vtxTableConfig()->powerLevels;
2587 #else
2588 const uint8_t maxBandIndex = VTX_TABLE_MAX_BANDS;
2589 const uint8_t maxChannelIndex = VTX_TABLE_MAX_CHANNELS;
2590 const uint8_t maxPowerIndex = VTX_TABLE_MAX_POWER_LEVELS;
2591 #endif
2592 ptr = cmdline;
2593 i = atoi(ptr++);
2594 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2595 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2596 uint8_t validArgumentCount = 0;
2597 ptr = nextArg(ptr);
2598 if (ptr) {
2599 val = atoi(ptr);
2600 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2601 cac->auxChannelIndex = val;
2602 validArgumentCount++;
2605 ptr = nextArg(ptr);
2606 if (ptr) {
2607 val = atoi(ptr);
2608 if (val >= 0 && val <= maxBandIndex) {
2609 cac->band = val;
2610 validArgumentCount++;
2613 ptr = nextArg(ptr);
2614 if (ptr) {
2615 val = atoi(ptr);
2616 if (val >= 0 && val <= maxChannelIndex) {
2617 cac->channel = val;
2618 validArgumentCount++;
2621 ptr = nextArg(ptr);
2622 if (ptr) {
2623 val = atoi(ptr);
2624 if (val >= 0 && val <= maxPowerIndex) {
2625 cac->power= val;
2626 validArgumentCount++;
2629 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2631 if (validArgumentCount != 6) {
2632 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2633 cliShowInvalidArgumentCountError(cmdName);
2634 } else {
2635 cliDumpPrintLinef(0, false, format,
2637 cac->auxChannelIndex,
2638 cac->band,
2639 cac->channel,
2640 cac->power,
2641 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2642 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2645 } else {
2646 cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2651 #endif // VTX_CONTROL
2653 #ifdef USE_VTX_TABLE
2655 static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
2657 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
2658 char freqtmp[5 + 1];
2659 freqbuf[0] = 0;
2660 strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
2661 for (int channel = 0; channel < channels; channel++) {
2662 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2663 strcat(freqbuf, freqtmp);
2665 return freqbuf;
2668 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2670 char *fmt = "vtxtable band %d %s %c%s";
2671 bool equalsDefault = false;
2673 if (defaultConfig) {
2674 equalsDefault = true;
2675 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2676 equalsDefault = false;
2678 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2679 equalsDefault = false;
2681 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2682 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2683 equalsDefault = false;
2686 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2687 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
2688 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2691 char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
2692 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2693 return headingStr;
2696 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2698 // (max 4 digit + 1 space) per level
2699 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2700 char pwrtmp[5 + 1];
2701 pwrbuf[0] = 0;
2702 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2703 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2704 strcat(pwrbuf, pwrtmp);
2706 return pwrbuf;
2709 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2711 char *fmt = "vtxtable powervalues%s";
2712 bool equalsDefault = false;
2713 if (defaultConfig) {
2714 equalsDefault = true;
2715 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2716 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2717 equalsDefault = false;
2720 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2721 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2722 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2725 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2726 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2727 return headingStr;
2730 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2732 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2733 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2734 pwrbuf[0] = 0;
2735 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2736 strcat(pwrbuf, " ");
2737 strcpy(pwrtmp, labels[pwrindex]);
2738 // trim trailing space
2739 char *sp;
2740 while ((sp = strchr(pwrtmp, ' '))) {
2741 *sp = 0;
2743 strcat(pwrbuf, pwrtmp);
2745 return pwrbuf;
2748 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2750 char *fmt = "vtxtable powerlabels%s";
2751 bool equalsDefault = false;
2752 if (defaultConfig) {
2753 equalsDefault = true;
2754 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2755 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2756 equalsDefault = false;
2759 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2760 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2761 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2764 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2765 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2766 return headingStr;
2769 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2771 bool equalsDefault;
2772 char *fmt;
2774 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2776 // bands
2777 equalsDefault = false;
2778 fmt = "vtxtable bands %d";
2779 if (defaultConfig) {
2780 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2781 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2782 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2784 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2786 // channels
2787 equalsDefault = false;
2788 fmt = "vtxtable channels %d";
2789 if (defaultConfig) {
2790 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2791 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2792 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2794 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2796 // band
2798 for (int band = 0; band < currentConfig->bands; band++) {
2799 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2802 // powerlevels
2804 equalsDefault = false;
2805 fmt = "vtxtable powerlevels %d";
2806 if (defaultConfig) {
2807 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2808 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2809 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2811 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2813 // powervalues
2815 // powerlabels
2816 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2817 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2820 static void cliVtxTable(const char *cmdName, char *cmdline)
2822 char *tok;
2823 char *saveptr;
2825 // Band number or nothing
2826 tok = strtok_r(cmdline, " ", &saveptr);
2828 if (!tok) {
2829 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2830 return;
2833 if (strcasecmp(tok, "bands") == 0) {
2834 tok = strtok_r(NULL, " ", &saveptr);
2835 int bands = atoi(tok);
2836 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2837 cliShowArgumentRangeError(cmdName, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS);
2838 return;
2840 if (bands < vtxTableConfigMutable()->bands) {
2841 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2842 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2845 vtxTableConfigMutable()->bands = bands;
2847 } else if (strcasecmp(tok, "channels") == 0) {
2848 tok = strtok_r(NULL, " ", &saveptr);
2850 int channels = atoi(tok);
2851 if (channels < 0 || channels > VTX_TABLE_MAX_CHANNELS) {
2852 cliShowArgumentRangeError(cmdName, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS);
2853 return;
2855 if (channels < vtxTableConfigMutable()->channels) {
2856 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2857 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2860 vtxTableConfigMutable()->channels = channels;
2862 } else if (strcasecmp(tok, "powerlevels") == 0) {
2863 // Number of power levels
2864 tok = strtok_r(NULL, " ", &saveptr);
2865 if (tok) {
2866 int levels = atoi(tok);
2867 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2868 cliShowArgumentRangeError(cmdName, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS);
2869 } else {
2870 if (levels < vtxTableConfigMutable()->powerLevels) {
2871 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2872 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2874 vtxTableConfigMutable()->powerLevels = levels;
2876 } else {
2877 // XXX Show current level count?
2879 return;
2881 } else if (strcasecmp(tok, "powervalues") == 0) {
2882 // Power values
2883 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2884 int count;
2885 int levels = vtxTableConfigMutable()->powerLevels;
2887 memset(power, 0, sizeof(power));
2889 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2890 int value = atoi(tok);
2891 power[count] = value;
2894 // Check remaining tokens
2896 if (count < levels) {
2897 cliPrintErrorLinef(cmdName, "NOT ENOUGH VALUES (EXPECTED %d)", levels);
2898 return;
2899 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2900 cliPrintErrorLinef(cmdName, "TOO MANY VALUES (EXPECTED %d)", levels);
2901 return;
2904 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
2905 vtxTableConfigMutable()->powerValues[i] = power[i];
2908 } else if (strcasecmp(tok, "powerlabels") == 0) {
2909 // Power labels
2910 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
2911 int levels = vtxTableConfigMutable()->powerLevels;
2912 int count;
2913 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2914 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
2915 for (unsigned i = 0; i < strlen(label[count]); i++) {
2916 label[count][i] = toupper(label[count][i]);
2920 // Check remaining tokens
2922 if (count < levels) {
2923 cliPrintErrorLinef(cmdName, "NOT ENOUGH LABELS (EXPECTED %d)", levels);
2924 return;
2925 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2926 cliPrintErrorLinef(cmdName, "TOO MANY LABELS (EXPECTED %d)", levels);
2927 return;
2930 for (int i = 0; i < count; i++) {
2931 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
2933 } else if (strcasecmp(tok, "band") == 0) {
2935 int bands = vtxTableConfigMutable()->bands;
2937 tok = strtok_r(NULL, " ", &saveptr);
2938 if (!tok) {
2939 return;
2942 int band = atoi(tok);
2943 --band;
2945 if (band < 0 || band >= bands) {
2946 cliShowArgumentRangeError(cmdName, "BAND NUMBER", 1, bands);
2947 return;
2950 // Band name
2951 tok = strtok_r(NULL, " ", &saveptr);
2953 if (!tok) {
2954 return;
2957 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
2958 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
2959 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
2960 for (unsigned i = 0; i < strlen(bandname); i++) {
2961 bandname[i] = toupper(bandname[i]);
2964 // Band letter
2965 tok = strtok_r(NULL, " ", &saveptr);
2967 if (!tok) {
2968 return;
2971 char bandletter = toupper(tok[0]);
2973 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
2974 int channel = 0;
2975 int channels = vtxTableConfigMutable()->channels;
2976 bool isFactory = false;
2978 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
2979 if (channel == 0 && !isdigit(tok[0])) {
2980 channel -= 1;
2981 if (strcasecmp(tok, "FACTORY") == 0) {
2982 isFactory = true;
2983 } else if (strcasecmp(tok, "CUSTOM") == 0) {
2984 isFactory = false;
2985 } else {
2986 cliPrintErrorLinef(cmdName, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
2987 return;
2990 int freq = atoi(tok);
2991 if (freq < 0) {
2992 cliPrintErrorLinef(cmdName, "INVALID FREQUENCY %s", tok);
2993 return;
2995 bandfreq[channel] = freq;
2998 if (channel < channels) {
2999 cliPrintErrorLinef(cmdName, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
3000 return;
3001 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
3002 cliPrintErrorLinef(cmdName, "TOO MANY FREQUENCIES (EXPECTED %d)", channels);
3003 return;
3006 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
3007 vtxTableConfigMutable()->bandLetters[band] = bandletter;
3009 for (int i = 0; i < channel; i++) {
3010 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
3012 vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
3013 } else {
3014 // Bad subcommand
3015 cliPrintErrorLinef(cmdName, "INVALID SUBCOMMAND %s", tok);
3019 static void cliVtxInfo(const char *cmdName, char *cmdline)
3021 UNUSED(cmdline);
3023 // Display the available power levels
3024 uint16_t levels[VTX_TABLE_MAX_POWER_LEVELS];
3025 uint16_t powers[VTX_TABLE_MAX_POWER_LEVELS];
3026 vtxDevice_t *vtxDevice = vtxCommonDevice();
3027 if (vtxDevice) {
3028 uint8_t level_count = vtxCommonGetVTXPowerLevels(vtxDevice, levels, powers);
3030 if (level_count) {
3031 for (int i = 0; i < level_count; i++) {
3032 cliPrintLinef("level %d dBm, power %d mW", levels[i], powers[i]);
3034 } else {
3035 cliPrintErrorLinef(cmdName, "NO POWER VALUES DEFINED");
3037 } else {
3038 cliPrintErrorLinef(cmdName, "NO VTX");
3041 #endif // USE_VTX_TABLE
3043 #if defined(USE_SIMPLIFIED_TUNING)
3044 static void applySimplifiedTuningAllProfiles(void)
3046 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3047 applySimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3051 static void cliSimplifiedTuning(const char *cmdName, char *cmdline)
3053 if (strcasecmp(cmdline, "apply") == 0) {
3054 applySimplifiedTuningAllProfiles();
3056 cliPrintLine("Applied simplified tuning.");
3057 } else if (strcasecmp(cmdline, "disable") == 0) {
3058 for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
3059 disableSimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
3062 cliPrintLine("Disabled simplified tuning.");
3063 } else {
3064 cliShowParseError(cmdName);
3067 #endif
3069 static void printCraftName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
3071 const bool equalsDefault = strlen(pilotConfig->craftName) == 0;
3072 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->craftName);
3075 #if defined(USE_BOARD_INFO)
3077 static void printBoardName(dumpFlags_t dumpMask)
3079 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
3080 cliPrintLinef("board_name %s", getBoardName());
3084 static void cliBoardName(const char *cmdName, char *cmdline)
3086 const unsigned int len = strlen(cmdline);
3087 const char *boardName = getBoardName();
3088 if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
3089 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "BOARD_NAME", boardName);
3090 } else {
3091 if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
3092 boardInformationUpdated = true;
3094 cliPrintHashLine("Set board_name.");
3096 printBoardName(DUMP_ALL);
3100 static void printManufacturerId(dumpFlags_t dumpMask)
3102 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
3103 cliPrintLinef("manufacturer_id %s", getManufacturerId());
3107 static void cliManufacturerId(const char *cmdName, char *cmdline)
3109 const unsigned int len = strlen(cmdline);
3110 const char *manufacturerId = getManufacturerId();
3111 if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
3112 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
3113 } else {
3114 if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
3115 boardInformationUpdated = true;
3117 cliPrintHashLine("Set manufacturer_id.");
3119 printManufacturerId(DUMP_ALL);
3123 #if defined(USE_SIGNATURE)
3124 static void writeSignature(char *signatureStr, uint8_t *signature)
3126 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3127 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
3131 static void cliSignature(const char *cmdName, char *cmdline)
3133 const int len = strlen(cmdline);
3135 uint8_t signature[SIGNATURE_LENGTH] = {0};
3136 if (len > 0) {
3137 if (len != 2 * SIGNATURE_LENGTH) {
3138 cliPrintErrorLinef(cmdName, "INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
3140 return;
3143 #define BLOCK_SIZE 2
3144 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
3145 char temp[BLOCK_SIZE + 1];
3146 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
3147 temp[BLOCK_SIZE] = '\0';
3148 char *end;
3149 unsigned result = strtoul(temp, &end, 16);
3150 if (end == &temp[BLOCK_SIZE]) {
3151 signature[i] = result;
3152 } else {
3153 cliPrintErrorLinef(cmdName, "INVALID CHARACTER FOUND: %c", end[0]);
3155 return;
3158 #undef BLOCK_SIZE
3161 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
3162 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
3163 writeSignature(signatureStr, getSignature());
3164 cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "SIGNATURE", signatureStr);
3165 } else {
3166 if (len > 0 && !configIsInCopy && setSignature(signature)) {
3167 signatureUpdated = true;
3169 writeSignature(signatureStr, getSignature());
3171 cliPrintHashLine("Set signature.");
3172 } else if (signatureUpdated || signatureIsSet()) {
3173 writeSignature(signatureStr, getSignature());
3176 cliPrintLinef("signature %s", signatureStr);
3179 #endif
3181 #undef ERROR_MESSAGE
3183 #endif // USE_BOARD_INFO
3185 static void cliMcuId(const char *cmdName, char *cmdline)
3187 UNUSED(cmdName);
3188 UNUSED(cmdline);
3190 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
3193 static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
3195 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3196 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
3197 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3198 const char *format = "feature -%s";
3199 const bool equalsDefault = (~defaultMask | mask) & (1 << i);
3200 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3201 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
3202 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3205 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
3206 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
3207 const char *format = "feature %s";
3208 if (defaultMask & (1 << i)) {
3209 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
3211 if (mask & (1 << i)) {
3212 const bool equalsDefault = (defaultMask | ~mask) & (1 << i);
3213 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3214 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
3220 static void cliFeature(const char *cmdName, char *cmdline)
3222 uint32_t len = strlen(cmdline);
3223 const uint32_t mask = featureConfig()->enabledFeatures;
3224 if (len == 0) {
3225 cliPrint("Enabled: ");
3226 for (uint32_t i = 0; ; i++) {
3227 if (featureNames[i] == NULL) {
3228 break;
3230 if (mask & (1 << i)) {
3231 cliPrintf("%s ", featureNames[i]);
3234 cliPrintLinefeed();
3235 } else if (strncasecmp(cmdline, "list", len) == 0) {
3236 cliPrint("Available:");
3237 for (uint32_t i = 0; ; i++) {
3238 if (featureNames[i] == NULL)
3239 break;
3240 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
3241 cliPrintf(" %s", featureNames[i]);
3243 cliPrintLinefeed();
3244 return;
3245 } else {
3246 uint32_t feature;
3248 bool remove = false;
3249 if (cmdline[0] == '-') {
3250 // remove feature
3251 remove = true;
3252 cmdline++; // skip over -
3253 len--;
3256 for (uint32_t i = 0; ; i++) {
3257 if (featureNames[i] == NULL) {
3258 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
3259 break;
3262 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
3263 feature = 1 << i;
3264 #ifndef USE_GPS
3265 if (feature & FEATURE_GPS) {
3266 cliPrintLine("unavailable");
3267 break;
3269 #endif
3270 #ifndef USE_RANGEFINDER
3271 if (feature & FEATURE_RANGEFINDER) {
3272 cliPrintLine("unavailable");
3273 break;
3275 #endif
3276 if (remove) {
3277 featureConfigClear(feature);
3278 cliPrint("Disabled");
3279 } else {
3280 featureConfigSet(feature);
3281 cliPrint("Enabled");
3283 cliPrintLinef(" %s", featureNames[i]);
3284 break;
3290 #if defined(USE_BEEPER)
3291 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3293 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3294 const uint8_t beeperCount = beeperTableEntryCount();
3295 for (int32_t i = 0; i < beeperCount - 1; i++) {
3296 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3297 const char *formatOff = "%s -%s";
3298 const char *formatOn = "%s %s";
3299 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3300 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3301 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3302 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3303 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3308 static void processBeeperCommand(const char *cmdName, char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3310 uint32_t len = strlen(cmdline);
3311 uint8_t beeperCount = beeperTableEntryCount();
3313 if (len == 0) {
3314 cliPrintf("Disabled:");
3315 for (int32_t i = 0; ; i++) {
3316 if (i == beeperCount - 1) {
3317 if (*offFlags == 0)
3318 cliPrint(" none");
3319 break;
3322 if (beeperModeMaskForTableIndex(i) & *offFlags)
3323 cliPrintf(" %s", beeperNameForTableIndex(i));
3325 cliPrintLinefeed();
3326 } else if (strncasecmp(cmdline, "list", len) == 0) {
3327 cliPrint("Available:");
3328 for (uint32_t i = 0; i < beeperCount; i++) {
3329 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3330 cliPrintf(" %s", beeperNameForTableIndex(i));
3333 cliPrintLinefeed();
3334 } else {
3335 bool remove = false;
3336 if (cmdline[0] == '-') {
3337 remove = true; // this is for beeper OFF condition
3338 cmdline++;
3339 len--;
3342 for (uint32_t i = 0; ; i++) {
3343 if (i == beeperCount) {
3344 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
3345 break;
3347 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3348 if (remove) { // beeper off
3349 if (i == BEEPER_ALL - 1) {
3350 *offFlags = allowedFlags;
3351 } else {
3352 *offFlags |= beeperModeMaskForTableIndex(i);
3354 cliPrint("Disabled");
3356 else { // beeper on
3357 if (i == BEEPER_ALL - 1) {
3358 *offFlags = 0;
3359 } else {
3360 *offFlags &= ~beeperModeMaskForTableIndex(i);
3362 cliPrint("Enabled");
3364 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3365 break;
3371 #if defined(USE_DSHOT)
3372 static void cliBeacon(const char *cmdName, char *cmdline)
3374 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3376 #endif
3378 static void cliBeeper(const char *cmdName, char *cmdline)
3380 processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3382 #endif
3384 #if defined(USE_RX_BIND)
3385 static void cliRxBind(const char *cmdName, char *cmdline)
3387 UNUSED(cmdline);
3388 if (!startRxBind()) {
3389 cliPrintErrorLinef(cmdName, "Not supported.");
3390 } else {
3391 cliPrintLinef("Binding...");
3394 #endif
3396 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3398 bool equalsDefault = true;
3399 char buf[16];
3400 char bufDefault[16];
3401 uint32_t i;
3403 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3404 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3405 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3406 if (defaultRxConfig) {
3407 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3408 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3411 buf[i] = '\0';
3413 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3414 const char *formatMap = "map %s";
3415 if (defaultRxConfig) {
3416 bufDefault[i] = '\0';
3417 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3419 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3423 static void cliMap(const char *cmdName, char *cmdline)
3425 uint32_t i;
3426 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3428 uint32_t len = strlen(cmdline);
3429 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3431 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3432 buf[i] = toupper((unsigned char)cmdline[i]);
3434 buf[i] = '\0';
3436 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3437 buf[i] = toupper((unsigned char)cmdline[i]);
3439 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3440 continue;
3442 cliShowParseError(cmdName);
3443 return;
3445 parseRcChannels(buf, rxConfigMutable());
3446 } else if (len > 0) {
3447 cliShowInvalidArgumentCountError(cmdName);
3448 return;
3451 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3452 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3455 buf[i] = '\0';
3456 cliPrintLinef("map %s", buf);
3459 static char *skipSpace(char *buffer)
3461 while (*(buffer) == ' ') {
3462 buffer++;
3465 return buffer;
3468 static char *checkCommand(char *cmdline, const char *command)
3470 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3471 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3472 return skipSpace(cmdline + strlen(command) + 1);
3473 } else {
3474 return 0;
3478 static void cliRebootEx(rebootTarget_e rebootTarget)
3480 cliPrint("\r\nRebooting");
3481 cliWriterFlush();
3482 waitForSerialPortToFinishTransmitting(cliPort);
3483 motorShutdown();
3485 switch (rebootTarget) {
3486 case REBOOT_TARGET_BOOTLOADER_ROM:
3487 systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
3489 break;
3490 #if defined(USE_FLASH_BOOT_LOADER)
3491 case REBOOT_TARGET_BOOTLOADER_FLASH:
3492 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
3494 break;
3495 #endif
3496 case REBOOT_TARGET_FIRMWARE:
3497 default:
3498 systemReset();
3500 break;
3504 static void cliReboot(void)
3506 cliRebootEx(REBOOT_TARGET_FIRMWARE);
3509 static void cliBootloader(const char *cmdName, char *cmdline)
3511 rebootTarget_e rebootTarget;
3512 if (
3513 #if !defined(USE_FLASH_BOOT_LOADER)
3514 isEmpty(cmdline) ||
3515 #endif
3516 strncasecmp(cmdline, "rom", 3) == 0) {
3517 rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
3519 cliPrintHashLine("restarting in ROM bootloader mode");
3520 #if defined(USE_FLASH_BOOT_LOADER)
3521 } else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
3522 rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
3524 cliPrintHashLine("restarting in flash bootloader mode");
3525 #endif
3526 } else {
3527 cliPrintErrorLinef(cmdName, "Invalid option");
3529 return;
3532 cliRebootEx(rebootTarget);
3535 static void cliExit(const char *cmdName, char *cmdline)
3537 UNUSED(cmdName);
3538 UNUSED(cmdline);
3540 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3541 cliWriterFlush();
3543 *cliBuffer = '\0';
3544 bufferIndex = 0;
3545 cliMode = false;
3546 // incase a motor was left running during motortest, clear it here
3547 mixerResetDisarmedMotors();
3548 cliReboot();
3551 #ifdef USE_GPS
3552 static void cliGpsPassthrough(const char *cmdName, char *cmdline)
3554 UNUSED(cmdName);
3555 UNUSED(cmdline);
3557 gpsEnablePassthrough(cliPort);
3559 #endif
3561 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3562 static void cliPrintGyroRegisters(uint8_t whichSensor)
3564 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3565 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3566 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3569 static void cliDumpGyroRegisters(const char *cmdName, char *cmdline)
3571 UNUSED(cmdName);
3572 UNUSED(cmdline);
3574 #ifdef USE_MULTI_GYRO
3575 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3576 cliPrintLinef("\r\n# Gyro 1");
3577 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3579 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3580 cliPrintLinef("\r\n# Gyro 2");
3581 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3583 #else
3584 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3585 #endif
3587 #endif
3590 static int parseOutputIndex(const char *cmdName, char *pch, bool allowAllEscs)
3592 int outputIndex = atoi(pch);
3593 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3594 cliPrintLinef("Using output %d.", outputIndex);
3595 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3596 cliPrintLinef("Using all outputs.");
3597 } else {
3598 cliPrintErrorLinef(cmdName, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3600 return -1;
3603 return outputIndex;
3606 #if defined(USE_DSHOT)
3607 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3609 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3610 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3611 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3613 enum {
3614 ESC_INFO_KISS_V1,
3615 ESC_INFO_KISS_V2,
3616 ESC_INFO_BLHELI32
3619 #define ESC_INFO_VERSION_POSITION 12
3621 static void printEscInfo(const char *cmdName, const uint8_t *escInfoBuffer, uint8_t bytesRead)
3623 bool escInfoReceived = false;
3624 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3625 uint8_t escInfoVersion;
3626 uint8_t frameLength;
3627 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3628 escInfoVersion = ESC_INFO_BLHELI32;
3629 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3630 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3631 escInfoVersion = ESC_INFO_KISS_V2;
3632 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3633 } else {
3634 escInfoVersion = ESC_INFO_KISS_V1;
3635 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3638 if (bytesRead == frameLength) {
3639 escInfoReceived = true;
3641 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3642 uint8_t firmwareVersion = 0;
3643 uint8_t firmwareSubVersion = 0;
3644 uint8_t escType = 0;
3645 switch (escInfoVersion) {
3646 case ESC_INFO_KISS_V1:
3647 firmwareVersion = escInfoBuffer[12];
3648 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3649 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3651 break;
3652 case ESC_INFO_KISS_V2:
3653 firmwareVersion = escInfoBuffer[13];
3654 firmwareSubVersion = escInfoBuffer[14];
3655 escType = escInfoBuffer[15];
3657 break;
3658 case ESC_INFO_BLHELI32:
3659 firmwareVersion = escInfoBuffer[13];
3660 firmwareSubVersion = escInfoBuffer[14];
3661 escType = escInfoBuffer[15];
3663 break;
3666 cliPrint("ESC Type: ");
3667 switch (escInfoVersion) {
3668 case ESC_INFO_KISS_V1:
3669 case ESC_INFO_KISS_V2:
3670 switch (escType) {
3671 case 1:
3672 cliPrintLine("KISS8A");
3674 break;
3675 case 2:
3676 cliPrintLine("KISS16A");
3678 break;
3679 case 3:
3680 cliPrintLine("KISS24A");
3682 break;
3683 case 5:
3684 cliPrintLine("KISS Ultralite");
3686 break;
3687 default:
3688 cliPrintLine("unknown");
3690 break;
3693 break;
3694 case ESC_INFO_BLHELI32:
3696 char *escType = (char *)(escInfoBuffer + 31);
3697 escType[32] = 0;
3698 cliPrintLine(escType);
3701 break;
3704 cliPrint("MCU Serial No: 0x");
3705 for (int i = 0; i < 12; i++) {
3706 if (i && (i % 3 == 0)) {
3707 cliPrint("-");
3709 cliPrintf("%02x", escInfoBuffer[i]);
3711 cliPrintLinefeed();
3713 switch (escInfoVersion) {
3714 case ESC_INFO_KISS_V1:
3715 case ESC_INFO_KISS_V2:
3716 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3718 break;
3719 case ESC_INFO_BLHELI32:
3720 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3722 break;
3724 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3725 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3726 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3727 if (escInfoVersion == ESC_INFO_BLHELI32) {
3728 uint8_t setting = escInfoBuffer[18];
3729 cliPrint("Low voltage Limit: ");
3730 switch (setting) {
3731 case 0:
3732 cliPrintLine("off");
3734 break;
3735 case 255:
3736 cliPrintLine("unsupported");
3738 break;
3739 default:
3740 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3742 break;
3745 setting = escInfoBuffer[19];
3746 cliPrint("Current Limit: ");
3747 switch (setting) {
3748 case 0:
3749 cliPrintLine("off");
3751 break;
3752 case 255:
3753 cliPrintLine("unsupported");
3755 break;
3756 default:
3757 cliPrintLinef("%d", setting);
3759 break;
3762 for (int i = 0; i < 4; i++) {
3763 setting = escInfoBuffer[i + 20];
3764 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3768 } else {
3769 cliPrintErrorLinef(cmdName, "CHECKSUM ERROR.");
3774 if (!escInfoReceived) {
3775 cliPrintLine("No Info.");
3779 static void executeEscInfoCommand(const char *cmdName, uint8_t escIndex)
3781 cliPrintLinef("Info for ESC %d:", escIndex);
3783 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3785 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3787 dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, DSHOT_CMD_TYPE_BLOCKING);
3789 delay(10);
3791 printEscInfo(cmdName, escInfoBuffer, getNumberEscBytesRead());
3793 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3795 static void cliDshotProg(const char *cmdName, char *cmdline)
3797 if (isEmpty(cmdline) || !isMotorProtocolDshot()) {
3798 cliShowParseError(cmdName);
3800 return;
3803 char *saveptr;
3804 char *pch = strtok_r(cmdline, " ", &saveptr);
3805 int pos = 0;
3806 int escIndex = 0;
3807 bool firstCommand = true;
3808 while (pch != NULL) {
3809 switch (pos) {
3810 case 0:
3811 escIndex = parseOutputIndex(cmdName, pch, true);
3812 if (escIndex == -1) {
3813 return;
3816 break;
3817 default:
3819 int command = atoi(pch);
3820 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3821 if (firstCommand) {
3822 // pwmDisableMotors();
3823 motorDisable();
3825 firstCommand = false;
3828 if (command != DSHOT_CMD_ESC_INFO) {
3829 dshotCommandWrite(escIndex, getMotorCount(), command, DSHOT_CMD_TYPE_BLOCKING);
3830 } else {
3831 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3832 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3833 if (escIndex != ALL_MOTORS) {
3834 executeEscInfoCommand(cmdName, escIndex);
3835 } else {
3836 for (uint8_t i = 0; i < getMotorCount(); i++) {
3837 executeEscInfoCommand(cmdName, i);
3840 } else
3841 #endif
3843 cliPrintLine("Not supported.");
3847 cliPrintLinef("Command Sent: %d", command);
3849 } else {
3850 cliPrintErrorLinef(cmdName, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3854 break;
3857 pos++;
3858 pch = strtok_r(NULL, " ", &saveptr);
3861 motorEnable();
3863 #endif // USE_DSHOT
3865 #ifdef USE_ESCSERIAL
3866 static void cliEscPassthrough(const char *cmdName, char *cmdline)
3868 if (isEmpty(cmdline)) {
3869 cliShowInvalidArgumentCountError(cmdName);
3871 return;
3874 char *saveptr;
3875 char *pch = strtok_r(cmdline, " ", &saveptr);
3876 int pos = 0;
3877 uint8_t mode = 0;
3878 int escIndex = 0;
3879 while (pch != NULL) {
3880 switch (pos) {
3881 case 0:
3882 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3883 mode = PROTOCOL_SIMONK;
3884 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3885 mode = PROTOCOL_BLHELI;
3886 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3887 mode = PROTOCOL_KISS;
3888 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3889 mode = PROTOCOL_KISSALL;
3890 } else {
3891 cliShowParseError(cmdName);
3893 return;
3895 break;
3896 case 1:
3897 escIndex = parseOutputIndex(cmdName, pch, mode == PROTOCOL_KISS);
3898 if (escIndex == -1) {
3899 return;
3902 break;
3903 default:
3904 cliShowInvalidArgumentCountError(cmdName);
3906 return;
3908 break;
3911 pos++;
3912 pch = strtok_r(NULL, " ", &saveptr);
3915 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
3916 cliPrintErrorLinef(cmdName, "Error starting ESC connection");
3919 #endif
3921 #ifndef USE_QUAD_MIXER_ONLY
3922 static void cliMixer(const char *cmdName, char *cmdline)
3924 int len;
3926 len = strlen(cmdline);
3928 if (len == 0) {
3929 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3930 return;
3931 } else if (strncasecmp(cmdline, "list", len) == 0) {
3932 cliPrint("Available:");
3933 for (uint32_t i = 0; ; i++) {
3934 if (mixerNames[i] == NULL)
3935 break;
3936 cliPrintf(" %s", mixerNames[i]);
3938 cliPrintLinefeed();
3939 return;
3942 for (uint32_t i = 0; ; i++) {
3943 if (mixerNames[i] == NULL) {
3944 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
3945 return;
3947 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
3948 mixerConfigMutable()->mixerMode = i + 1;
3949 break;
3953 cliMixer(cmdName, "");
3955 #endif
3957 static void cliMotor(const char *cmdName, char *cmdline)
3959 if (isEmpty(cmdline)) {
3960 cliShowInvalidArgumentCountError(cmdName);
3962 return;
3965 int motorIndex = 0;
3966 int motorValue = 0;
3968 char *saveptr;
3969 char *pch = strtok_r(cmdline, " ", &saveptr);
3970 int index = 0;
3971 while (pch != NULL) {
3972 switch (index) {
3973 case 0:
3974 motorIndex = parseOutputIndex(cmdName, pch, true);
3975 if (motorIndex == -1) {
3976 return;
3979 break;
3980 case 1:
3981 motorValue = atoi(pch);
3983 break;
3985 index++;
3986 pch = strtok_r(NULL, " ", &saveptr);
3989 if (index == 2) {
3990 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
3991 cliShowArgumentRangeError(cmdName, "VALUE", 1000, 2000);
3992 } else {
3993 uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
3995 if (motorIndex != ALL_MOTORS) {
3996 motor_disarmed[motorIndex] = motorOutputValue;
3998 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
3999 } else {
4000 for (int i = 0; i < getMotorCount(); i++) {
4001 motor_disarmed[i] = motorOutputValue;
4004 cliPrintLinef("all motors: %d", motorOutputValue);
4007 } else {
4008 cliShowInvalidArgumentCountError(cmdName);
4012 #ifndef MINIMAL_CLI
4013 static void cliPlaySound(const char *cmdName, char *cmdline)
4015 int i;
4016 const char *name;
4017 static int lastSoundIdx = -1;
4019 if (isEmpty(cmdline)) {
4020 i = lastSoundIdx + 1; //next sound index
4021 if ((name=beeperNameForTableIndex(i)) == NULL) {
4022 while (true) { //no name for index; try next one
4023 if (++i >= beeperTableEntryCount())
4024 i = 0; //if end then wrap around to first entry
4025 if ((name=beeperNameForTableIndex(i)) != NULL)
4026 break; //if name OK then play sound below
4027 if (i == lastSoundIdx + 1) { //prevent infinite loop
4028 cliPrintErrorLinef(cmdName, "ERROR PLAYING SOUND");
4029 return;
4033 } else { //index value was given
4034 i = atoi(cmdline);
4035 if ((name=beeperNameForTableIndex(i)) == NULL) {
4036 cliPrintLinef("No sound for index %d", i);
4037 return;
4040 lastSoundIdx = i;
4041 beeperSilence();
4042 cliPrintLinef("Playing sound %d: %s", i, name);
4043 beeper(beeperModeForTableIndex(i));
4045 #endif
4047 static void cliProfile(const char *cmdName, char *cmdline)
4049 if (isEmpty(cmdline)) {
4050 cliPrintLinef("profile %d", getPidProfileIndexToUse());
4051 return;
4052 } else {
4053 const int i = atoi(cmdline);
4054 if (i >= 0 && i < PID_PROFILE_COUNT) {
4055 changePidProfile(i);
4056 cliProfile(cmdName, "");
4057 } else {
4058 cliPrintErrorLinef(cmdName, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
4063 static void cliRateProfile(const char *cmdName, char *cmdline)
4065 if (isEmpty(cmdline)) {
4066 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
4067 return;
4068 } else {
4069 const int i = atoi(cmdline);
4070 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
4071 changeControlRateProfile(i);
4072 cliRateProfile(cmdName, "");
4073 } else {
4074 cliPrintErrorLinef(cmdName, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
4079 static void cliDumpPidProfile(const char *cmdName, uint8_t pidProfileIndex, dumpFlags_t dumpMask)
4081 if (pidProfileIndex >= PID_PROFILE_COUNT) {
4082 // Faulty values
4083 return;
4086 pidProfileIndexToUse = pidProfileIndex;
4088 cliPrintLinefeed();
4089 cliProfile(cmdName, "");
4091 char profileStr[10];
4092 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
4093 dumpAllValues(cmdName, PROFILE_VALUE, dumpMask, profileStr);
4095 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4098 static void cliDumpRateProfile(const char *cmdName, uint8_t rateProfileIndex, dumpFlags_t dumpMask)
4100 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
4101 // Faulty values
4102 return;
4105 rateProfileIndexToUse = rateProfileIndex;
4107 cliPrintLinefeed();
4108 cliRateProfile(cmdName, "");
4110 char rateProfileStr[14];
4111 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
4112 dumpAllValues(cmdName, PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
4114 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4117 #ifdef USE_CLI_BATCH
4118 static void cliPrintCommandBatchWarning(const char *cmdName, const char *warning)
4120 cliPrintErrorLinef(cmdName, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
4121 if (warning) {
4122 cliPrintErrorLinef(cmdName, warning);
4126 static void resetCommandBatch(void)
4128 commandBatchActive = false;
4129 commandBatchError = false;
4132 static void cliBatch(const char *cmdName, char *cmdline)
4134 if (strncasecmp(cmdline, "start", 5) == 0) {
4135 if (!commandBatchActive) {
4136 commandBatchActive = true;
4137 commandBatchError = false;
4139 cliPrintLine("Command batch started");
4140 } else if (strncasecmp(cmdline, "end", 3) == 0) {
4141 if (commandBatchActive && commandBatchError) {
4142 cliPrintCommandBatchWarning(cmdName, NULL);
4143 } else {
4144 cliPrintLine("Command batch ended");
4146 resetCommandBatch();
4147 } else {
4148 cliPrintErrorLinef(cmdName, "Invalid option");
4151 #endif
4153 static bool prepareSave(void)
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(const char *cmdName)
4178 bool success = prepareSave();
4179 #if defined(USE_CLI_BATCH)
4180 if (!success) {
4181 cliPrintCommandBatchWarning(cmdName, "PLEASE FIX ERRORS THEN 'SAVE'");
4182 resetCommandBatch();
4184 return false;
4186 #else
4187 UNUSED(cmdName);
4188 UNUSED(success);
4189 #endif
4191 return true;
4194 static void cliSave(const char *cmdName, char *cmdline)
4196 UNUSED(cmdline);
4198 if (tryPrepareSave(cmdName)) {
4199 writeEEPROM();
4200 cliPrintHashLine("saving");
4202 cliReboot();
4206 static void cliDefaults(const char *cmdName, char *cmdline)
4208 bool saveConfigs = true;
4209 uint16_t parameterGroupId = 0;
4211 char *saveptr;
4212 char* tok = strtok_r(cmdline, " ", &saveptr);
4213 int index = 0;
4214 bool expectParameterGroupId = false;
4215 while (tok != NULL) {
4216 if (expectParameterGroupId) {
4217 parameterGroupId = atoi(tok);
4218 expectParameterGroupId = false;
4220 if (!parameterGroupId) {
4221 cliShowParseError(cmdName);
4222 return;
4224 } else if (strcasestr(tok, "group_id")) {
4225 expectParameterGroupId = true;
4226 } else if (strcasestr(tok, "nosave")) {
4227 saveConfigs = false;
4228 } else {
4229 cliShowParseError(cmdName);
4231 return;
4234 index++;
4235 tok = strtok_r(NULL, " ", &saveptr);
4238 if (expectParameterGroupId) {
4239 cliShowParseError(cmdName);
4241 return;
4244 if (parameterGroupId) {
4245 cliPrintLinef("\r\n# resetting group %d to defaults", parameterGroupId);
4246 backupConfigs();
4247 } else {
4248 cliPrintHashLine("resetting to defaults");
4251 resetConfig();
4253 #ifdef USE_CLI_BATCH
4254 // Reset only the error state and allow the batch active state to remain.
4255 // This way if a "defaults nosave" was issued after the "batch on" we'll
4256 // only reset the current error state but the batch will still be active
4257 // for subsequent commands.
4258 commandBatchError = false;
4259 #endif
4261 #if defined(USE_SIMPLIFIED_TUNING)
4262 applySimplifiedTuningAllProfiles();
4263 #endif
4265 if (parameterGroupId) {
4266 restoreConfigs(parameterGroupId);
4269 if (saveConfigs && tryPrepareSave(cmdName)) {
4270 writeUnmodifiedConfigToEEPROM();
4272 cliReboot();
4276 static void cliPrintVarDefault(const char *cmdName, const clivalue_t *value)
4278 const pgRegistry_t *pg = pgFind(value->pgn);
4279 if (pg) {
4280 const char *defaultFormat = "Default value: ";
4281 const int valueOffset = getValueOffset(value);
4282 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
4283 if (!equalsDefault) {
4284 cliPrintf(defaultFormat, value->name);
4285 printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
4286 cliPrintLinefeed();
4291 STATIC_UNIT_TESTED void cliGet(const char *cmdName, char *cmdline)
4293 const clivalue_t *val;
4294 int matchedCommands = 0;
4296 pidProfileIndexToUse = getCurrentPidProfileIndex();
4297 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4299 backupAndResetConfigs();
4301 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4302 if (strcasestr(valueTable[i].name, cmdline)) {
4303 val = &valueTable[i];
4304 if (matchedCommands > 0) {
4305 cliPrintLinefeed();
4307 cliPrintf("%s = ", valueTable[i].name);
4308 cliPrintVar(cmdName, val, 0);
4309 cliPrintLinefeed();
4310 switch (val->type & VALUE_SECTION_MASK) {
4311 case PROFILE_VALUE:
4312 cliProfile(cmdName, "");
4314 break;
4315 case PROFILE_RATE_VALUE:
4316 cliRateProfile(cmdName, "");
4318 break;
4319 default:
4321 break;
4323 cliPrintVarRange(val);
4324 cliPrintVarDefault(cmdName, val);
4326 matchedCommands++;
4330 restoreConfigs(0);
4332 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4333 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4335 if (!matchedCommands) {
4336 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
4340 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
4342 while (*(bufEnd - 1) == ' ') {
4343 bufEnd--;
4346 return bufEnd - bufBegin;
4349 uint16_t cliGetSettingIndex(char *name, uint8_t length)
4351 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4352 const char *settingName = valueTable[i].name;
4354 // ensure exact match when setting to prevent setting variables with shorter names
4355 if (strncasecmp(name, settingName, strlen(settingName)) == 0 && length == strlen(settingName)) {
4356 return i;
4359 return valueTableEntryCount;
4362 STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
4364 const uint32_t len = strlen(cmdline);
4365 char *eqptr;
4367 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4368 cliPrintLine("Current settings: ");
4370 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4371 const clivalue_t *val = &valueTable[i];
4372 cliPrintf("%s = ", valueTable[i].name);
4373 cliPrintVar(cmdName, val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4374 cliPrintLinefeed();
4376 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4377 // has equals
4379 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4381 // skip the '=' and any ' ' characters
4382 eqptr++;
4383 eqptr = skipSpace(eqptr);
4385 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4386 if (index >= valueTableEntryCount) {
4387 cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
4388 return;
4390 const clivalue_t *val = &valueTable[index];
4392 bool valueChanged = false;
4393 int16_t value = 0;
4394 switch (val->type & VALUE_MODE_MASK) {
4395 case MODE_DIRECT: {
4396 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4397 uint32_t value = strtoul(eqptr, NULL, 10);
4399 if (value <= val->config.u32Max) {
4400 cliSetVar(val, value);
4401 valueChanged = true;
4403 } else {
4404 int value = atoi(eqptr);
4406 int min;
4407 int max;
4408 getMinMax(val, &min, &max);
4410 if (value >= min && value <= max) {
4411 cliSetVar(val, value);
4412 valueChanged = true;
4417 break;
4418 case MODE_LOOKUP:
4419 case MODE_BITSET: {
4420 int tableIndex;
4421 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4422 tableIndex = TABLE_OFF_ON;
4423 } else {
4424 tableIndex = val->config.lookup.tableIndex;
4426 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4427 bool matched = false;
4428 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4429 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4431 if (matched) {
4432 value = tableValueIndex;
4434 cliSetVar(val, value);
4435 valueChanged = true;
4440 break;
4442 case MODE_ARRAY: {
4443 const uint8_t arrayLength = val->config.array.length;
4444 char *valPtr = eqptr;
4446 int i = 0;
4447 while (i < arrayLength && valPtr != NULL) {
4448 // skip spaces
4449 valPtr = skipSpace(valPtr);
4451 // process substring starting at valPtr
4452 // note: no need to copy substrings for atoi()
4453 // it stops at the first character that cannot be converted...
4454 switch (val->type & VALUE_TYPE_MASK) {
4455 default:
4456 case VAR_UINT8:
4458 // fetch data pointer
4459 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4460 // store value
4461 *data = (uint8_t)atoi((const char*) valPtr);
4464 break;
4465 case VAR_INT8:
4467 // fetch data pointer
4468 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4469 // store value
4470 *data = (int8_t)atoi((const char*) valPtr);
4473 break;
4474 case VAR_UINT16:
4476 // fetch data pointer
4477 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4478 // store value
4479 *data = (uint16_t)atoi((const char*) valPtr);
4482 break;
4483 case VAR_INT16:
4485 // fetch data pointer
4486 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4487 // store value
4488 *data = (int16_t)atoi((const char*) valPtr);
4491 break;
4492 case VAR_UINT32:
4494 // fetch data pointer
4495 uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
4496 // store value
4497 *data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
4500 break;
4503 // find next comma (or end of string)
4504 valPtr = strchr(valPtr, ',') + 1;
4506 i++;
4510 // mark as changed
4511 valueChanged = true;
4513 break;
4514 case MODE_STRING: {
4515 char *valPtr = eqptr;
4516 valPtr = skipSpace(valPtr);
4518 const unsigned int len = strlen(valPtr);
4519 const uint8_t min = val->config.string.minlength;
4520 const uint8_t max = val->config.string.maxlength;
4521 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4522 strlen((char *)cliGetValuePointer(val)) == 0 ||
4523 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4525 if (updatable && len > 0 && len <= max) {
4526 memset((char *)cliGetValuePointer(val), 0, max);
4527 if (len >= min && strncmp(valPtr, emptyName, len)) {
4528 strncpy((char *)cliGetValuePointer(val), valPtr, len);
4530 valueChanged = true;
4531 } else {
4532 cliPrintErrorLinef(cmdName, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4535 break;
4538 if (valueChanged) {
4539 cliPrintf("%s set to ", val->name);
4540 cliPrintVar(cmdName, val, 0);
4541 } else {
4542 cliPrintErrorLinef(cmdName, "INVALID VALUE");
4543 cliPrintVarRange(val);
4546 return;
4547 } else {
4548 // no equals, check for matching variables.
4549 cliGet(cmdName, cmdline);
4553 static const char *getMcuTypeById(mcuTypeId_e id)
4555 if (id < ARRAYLEN(mcuTypeNames)) {
4556 return mcuTypeNames[id];
4557 } else {
4558 return "UNKNOWN";
4562 static void cliStatus(const char *cmdName, char *cmdline)
4564 UNUSED(cmdName);
4565 UNUSED(cmdline);
4567 // MCU type, clock, vrefint, core temperature
4569 cliPrintf("MCU %s Clock=%dMHz", getMcuTypeById(getMcuTypeId()), (SystemCoreClock / 1000000));
4571 #if defined(STM32F4) || defined(STM32G4)
4572 // Only F4 and G4 is capable of switching between HSE/HSI (for now)
4573 int sysclkSource = SystemSYSCLKSource();
4575 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4576 const char *PLLSource[] = { "-HSI", "-HSE" };
4578 int pllSource;
4580 if (sysclkSource >= 2) {
4581 pllSource = SystemPLLSource();
4584 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4585 #endif
4587 #ifdef USE_ADC_INTERNAL
4588 uint16_t vrefintMv = getVrefMv();
4589 int16_t coretemp = getCoreTemperatureCelsius();
4590 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4591 #else
4592 cliPrintLinefeed();
4593 #endif
4595 // Stack and config sizes and usages
4597 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4598 #ifdef USE_STACK_CHECK
4599 cliPrintf(", Stack used: %d", stackUsedSize());
4600 #endif
4601 cliPrintLinefeed();
4603 cliPrintLinef("Configuration: %s, size: %d, max available: %d", configurationStates[systemConfigMutable()->configurationState], getEEPROMConfigSize(), getEEPROMStorageSize());
4605 // Devices
4606 #if defined(USE_SPI) || defined(USE_I2C)
4607 cliPrint("Devices detected:");
4608 #if defined(USE_SPI)
4609 cliPrintf(" SPI:%d", spiGetRegisteredDeviceCount());
4610 #if defined(USE_I2C)
4611 cliPrint(",");
4612 #endif
4613 #endif
4614 #if defined(USE_I2C)
4615 cliPrintf(" I2C:%d", i2cGetRegisteredDeviceCount());
4616 #endif
4617 cliPrintLinefeed();
4618 #endif
4620 // Sensors
4621 cliPrint("Gyros detected:");
4622 bool found = false;
4623 for (unsigned pos = 0; pos < 7; pos++) {
4624 if (gyroConfig()->gyrosDetected & BIT(pos)) {
4625 if (found) {
4626 cliPrint(",");
4627 } else {
4628 found = true;
4630 cliPrintf(" gyro %d", pos + 1);
4633 #ifdef USE_SPI
4634 if (gyroActiveDev()->gyroModeSPI != GYRO_EXTI_NO_INT) {
4635 cliPrintf(" locked");
4637 if (gyroActiveDev()->gyroModeSPI == GYRO_EXTI_INT_DMA) {
4638 cliPrintf(" dma");
4640 if (spiGetExtDeviceCount(&gyroActiveDev()->dev) > 1) {
4641 cliPrintf(" shared");
4643 #endif
4644 cliPrintLinefeed();
4646 #if defined(USE_SENSOR_NAMES)
4647 const uint32_t detectedSensorsMask = sensorsMask();
4648 for (uint32_t i = 0; ; i++) {
4649 if (sensorTypeNames[i] == NULL) {
4650 break;
4652 const uint32_t mask = (1 << i);
4653 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4654 const uint8_t sensorHardwareIndex = detectedSensors[i];
4655 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4656 if (i) {
4657 cliPrint(", ");
4659 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4660 #if defined(USE_ACC)
4661 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4662 cliPrintf(".%c", acc.dev.revisionCode);
4664 #endif
4667 cliPrintLinefeed();
4668 #endif /* USE_SENSOR_NAMES */
4670 #if defined(USE_OSD)
4671 osdDisplayPortDevice_e displayPortDeviceType;
4672 displayPort_t *osdDisplayPort = osdGetDisplayPort(&displayPortDeviceType);
4674 cliPrintLinef("OSD: %s (%u x %u)", lookupTableOsdDisplayPortDevice[displayPortDeviceType], osdDisplayPort->cols, osdDisplayPort->rows);
4675 #endif
4677 #ifdef BUILD_KEY
4678 cliPrintf("BUILD KEY: %s", buildKey);
4679 #ifdef RELEASE_NAME
4680 cliPrintf(" (%s)", STR(RELEASE_NAME));
4681 #endif
4682 cliPrintLinefeed();
4683 #endif
4685 // Uptime and wall clock
4687 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4689 #ifdef USE_RTC_TIME
4690 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4691 dateTime_t dt;
4692 if (rtcGetDateTime(&dt)) {
4693 dateTimeFormatLocal(buf, &dt);
4694 cliPrintf(", Current Time: %s", buf);
4696 #endif
4697 cliPrintLinefeed();
4699 // Run status
4701 const int gyroRate = getTaskDeltaTimeUs(TASK_GYRO) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_GYRO)));
4702 int rxRate = getCurrentRxIntervalUs();
4703 if (rxRate != 0) {
4704 rxRate = (int)(1000000.0f / ((float)rxRate));
4706 const int systemRate = getTaskDeltaTimeUs(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_SYSTEM)));
4707 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4708 constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE), getTaskDeltaTimeUs(TASK_GYRO), gyroRate, rxRate, systemRate);
4710 // Battery meter
4712 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4714 // Other devices and status
4716 #ifdef USE_I2C
4717 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4718 #else
4719 const uint16_t i2cErrorCounter = 0;
4720 #endif
4721 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4723 #ifdef USE_SDCARD
4724 cliSdInfo(cmdName, "");
4725 #endif
4727 #ifdef USE_FLASH_CHIP
4728 const flashGeometry_t *layout = flashGetGeometry();
4729 if (layout->jedecId != 0) {
4730 cliPrintLinef("FLASH: JEDEC ID=0x%08x %uM", layout->jedecId, layout->totalSize >> 20);
4732 #endif
4734 cliPrint("Arming disable flags:");
4735 armingDisableFlags_e flags = getArmingDisableFlags();
4736 while (flags) {
4737 const int bitpos = ffs(flags) - 1;
4738 flags &= ~(1 << bitpos);
4739 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4741 cliPrintLinefeed();
4744 static void cliTasks(const char *cmdName, char *cmdline)
4746 UNUSED(cmdName);
4747 UNUSED(cmdline);
4748 int averageLoadSum = 0;
4750 #ifndef MINIMAL_CLI
4751 if (systemConfig()->task_statistics) {
4752 #if defined(USE_LATE_TASK_STATISTICS)
4753 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms late run reqd/us");
4754 #else
4755 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4756 #endif
4757 } else {
4758 cliPrintLine("Task list");
4760 #endif
4761 for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4762 taskInfo_t taskInfo;
4763 getTaskInfo(taskId, &taskInfo);
4764 if (taskInfo.isEnabled) {
4765 int taskFrequency = taskInfo.averageDeltaTime10thUs == 0 ? 0 : lrintf(1e7f / taskInfo.averageDeltaTime10thUs);
4766 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4767 const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 : (taskInfo.maxExecutionTimeUs * taskFrequency) / 1000;
4768 const int averageLoad = taskInfo.averageExecutionTime10thUs == 0 ? 0 : (taskInfo.averageExecutionTime10thUs * taskFrequency) / 10000;
4769 if (taskId != TASK_SERIAL) {
4770 averageLoadSum += averageLoad;
4772 if (systemConfig()->task_statistics) {
4773 #if defined(USE_LATE_TASK_STATISTICS)
4774 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d %6d %6d %7d",
4775 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
4776 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4777 taskInfo.totalExecutionTimeUs / 1000,
4778 taskInfo.lateCount, taskInfo.runCount, taskInfo.execTime);
4779 #else
4780 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4781 taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
4782 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
4783 taskInfo.totalExecutionTimeUs / 1000);
4784 #endif
4785 } else {
4786 cliPrintLinef("%6d", taskFrequency);
4789 schedulerResetTaskMaxExecutionTime(taskId);
4792 if (systemConfig()->task_statistics) {
4793 cfCheckFuncInfo_t checkFuncInfo;
4794 getCheckFuncInfo(&checkFuncInfo);
4795 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTimeUs, checkFuncInfo.averageExecutionTimeUs, checkFuncInfo.totalExecutionTimeUs / 1000);
4796 cliPrintLinef("Total (excluding SERIAL) %33d.%1d%%", averageLoadSum/10, averageLoadSum%10);
4797 if (debugMode == DEBUG_SCHEDULER_DETERMINISM) {
4798 extern int32_t schedLoopStartCycles, taskGuardCycles;
4800 cliPrintLinef("Scheduler start cycles %d guard cycles %d", schedLoopStartCycles, taskGuardCycles);
4802 schedulerResetCheckFunctionMaxExecutionTime();
4806 static void printVersion(bool printBoardInfo)
4808 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4809 FC_FIRMWARE_NAME,
4810 targetName,
4811 systemConfig()->boardIdentifier,
4812 FC_VERSION_STRING,
4813 buildDate,
4814 buildTime,
4815 shortGitRevision,
4816 MSP_API_VERSION_STRING
4819 cliPrintLinefeed();
4821 #if defined(USE_BOARD_INFO)
4822 if (printBoardInfo && strlen(getManufacturerId()) && strlen(getBoardName())) {
4823 cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
4825 #else
4826 UNUSED(printBoardInfo);
4827 #endif
4830 static void cliVersion(const char *cmdName, char *cmdline)
4832 UNUSED(cmdName);
4833 UNUSED(cmdline);
4835 printVersion(true);
4838 #ifdef USE_RC_SMOOTHING_FILTER
4839 static void cliRcSmoothing(const char *cmdName, char *cmdline)
4841 UNUSED(cmdName);
4842 UNUSED(cmdline);
4843 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
4844 cliPrint("# RC Smoothing Type: ");
4845 if (rxConfig()->rc_smoothing_mode) {
4846 cliPrintLine("FILTER");
4847 if (rcSmoothingAutoCalculate()) {
4848 const uint16_t smoothedRxRateHz = lrintf(rcSmoothingData->smoothedRxRateHz);
4849 cliPrint("# Detected Rx frequency: ");
4850 if (getCurrentRxIntervalUs() == 0) {
4851 cliPrintLine("NO SIGNAL");
4852 } else {
4853 cliPrintLinef("%dHz", smoothedRxRateHz);
4856 cliPrintf("# Active setpoint cutoff: %dhz ", rcSmoothingData->setpointCutoffFrequency);
4857 if (rcSmoothingData->setpointCutoffSetting) {
4858 cliPrintLine("(manual)");
4859 } else {
4860 cliPrintLine("(auto)");
4862 cliPrintf("# Active FF cutoff: %dhz ", rcSmoothingData->feedforwardCutoffFrequency);
4863 if (rcSmoothingData->feedforwardCutoffSetting) {
4864 cliPrintLine("(manual)");
4865 } else {
4866 cliPrintLine("(auto)");
4868 cliPrintf("# Active throttle cutoff: %dhz ", rcSmoothingData->throttleCutoffFrequency);
4869 if (rcSmoothingData->throttleCutoffSetting) {
4870 cliPrintLine("(manual)");
4871 } else {
4872 cliPrintLine("(auto)");
4874 } else {
4875 cliPrintLine("OFF");
4878 #endif // USE_RC_SMOOTHING_FILTER
4880 #if defined(USE_RESOURCE_MGMT)
4882 #define RESOURCE_VALUE_MAX_INDEX(x) ((x) == 0 ? 1 : (x))
4884 typedef struct {
4885 const uint8_t owner;
4886 pgn_t pgn;
4887 uint8_t stride;
4888 uint8_t offset;
4889 const uint8_t maxIndex;
4890 } cliResourceValue_t;
4892 // Handy macros for keeping the table tidy.
4893 // DEFS : Single entry
4894 // DEFA : Array of uint8_t (stride = 1)
4895 // DEFW : Wider stride case; array of structs.
4897 #define DEFS(owner, pgn, type, member) \
4898 { owner, pgn, 0, offsetof(type, member), 0 }
4900 #define DEFA(owner, pgn, type, member, max) \
4901 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
4903 #define DEFW(owner, pgn, type, member, max) \
4904 { owner, pgn, sizeof(type), offsetof(type, member), max }
4906 const cliResourceValue_t resourceTable[] = {
4907 #if defined(USE_BEEPER)
4908 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
4909 #endif
4910 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
4911 #if defined(USE_SERVOS)
4912 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
4913 #endif
4914 #if defined(USE_RX_PPM)
4915 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
4916 #endif
4917 #if defined(USE_RX_PWM)
4918 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
4919 #endif
4920 #if defined(USE_RANGEFINDER_HCSR04)
4921 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
4922 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
4923 #endif
4924 #if defined(USE_LED_STRIP)
4925 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
4926 #endif
4927 #ifdef USE_UART
4928 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
4929 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
4930 #endif
4931 #ifdef USE_INVERTER
4932 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
4933 #endif
4934 #ifdef USE_I2C
4935 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
4936 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
4937 #endif
4938 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
4939 #ifdef USE_SPEKTRUM_BIND
4940 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
4941 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
4942 #endif
4943 #ifdef USE_TRANSPONDER
4944 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
4945 #endif
4946 #ifdef USE_SPI
4947 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
4948 DEFW( OWNER_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
4949 DEFW( OWNER_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
4950 #endif
4951 #ifdef USE_ESCSERIAL
4952 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
4953 #endif
4954 #ifdef USE_CAMERA_CONTROL
4955 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
4956 #endif
4957 #ifdef USE_ADC
4958 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
4959 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
4960 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
4961 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
4962 #endif
4963 #ifdef USE_BARO
4964 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
4965 DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
4966 DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
4967 #endif
4968 #ifdef USE_MAG
4969 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
4970 #ifdef USE_MAG_DATA_READY_SIGNAL
4971 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
4972 #endif
4973 #endif
4974 #ifdef USE_SDCARD_SPI
4975 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
4976 #endif
4977 #ifdef USE_SDCARD
4978 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
4979 #endif
4980 #if defined(STM32H7) && defined(USE_SDCARD_SDIO)
4981 DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
4982 DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
4983 DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
4984 DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
4985 DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
4986 DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
4987 #endif
4988 #ifdef USE_PINIO
4989 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
4990 #endif
4991 #if defined(USE_USB_MSC)
4992 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
4993 #endif
4994 #ifdef USE_FLASH_CHIP
4995 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
4996 #endif
4997 #ifdef USE_MAX7456
4998 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
4999 #endif
5000 #ifdef USE_RX_SPI
5001 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
5002 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
5003 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
5004 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
5005 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
5006 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
5007 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
5008 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
5009 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
5010 #endif
5011 #endif
5012 #if defined(USE_RX_EXPRESSLRS)
5013 DEFS( OWNER_RX_SPI_EXPRESSLRS_RESET, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, resetIoTag ),
5014 DEFS( OWNER_RX_SPI_EXPRESSLRS_BUSY, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, busyIoTag ),
5015 #endif
5016 #endif
5017 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
5018 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
5019 #ifdef USE_USB_DETECT
5020 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
5021 #endif
5022 #ifdef USE_VTX_RTC6705
5023 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
5024 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
5025 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
5026 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
5027 #endif
5028 #ifdef USE_PIN_PULL_UP_DOWN
5029 DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5030 DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
5031 #endif
5034 #undef DEFS
5035 #undef DEFA
5036 #undef DEFW
5038 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
5040 const pgRegistry_t* rec = pgFind(value.pgn);
5041 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
5044 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
5046 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5047 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
5048 const char* owner = ownerNames[resourceTable[i].owner];
5049 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
5050 const void *currentConfig;
5051 const void *defaultConfig;
5052 if (isReadingConfigFromCopy()) {
5053 currentConfig = pg->copy;
5054 defaultConfig = pg->address;
5055 } else {
5056 currentConfig = pg->address;
5057 defaultConfig = NULL;
5060 for (int index = 0; index < RESOURCE_VALUE_MAX_INDEX(resourceTable[i].maxIndex); index++) {
5061 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5062 ioTag_t ioTagDefault = 0;
5063 if (defaultConfig) {
5064 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
5067 const bool equalsDefault = ioTag == ioTagDefault;
5068 const char *format = "resource %s %d %c%02d";
5069 const char *formatUnassigned = "resource %s %d NONE";
5070 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5071 if (ioTagDefault) {
5072 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
5073 } else if (defaultConfig) {
5074 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5076 if (ioTag) {
5077 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
5078 } else if (!(dumpMask & HIDE_UNUSED)) {
5079 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
5085 static void printResourceOwner(uint8_t owner, uint8_t index)
5087 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
5089 if (resourceTable[owner].maxIndex > 0) {
5090 cliPrintf(" %d", RESOURCE_INDEX(index));
5094 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
5096 if (!newTag) {
5097 return;
5100 const char * format = "\r\nNOTE: %c%02d already assigned to ";
5101 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
5102 for (int i = 0; i < RESOURCE_VALUE_MAX_INDEX(resourceTable[r].maxIndex); i++) {
5103 ioTag_t *tag = getIoTag(resourceTable[r], i);
5104 if (*tag == newTag) {
5105 bool cleared = false;
5106 if (r == resourceIndex) {
5107 if (i == index) {
5108 continue;
5110 *tag = IO_TAG_NONE;
5111 cleared = true;
5114 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
5116 printResourceOwner(r, i);
5118 if (cleared) {
5119 cliPrintf(". ");
5120 printResourceOwner(r, i);
5121 cliPrintf(" disabled");
5124 cliPrintLine(".");
5130 static bool strToPin(char *ptr, ioTag_t *tag)
5132 if (strcasecmp(ptr, "NONE") == 0) {
5133 *tag = IO_TAG_NONE;
5135 return true;
5136 } else {
5137 const unsigned port = (*ptr >= 'a') ? *ptr - 'a' : *ptr - 'A';
5138 if (port < 8) {
5139 ptr++;
5141 char *end;
5142 const long pin = strtol(ptr, &end, 10);
5143 if (end != ptr && pin >= 0 && pin < 16) {
5144 *tag = DEFIO_TAG_MAKE(port, pin);
5146 return true;
5151 return false;
5154 #ifdef USE_DMA
5155 static void showDma(void)
5157 cliPrintLinefeed();
5159 #ifdef MINIMAL_CLI
5160 cliPrintLine("DMA:");
5161 #else
5162 cliPrintLine("Currently active DMA:");
5163 cliRepeat('-', 20);
5164 #endif
5165 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
5166 const resourceOwner_t *owner = dmaGetOwner(i);
5168 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
5169 if (owner->resourceIndex > 0) {
5170 cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
5171 } else {
5172 cliPrintLinef(" %s", ownerNames[owner->owner]);
5176 #endif
5178 #ifdef USE_DMA_SPEC
5180 typedef struct dmaoptEntry_s {
5181 char *device;
5182 dmaPeripheral_e peripheral;
5183 pgn_t pgn;
5184 uint8_t stride;
5185 uint8_t offset;
5186 uint8_t maxIndex;
5187 uint32_t presenceMask;
5188 } dmaoptEntry_t;
5190 #define MASK_IGNORED (0)
5192 // Handy macros for keeping the table tidy.
5193 // DEFS : Single entry
5194 // DEFA : Array of uint8_t (stride = 1)
5195 // DEFW : Wider stride case; array of structs.
5197 #define DEFS(device, peripheral, pgn, type, member) \
5198 { device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
5200 #define DEFA(device, peripheral, pgn, type, member, max, mask) \
5201 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
5203 #define DEFW(device, peripheral, pgn, type, member, max, mask) \
5204 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max, mask }
5206 dmaoptEntry_t dmaoptEntryTable[] = {
5207 DEFW("SPI_SDO", DMA_PERIPH_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5208 DEFW("SPI_SDI", DMA_PERIPH_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5209 // SPI_TX/SPI_RX for backwards compatibility with unified configs defined for 4.2.x
5210 DEFW("SPI_TX", DMA_PERIPH_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5211 DEFW("SPI_RX", DMA_PERIPH_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
5212 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
5213 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
5214 DEFW("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5215 DEFW("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, UARTDEV_CONFIG_MAX, MASK_IGNORED),
5216 #if defined(STM32H7) || defined(STM32G4)
5217 DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
5218 #endif
5221 #undef DEFS
5222 #undef DEFA
5223 #undef DEFW
5225 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
5226 #define DMA_OPT_STRING_BUFSIZE 5
5228 #if defined(STM32H7) || defined(STM32G4)
5229 #define DMA_CHANREQ_STRING "Request"
5230 #else
5231 #define DMA_CHANREQ_STRING "Channel"
5232 #endif
5234 #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
5235 #define DMA_STCH_STRING "Stream"
5236 #else
5237 #define DMA_STCH_STRING "Channel"
5238 #endif
5240 #define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
5242 static void optToString(int optval, char *buf)
5244 if (optval == DMA_OPT_UNUSED) {
5245 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
5246 } else {
5247 tfp_sprintf(buf, "%d", optval);
5251 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5253 // We compute number to display for different peripherals in advance.
5254 // This is done to deal with TIMUP which numbered non-contiguously.
5255 // Note that using timerGetNumberByIndex is not a generic solution,
5256 // but we are lucky that TIMUP is the only peripheral with non-contiguous numbering.
5258 int uiIndex;
5260 if (entry->presenceMask) {
5261 uiIndex = timerGetNumberByIndex(index);
5262 } else {
5263 uiIndex = DMA_OPT_UI_INDEX(index);
5266 if (dmaopt != DMA_OPT_UNUSED) {
5267 printValue(dumpMask, equalsDefault,
5268 "dma %s %d %d",
5269 entry->device, uiIndex, dmaopt);
5271 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
5272 dmaCode_t dmaCode = 0;
5273 if (dmaChannelSpec) {
5274 dmaCode = dmaChannelSpec->code;
5276 printValue(dumpMask, equalsDefault,
5277 "# %s %d: " DMASPEC_FORMAT_STRING,
5278 entry->device, uiIndex, DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
5279 } else if (!(dumpMask & HIDE_UNUSED)) {
5280 printValue(dumpMask, equalsDefault,
5281 "dma %s %d NONE",
5282 entry->device, uiIndex);
5286 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
5288 const pgRegistry_t* pg = pgFind(entry->pgn);
5289 const void *currentConfig;
5290 const void *defaultConfig;
5292 if (isReadingConfigFromCopy()) {
5293 currentConfig = pg->copy;
5294 defaultConfig = pg->address;
5295 } else {
5296 currentConfig = pg->address;
5297 defaultConfig = NULL;
5300 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5301 dmaoptValue_t defaultOpt;
5303 if (defaultConfig) {
5304 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
5305 } else {
5306 defaultOpt = DMA_OPT_UNUSED;
5309 bool equalsDefault = currentOpt == defaultOpt;
5310 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5312 if (defaultConfig) {
5313 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5316 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
5317 return headingStr;
5320 #if defined(USE_TIMER_MGMT)
5321 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5323 const char *format = "dma pin %c%02d %d";
5325 if (dmaopt != DMA_OPT_UNUSED) {
5326 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5327 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5328 dmaopt
5331 if (printDetails) {
5332 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
5333 dmaCode_t dmaCode = 0;
5334 if (dmaChannelSpec) {
5335 dmaCode = dmaChannelSpec->code;
5336 printValue(dumpMask, false,
5337 "# pin %c%02d: " DMASPEC_FORMAT_STRING,
5338 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5339 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
5343 } else if (!(dumpMask & HIDE_UNUSED)) {
5344 printValue(dumpMask, equalsDefault,
5345 "dma pin %c%02d NONE",
5346 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
5351 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
5353 const ioTag_t ioTag = currentConfig[index].ioTag;
5355 if (!ioTag) {
5356 return headingStr;
5359 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
5360 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
5362 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
5363 bool equalsDefault = defaultDmaopt == dmaopt;
5364 if (defaultConfig) {
5365 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5366 if (defaultConfig[i].ioTag == ioTag) {
5367 defaultDmaopt = defaultConfig[i].dmaopt;
5369 // 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.
5370 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
5372 tagsInUse[index] = true;
5374 break;
5379 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5381 if (defaultConfig) {
5382 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
5385 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
5386 return headingStr;
5388 #endif
5390 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
5392 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5393 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5394 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
5395 for (int index = 0; index < entry->maxIndex; index++) {
5396 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
5400 #if defined(USE_TIMER_MGMT)
5401 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5402 const timerIOConfig_t *currentConfig;
5403 const timerIOConfig_t *defaultConfig;
5405 if (isReadingConfigFromCopy()) {
5406 currentConfig = (timerIOConfig_t *)pg->copy;
5407 defaultConfig = (timerIOConfig_t *)pg->address;
5408 } else {
5409 currentConfig = (timerIOConfig_t *)pg->address;
5410 defaultConfig = NULL;
5413 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5414 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5415 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
5418 if (defaultConfig) {
5419 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5420 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
5421 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
5422 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
5423 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
5425 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
5429 #endif
5432 static void cliDmaopt(const char *cmdName, char *cmdline)
5434 char *pch = NULL;
5435 char *saveptr;
5437 // Peripheral name or command option
5438 pch = strtok_r(cmdline, " ", &saveptr);
5439 if (!pch) {
5440 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5442 return;
5443 } else if (strcasecmp(pch, "list") == 0) {
5444 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5446 return;
5449 dmaoptEntry_t *entry = NULL;
5450 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5451 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5452 entry = &dmaoptEntryTable[i];
5456 if (!entry && strcasecmp(pch, "pin") != 0) {
5457 cliPrintErrorLinef(cmdName, "BAD DEVICE: %s", pch);
5458 return;
5461 // Index
5462 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5464 int index = 0;
5465 dmaoptValue_t *optaddr = NULL;
5467 ioTag_t ioTag = IO_TAG_NONE;
5468 #if defined(USE_TIMER_MGMT)
5469 timerIOConfig_t *timerIoConfig = NULL;
5470 #endif
5471 const timerHardware_t *timer = NULL;
5472 pch = strtok_r(NULL, " ", &saveptr);
5473 if (entry) {
5474 index = pch ? (atoi(pch) - 1) : -1;
5475 if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
5476 cliPrintErrorLinef(cmdName, "BAD INDEX: '%s'", pch ? pch : "");
5477 return;
5480 const pgRegistry_t* pg = pgFind(entry->pgn);
5481 const void *currentConfig;
5482 if (isWritingConfigToCopy()) {
5483 currentConfig = pg->copy;
5484 } else {
5485 currentConfig = pg->address;
5487 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5488 orgval = *optaddr;
5489 } else {
5490 // It's a pin
5491 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5492 cliPrintErrorLinef(cmdName, "INVALID PIN: '%s'", pch ? pch : "");
5494 return;
5497 orgval = dmaoptByTag(ioTag);
5498 #if defined(USE_TIMER_MGMT)
5499 timerIoConfig = timerIoConfigByTag(ioTag);
5500 #endif
5501 timer = timerGetConfiguredByTag(ioTag);
5504 // opt or list
5505 pch = strtok_r(NULL, " ", &saveptr);
5506 if (!pch) {
5507 if (entry) {
5508 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5510 #if defined(USE_TIMER_MGMT)
5511 else {
5512 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5514 #endif
5516 return;
5517 } else if (strcasecmp(pch, "list") == 0) {
5518 // Show possible opts
5519 const dmaChannelSpec_t *dmaChannelSpec;
5520 if (entry) {
5521 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5522 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5524 } else {
5525 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5526 cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5530 return;
5531 } else if (pch) {
5532 int optval;
5533 if (strcasecmp(pch, "none") == 0) {
5534 optval = DMA_OPT_UNUSED;
5535 } else {
5536 optval = atoi(pch);
5538 if (entry) {
5539 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5540 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5542 return;
5544 } else {
5545 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5546 cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5548 return;
5553 char optvalString[DMA_OPT_STRING_BUFSIZE];
5554 optToString(optval, optvalString);
5556 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5557 optToString(orgval, orgvalString);
5559 if (optval != orgval) {
5560 if (entry) {
5561 *optaddr = optval;
5563 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5564 } else {
5565 #if defined(USE_TIMER_MGMT)
5566 timerIoConfig->dmaopt = optval;
5567 #endif
5569 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5571 } else {
5572 if (entry) {
5573 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5574 } else {
5575 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5580 #endif // USE_DMA_SPEC
5582 #ifdef USE_DMA
5583 static void cliDma(const char *cmdName, char* cmdline)
5585 int len = strlen(cmdline);
5586 if (len && strncasecmp(cmdline, "show", len) == 0) {
5587 showDma();
5589 return;
5592 #if defined(USE_DMA_SPEC)
5593 cliDmaopt(cmdName, cmdline);
5594 #else
5595 cliShowParseError(cmdName);
5596 #endif
5598 #endif
5599 #endif // USE_RESOURCE_MGMT
5601 #ifdef USE_TIMER_MGMT
5602 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5604 const char *format = "timer %c%02d AF%d";
5605 const char *emptyFormat = "timer %c%02d NONE";
5607 if (timerIndex > 0) {
5608 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5609 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5610 IO_GPIOPortIdxByTag(ioTag) + 'A',
5611 IO_GPIOPinIdxByTag(ioTag),
5612 timer->alternateFunction
5614 if (printDetails) {
5615 printValue(dumpMask, false,
5616 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5617 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5618 timerGetTIMNumber(timer->tim),
5619 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5620 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5621 timer->alternateFunction
5624 } else {
5625 printValue(dumpMask, equalsDefault, emptyFormat,
5626 IO_GPIOPortIdxByTag(ioTag) + 'A',
5627 IO_GPIOPinIdxByTag(ioTag)
5632 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5634 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5635 const timerIOConfig_t *currentConfig;
5636 const timerIOConfig_t *defaultConfig;
5638 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5639 if (isReadingConfigFromCopy()) {
5640 currentConfig = (timerIOConfig_t *)pg->copy;
5641 defaultConfig = (timerIOConfig_t *)pg->address;
5642 } else {
5643 currentConfig = (timerIOConfig_t *)pg->address;
5644 defaultConfig = NULL;
5647 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5648 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5649 const ioTag_t ioTag = currentConfig[i].ioTag;
5651 if (!ioTag) {
5652 continue;
5655 const uint8_t timerIndex = currentConfig[i].index;
5657 uint8_t defaultTimerIndex = 0;
5658 if (defaultConfig) {
5659 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5660 if (defaultConfig[i].ioTag == ioTag) {
5661 defaultTimerIndex = defaultConfig[i].index;
5662 tagsInUse[i] = true;
5664 break;
5669 const bool equalsDefault = defaultTimerIndex == timerIndex;
5670 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5671 if (defaultConfig && defaultTimerIndex) {
5672 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5675 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5678 if (defaultConfig) {
5679 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5680 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5681 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5682 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5684 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5690 #define TIMER_INDEX_UNDEFINED -1
5691 #define TIMER_AF_STRING_BUFSIZE 5
5693 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5695 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5696 if (!timer) {
5697 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5698 } else {
5699 tfp_sprintf(buf, "AF%d", timer->alternateFunction);
5703 #ifdef USE_TIMER_MAP_PRINT
5704 static void showTimerMap(void)
5706 cliPrintLinefeed();
5707 cliPrintLine("Timer Mapping:");
5708 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5709 const ioTag_t ioTag = timerIOConfig(i)->ioTag;
5711 if (!ioTag) {
5712 continue;
5715 cliPrintLinef(" TIMER_PIN_MAP(%d, P%c%d, %d, %d)",
5717 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5718 timerIOConfig(i)->index,
5719 timerIOConfig(i)->dmaopt
5723 #endif
5725 static void showTimers(void)
5727 cliPrintLinefeed();
5729 #ifdef MINIMAL_CLI
5730 cliPrintLine("Timers:");
5731 #else
5732 cliPrintLine("Currently active Timers:");
5733 cliRepeat('-', 23);
5734 #endif
5736 int8_t timerNumber;
5737 for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
5738 cliPrintf("TIM%d:", timerNumber);
5739 bool timerUsed = false;
5740 for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
5741 const timerHardware_t *timer = timerGetAllocatedByNumberAndChannel(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
5742 const resourceOwner_t *timerOwner = timerGetOwner(timer);
5743 if (timerOwner->owner) {
5744 if (!timerUsed) {
5745 timerUsed = true;
5747 cliPrintLinefeed();
5750 if (timerOwner->resourceIndex > 0) {
5751 cliPrintLinef(" CH%d%s: %s %d", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner], timerOwner->resourceIndex);
5752 } else {
5753 cliPrintLinef(" CH%d%s: %s", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner]);
5758 if (!timerUsed) {
5759 cliPrintLine(" FREE");
5764 static void cliTimer(const char *cmdName, char *cmdline)
5766 int len = strlen(cmdline);
5768 if (len == 0) {
5769 printTimer(DUMP_MASTER, NULL);
5771 return;
5772 } else if (strncasecmp(cmdline, "list", len) == 0) {
5773 cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
5775 return;
5776 #ifdef USE_TIMER_MAP_PRINT
5777 } else if (strncasecmp(cmdline, "map", len) == 0) {
5778 showTimerMap();
5780 return;
5781 #endif
5782 } else if (strncasecmp(cmdline, "show", len) == 0) {
5783 showTimers();
5785 return;
5788 char *pch = NULL;
5789 char *saveptr;
5791 ioTag_t ioTag = IO_TAG_NONE;
5792 pch = strtok_r(cmdline, " ", &saveptr);
5793 if (!pch || !strToPin(pch, &ioTag)) {
5794 cliShowParseError(cmdName);
5796 return;
5797 } else if (!IOGetByTag(ioTag)) {
5798 cliPrintErrorLinef(cmdName, "PIN NOT USED ON BOARD.");
5800 return;
5803 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5804 bool isExistingTimerOpt = false;
5805 /* find existing entry, or go for next available */
5806 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5807 if (timerIOConfig(i)->ioTag == ioTag) {
5808 timerIOIndex = i;
5809 isExistingTimerOpt = true;
5811 break;
5814 /* first available empty slot */
5815 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5816 timerIOIndex = i;
5820 if (timerIOIndex < 0) {
5821 cliPrintErrorLinef(cmdName, "PIN TIMER MAP FULL.");
5823 return;
5826 pch = strtok_r(NULL, " ", &saveptr);
5827 if (pch) {
5828 int timerIndex = TIMER_INDEX_UNDEFINED;
5829 if (strcasecmp(pch, "list") == 0) {
5830 /* output the list of available options */
5831 const timerHardware_t *timer;
5832 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5833 cliPrintLinef("# AF%d: TIM%d CH%d%s",
5834 timer->alternateFunction,
5835 timerGetTIMNumber(timer->tim),
5836 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5837 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
5841 return;
5842 } else if (strncasecmp(pch, "af", 2) == 0) {
5843 unsigned alternateFunction = atoi(&pch[2]);
5845 const timerHardware_t *timer;
5846 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5847 if (timer->alternateFunction == alternateFunction) {
5848 timerIndex = index;
5850 break;
5854 if (!timer) {
5855 cliPrintErrorLinef(cmdName, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5857 return;
5859 } else if (strcasecmp(pch, "none") != 0) {
5860 cliPrintErrorLinef(cmdName, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5862 return;
5865 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
5866 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
5867 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
5868 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
5870 char optvalString[DMA_OPT_STRING_BUFSIZE];
5871 alternateFunctionToString(ioTag, timerIndex, optvalString);
5873 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5874 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
5876 if (timerIndex == oldTimerIndex) {
5877 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
5878 } else {
5879 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5882 return;
5883 } else {
5884 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
5886 return;
5889 #endif
5891 #if defined(USE_RESOURCE_MGMT)
5892 static void cliResource(const char *cmdName, char *cmdline)
5894 char *pch = NULL;
5895 char *saveptr;
5897 pch = strtok_r(cmdline, " ", &saveptr);
5898 if (!pch) {
5899 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
5901 return;
5902 } else if (strcasecmp(pch, "show") == 0) {
5903 #ifdef MINIMAL_CLI
5904 cliPrintLine("IO");
5905 #else
5906 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
5907 cliRepeat('-', 20);
5908 #endif
5909 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
5910 const char* owner;
5911 owner = ownerNames[ioRecs[i].owner];
5913 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
5914 if (ioRecs[i].index > 0) {
5915 cliPrintf(" %d", ioRecs[i].index);
5917 cliPrintLinefeed();
5920 pch = strtok_r(NULL, " ", &saveptr);
5921 if (strcasecmp(pch, "all") == 0) {
5922 #if defined(USE_TIMER_MGMT)
5923 cliTimer(cmdName, "show");
5924 #endif
5925 #if defined(USE_DMA)
5926 cliDma(cmdName, "show");
5927 #endif
5930 return;
5933 unsigned resourceIndex = 0;
5934 for (; ; resourceIndex++) {
5935 if (resourceIndex >= ARRAYLEN(resourceTable)) {
5936 cliPrintErrorLinef(cmdName, "INVALID RESOURCE NAME: '%s'", pch);
5937 return;
5940 const char *resourceName = ownerNames[resourceTable[resourceIndex].owner];
5941 if (strncasecmp(pch, resourceName, strlen(resourceName)) == 0) {
5942 break;
5946 pch = strtok_r(NULL, " ", &saveptr);
5947 int index = atoi(pch);
5949 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
5950 if (index <= 0 || index > RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex)) {
5951 cliShowArgumentRangeError(cmdName, "INDEX", 1, RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex));
5952 return;
5954 index -= 1;
5956 pch = strtok_r(NULL, " ", &saveptr);
5959 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
5961 if (strlen(pch) > 0) {
5962 if (strToPin(pch, tag)) {
5963 if (*tag == IO_TAG_NONE) {
5964 #ifdef MINIMAL_CLI
5965 cliPrintLine("Freed");
5966 #else
5967 cliPrintLine("Resource is freed");
5968 #endif
5969 return;
5970 } else {
5971 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
5972 if (rec) {
5973 resourceCheck(resourceIndex, index, *tag);
5974 #ifdef MINIMAL_CLI
5975 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5976 #else
5977 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5978 #endif
5979 } else {
5980 cliShowParseError(cmdName);
5982 return;
5987 cliShowParseError(cmdName);
5989 #endif
5991 #ifdef USE_DSHOT_TELEMETRY
5994 static void cliDshotTelemetryInfo(const char *cmdName, char *cmdline)
5996 UNUSED(cmdName);
5997 UNUSED(cmdline);
5999 if (useDshotTelemetry) {
6000 cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
6001 cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
6002 int32_t directionChangeCycles = cmp32(dshotDMAHandlerCycleCounters.changeDirectionCompletedAt, dshotDMAHandlerCycleCounters.irqAt);
6003 int32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
6004 cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
6005 cliPrintLinefeed();
6007 #ifdef USE_DSHOT_TELEMETRY_STATS
6008 cliPrintLine("Motor Type eRPM RPM Hz Invalid TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6009 cliPrintLine("===== ====== ====== ====== ====== ======= ====== ====== ====== ====== ====== ====== ======");
6010 #else
6011 cliPrintLine("Motor Type eRPM RPM Hz TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
6012 cliPrintLine("===== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======");
6013 #endif
6015 for (uint8_t i = 0; i < getMotorCount(); i++) {
6016 const uint16_t erpm = getDshotTelemetry(i);
6017 const uint16_t rpm = erpmToRpm(erpm);
6019 cliPrintf("%5d %c%c%c%c%c %6d %6d %6d",
6020 i + 1,
6021 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_eRPM)) ? 'R' : '-'),
6022 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE)) ? 'T' : '-'),
6023 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_VOLTAGE)) ? 'V' : '-'),
6024 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_CURRENT)) ? 'C' : '-'),
6025 ((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_STATE_EVENTS)) ? 'S' : '-'),
6026 erpm * 100, rpm, rpm / 60);
6028 #ifdef USE_DSHOT_TELEMETRY_STATS
6029 if (isDshotMotorTelemetryActive(i)) {
6030 int32_t calcPercent = getDshotTelemetryMotorInvalidPercent(i);
6031 cliPrintf(" %3d.%02d%%", calcPercent / 100, calcPercent % 100);
6032 } else {
6033 cliPrint(" NO DATA");
6035 #endif
6037 cliPrintLinef(" %6d %3d.%02d %6d %6d %6d %6d %6d",
6038 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_TEMPERATURE],
6039 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] / 4,
6040 25 * (dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] % 4),
6041 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_CURRENT],
6042 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_STATE_EVENTS],
6043 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG1],
6044 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG2],
6045 dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG3]
6048 cliPrintLinefeed();
6050 const int len = MAX_GCR_EDGES;
6051 #ifdef DEBUG_BBDECODE
6052 extern uint16_t bbBuffer[134];
6053 for (int i = 0; i < 134; i++) {
6054 cliPrintf("%u ", (int)bbBuffer[i]);
6056 cliPrintLinefeed();
6057 #endif
6058 for (int i = 0; i < len; i++) {
6059 cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
6061 cliPrintLinefeed();
6062 for (int i = 1; i < len; i++) {
6063 cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
6065 cliPrintLinefeed();
6066 } else {
6067 cliPrintLine("Dshot telemetry not enabled");
6071 #endif
6073 static void printConfig(const char *cmdName, char *cmdline, bool doDiff)
6075 dumpFlags_t dumpMask = DUMP_MASTER;
6076 char *options;
6077 if ((options = checkCommand(cmdline, "master"))) {
6078 dumpMask = DUMP_MASTER; // only
6079 } else if ((options = checkCommand(cmdline, "profile"))) {
6080 dumpMask = DUMP_PROFILE; // only
6081 } else if ((options = checkCommand(cmdline, "rates"))) {
6082 dumpMask = DUMP_RATES; // only
6083 } else if ((options = checkCommand(cmdline, "hardware"))) {
6084 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
6085 } else if ((options = checkCommand(cmdline, "all"))) {
6086 dumpMask = DUMP_ALL; // all profiles and rates
6087 } else {
6088 options = cmdline;
6091 if (doDiff) {
6092 dumpMask = dumpMask | DO_DIFF;
6095 if (checkCommand(options, "defaults")) {
6096 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
6097 } else if (checkCommand(options, "bare")) {
6098 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
6101 backupAndResetConfigs();
6103 #ifdef USE_CLI_BATCH
6104 bool batchModeEnabled = false;
6105 #endif
6106 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
6107 cliPrintHashLine("version");
6108 printVersion(false);
6110 if (!(dumpMask & BARE)) {
6111 #ifdef USE_CLI_BATCH
6112 cliPrintHashLine("start the command batch");
6113 cliPrintLine("batch start");
6114 batchModeEnabled = true;
6115 #endif
6117 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
6118 cliPrintHashLine("reset configuration to default settings");
6119 cliPrintLine("defaults nosave");
6123 #if defined(USE_BOARD_INFO)
6124 cliPrintLinefeed();
6125 printBoardName(dumpMask);
6126 printManufacturerId(dumpMask);
6127 #endif
6129 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
6130 cliMcuId(cmdName, "");
6131 #if defined(USE_SIGNATURE)
6132 cliSignature(cmdName, "");
6133 #endif
6136 if (!(dumpMask & HARDWARE_ONLY)) {
6137 printCraftName(dumpMask, &pilotConfig_Copy);
6140 #ifdef USE_RESOURCE_MGMT
6141 printResource(dumpMask, "resources");
6142 #if defined(USE_TIMER_MGMT)
6143 printTimer(dumpMask, "timer");
6144 #endif
6145 #ifdef USE_DMA_SPEC
6146 printDmaopt(dumpMask, "dma");
6147 #endif
6148 #endif
6150 printFeature(dumpMask, featureConfig_Copy.enabledFeatures, featureConfig()->enabledFeatures, "feature");
6152 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
6154 if (!(dumpMask & HARDWARE_ONLY)) {
6155 #ifndef USE_QUAD_MIXER_ONLY
6156 const char *mixerHeadingStr = "mixer";
6157 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
6158 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
6159 const char *formatMixer = "mixer %s";
6160 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
6161 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
6163 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
6165 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
6167 #ifdef USE_SERVOS
6168 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
6170 const char *servoMixHeadingStr = "servo mixer";
6171 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
6172 cliPrintHashLine(servoMixHeadingStr);
6173 cliPrintLine("smix reset\r\n");
6174 servoMixHeadingStr = NULL;
6176 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
6177 #endif
6178 #endif
6180 #if defined(USE_BEEPER)
6181 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
6183 #if defined(USE_DSHOT)
6184 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
6185 #endif
6186 #endif // USE_BEEPER
6188 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
6190 #ifdef USE_LED_STRIP_STATUS_MODE
6191 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
6193 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
6195 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
6196 #endif
6198 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
6200 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
6202 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
6204 #ifdef USE_VTX_TABLE
6205 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
6206 #endif
6208 #ifdef USE_VTX_CONTROL
6209 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
6210 #endif
6212 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
6215 if (dumpMask & HARDWARE_ONLY) {
6216 dumpAllValues(cmdName, HARDWARE_VALUE, dumpMask, "master");
6217 } else {
6218 dumpAllValues(cmdName, MASTER_VALUE, dumpMask, "master");
6220 if (dumpMask & DUMP_ALL) {
6221 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
6222 cliDumpPidProfile(cmdName, pidProfileIndex, dumpMask);
6225 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
6227 if (!(dumpMask & BARE)) {
6228 cliPrintHashLine("restore original profile selection");
6230 cliProfile(cmdName, "");
6233 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
6235 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
6236 cliDumpRateProfile(cmdName, rateIndex, dumpMask);
6239 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
6241 if (!(dumpMask & BARE)) {
6242 cliPrintHashLine("restore original rateprofile selection");
6244 cliRateProfile(cmdName, "");
6246 cliPrintHashLine("save configuration");
6247 cliPrint("save");
6248 #ifdef USE_CLI_BATCH
6249 batchModeEnabled = false;
6250 #endif
6253 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
6254 } else {
6255 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6257 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6260 } else if (dumpMask & DUMP_PROFILE) {
6261 cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
6262 } else if (dumpMask & DUMP_RATES) {
6263 cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
6266 #ifdef USE_CLI_BATCH
6267 if (batchModeEnabled) {
6268 cliPrintHashLine("end the command batch");
6269 cliPrintLine("batch end");
6271 #endif
6273 // restore configs from copies
6274 restoreConfigs(0);
6277 static void cliDump(const char *cmdName, char *cmdline)
6279 printConfig(cmdName, cmdline, false);
6282 static void cliDiff(const char *cmdName, char *cmdline)
6284 printConfig(cmdName, cmdline, true);
6287 #if defined(USE_USB_MSC)
6288 static void cliMsc(const char *cmdName, char *cmdline)
6290 if (mscCheckFilesystemReady()) {
6291 #ifdef USE_RTC_TIME
6292 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
6293 if (!isEmpty(cmdline)) {
6294 timezoneOffsetMinutes = atoi(cmdline);
6295 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
6296 cliPrintErrorLinef(cmdName, "INVALID TIMEZONE OFFSET");
6297 return;
6300 #else
6301 int timezoneOffsetMinutes = 0;
6302 UNUSED(cmdline);
6303 #endif
6304 cliPrintHashLine("Restarting in mass storage mode");
6305 cliPrint("\r\nRebooting");
6306 cliWriterFlush();
6307 waitForSerialPortToFinishTransmitting(cliPort);
6308 motorShutdown();
6310 systemResetToMsc(timezoneOffsetMinutes);
6311 } else {
6312 cliPrintHashLine("Storage not present or failed to initialize!");
6315 #endif
6317 typedef void cliCommandFn(const char* name, char *cmdline);
6319 typedef struct {
6320 const char *name;
6321 #ifndef MINIMAL_CLI
6322 const char *description;
6323 const char *args;
6324 #endif
6325 cliCommandFn *cliCommand;
6326 } clicmd_t;
6328 #ifndef MINIMAL_CLI
6329 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6331 name , \
6332 description , \
6333 args , \
6334 cliCommand \
6336 #else
6337 #define CLI_COMMAND_DEF(name, description, args, cliCommand) \
6339 name, \
6340 cliCommand \
6342 #endif
6344 static void cliHelp(const char *cmdName, char *cmdline);
6346 // should be sorted a..z for bsearch()
6347 const clicmd_t cmdTable[] = {
6348 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
6349 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
6350 #ifdef USE_CLI_BATCH
6351 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
6352 #endif
6353 #if defined(USE_BEEPER)
6354 #if defined(USE_DSHOT)
6355 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
6356 "\t<->[name]", cliBeacon),
6357 #endif
6358 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
6359 "\t<->[name]", cliBeeper),
6360 #endif // USE_BEEPER
6361 #if defined(USE_RX_BIND)
6362 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
6363 #endif
6364 #if defined(USE_FLASH_BOOT_LOADER)
6365 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
6366 #else
6367 CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
6368 #endif
6369 #if defined(USE_BOARD_INFO)
6370 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
6371 #endif
6372 #ifdef USE_LED_STRIP_STATUS_MODE
6373 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
6374 #endif
6375 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{nosave}", cliDefaults),
6376 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
6377 #ifdef USE_RESOURCE_MGMT
6379 #ifdef USE_DMA
6380 #ifdef USE_DMA_SPEC
6381 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
6382 #else
6383 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
6384 #endif
6385 #endif
6387 #endif
6388 #ifdef USE_DSHOT_TELEMETRY
6389 CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
6390 #endif
6391 #ifdef USE_DSHOT
6392 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
6393 #endif
6394 CLI_COMMAND_DEF("dump", "dump configuration",
6395 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
6396 #ifdef USE_ESCSERIAL
6397 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
6398 #endif
6399 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
6400 CLI_COMMAND_DEF("feature", "configure features",
6401 "list\r\n"
6402 "\t<->[name]", cliFeature),
6403 #ifdef USE_FLASH_CHIP
6404 #ifdef USE_FLASHFS
6405 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
6406 #endif
6407 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
6408 #if defined(USE_FLASH_TOOLS) && defined(USE_FLASHFS)
6409 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
6410 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
6411 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
6412 #endif
6413 #endif
6414 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
6415 #ifdef USE_GPS
6416 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
6417 #endif
6418 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
6419 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
6420 #endif
6421 CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp),
6422 #ifdef USE_LED_STRIP_STATUS_MODE
6423 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
6424 #endif
6425 #if defined(USE_BOARD_INFO)
6426 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
6427 #endif
6428 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
6429 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
6430 #ifndef USE_QUAD_MIXER_ONLY
6431 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
6432 #endif
6433 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
6434 #ifdef USE_LED_STRIP_STATUS_MODE
6435 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
6436 #endif
6437 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
6438 #ifdef USE_USB_MSC
6439 #ifdef USE_RTC_TIME
6440 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
6441 #else
6442 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
6443 #endif
6444 #endif
6445 #ifndef MINIMAL_CLI
6446 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
6447 #endif
6448 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
6449 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
6450 #ifdef USE_RC_SMOOTHING_FILTER
6451 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
6452 #endif // USE_RC_SMOOTHING_FILTER
6453 #ifdef USE_RESOURCE_MGMT
6454 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
6455 #endif
6456 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
6457 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
6458 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
6459 #ifdef USE_SDCARD
6460 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
6461 #endif
6462 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
6463 #if defined(USE_SERIAL_PASSTHROUGH)
6464 #if defined(USE_PINIO)
6465 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|<dtr pinio>|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6466 #else
6467 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
6468 #endif
6469 #endif
6470 #ifdef USE_SERVOS
6471 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
6472 #endif
6473 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
6474 #if defined(USE_SIGNATURE)
6475 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
6476 #endif
6477 #if defined(USE_SIMPLIFIED_TUNING)
6478 CLI_COMMAND_DEF("simplified_tuning", "applies or disables simplified tuning", "apply | disable", cliSimplifiedTuning),
6479 #endif
6480 #ifdef USE_SERVOS
6481 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
6482 "\treset\r\n"
6483 "\tload <mixer>\r\n"
6484 "\treverse <servo> <source> r|n", cliServoMix),
6485 #endif
6486 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
6487 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
6488 #ifdef USE_TIMER_MGMT
6489 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
6490 #endif
6491 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
6492 #ifdef USE_VTX_CONTROL
6493 #ifdef MINIMAL_CLI
6494 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
6495 #else
6496 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
6497 #endif
6498 #endif
6499 #ifdef USE_VTX_TABLE
6500 CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL, cliVtxInfo),
6501 CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
6502 #endif
6505 static void cliHelp(const char *cmdName, char *cmdline)
6507 bool anyMatches = false;
6509 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
6510 bool printEntry = false;
6511 if (isEmpty(cmdline)) {
6512 printEntry = true;
6513 } else {
6514 if (strcasestr(cmdTable[i].name, cmdline)
6515 #ifndef MINIMAL_CLI
6516 || strcasestr(cmdTable[i].description, cmdline)
6517 #endif
6519 printEntry = true;
6523 if (printEntry) {
6524 anyMatches = true;
6525 cliPrint(cmdTable[i].name);
6526 #ifndef MINIMAL_CLI
6527 if (cmdTable[i].description) {
6528 cliPrintf(" - %s", cmdTable[i].description);
6530 if (cmdTable[i].args) {
6531 cliPrintf("\r\n\t%s", cmdTable[i].args);
6533 #endif
6534 cliPrintLinefeed();
6537 if (!isEmpty(cmdline) && !anyMatches) {
6538 cliPrintErrorLinef(cmdName, "NO MATCHES FOR '%s'", cmdline);
6542 static void processCharacter(const char c)
6544 if (bufferIndex && (c == '\n' || c == '\r')) {
6545 // enter pressed
6546 cliPrintLinefeed();
6548 // Strip comment starting with # from line
6549 char *p = cliBuffer;
6550 p = strchr(p, '#');
6551 if (NULL != p) {
6552 bufferIndex = (uint32_t)(p - cliBuffer);
6555 // Strip trailing whitespace
6556 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6557 bufferIndex--;
6560 // Process non-empty lines
6561 if (bufferIndex > 0) {
6562 cliBuffer[bufferIndex] = 0; // null terminate
6564 const clicmd_t *cmd;
6565 char *options;
6566 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6567 if ((options = checkCommand(cliBuffer, cmd->name))) {
6568 break;
6571 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6572 cmd->cliCommand(cmd->name, options);
6573 } else {
6574 cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
6576 bufferIndex = 0;
6579 memset(cliBuffer, 0, sizeof(cliBuffer));
6581 // 'exit' will reset this flag, so we don't need to print prompt again
6582 if (!cliMode) {
6583 return;
6586 cliPrompt();
6587 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6588 if (!bufferIndex && c == ' ')
6589 return; // Ignore leading spaces
6590 cliBuffer[bufferIndex++] = c;
6591 cliWrite(c);
6595 static void processCharacterInteractive(const char c)
6597 if (c == '\t' || c == '?') {
6598 // do tab completion
6599 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
6600 uint32_t i = bufferIndex;
6601 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6602 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) {
6603 continue;
6605 if (!pstart) {
6606 pstart = cmd;
6608 pend = cmd;
6610 if (pstart) { /* Buffer matches one or more commands */
6611 for (; ; bufferIndex++) {
6612 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
6613 break;
6614 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
6615 /* Unambiguous -- append a space */
6616 cliBuffer[bufferIndex++] = ' ';
6617 cliBuffer[bufferIndex] = '\0';
6618 break;
6620 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6623 if (!bufferIndex || pstart != pend) {
6624 /* Print list of ambiguous matches */
6625 cliPrint("\r\n\033[K");
6626 for (cmd = pstart; cmd <= pend; cmd++) {
6627 cliPrint(cmd->name);
6628 cliWrite('\t');
6630 cliPrompt();
6631 i = 0; /* Redraw prompt */
6633 for (; i < bufferIndex; i++)
6634 cliWrite(cliBuffer[i]);
6635 } else if (!bufferIndex && c == 4) { // CTRL-D
6636 cliExit("", cliBuffer);
6637 return;
6638 } else if (c == 12) { // NewPage / CTRL-L
6639 // clear screen
6640 cliPrint("\033[2J\033[1;1H");
6641 cliPrompt();
6642 } else if (c == 127) {
6643 // backspace
6644 if (bufferIndex) {
6645 cliBuffer[--bufferIndex] = 0;
6646 cliPrint("\010 \010");
6648 } else {
6649 processCharacter(c);
6653 void cliProcess(void)
6655 if (!cliWriter) {
6656 return;
6659 // Flush the buffer to get rid of any MSP data polls sent by configurator after CLI was invoked
6660 cliWriterFlush();
6662 while (serialRxBytesWaiting(cliPort)) {
6663 uint8_t c = serialRead(cliPort);
6665 processCharacterInteractive(c);
6669 void cliEnter(serialPort_t *serialPort)
6671 cliMode = true;
6672 cliPort = serialPort;
6673 setPrintfSerialPort(cliPort);
6674 bufWriterInit(&cliWriterDesc, cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6675 cliErrorWriter = cliWriter = &cliWriterDesc;
6677 #ifndef MINIMAL_CLI
6678 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6679 #else
6680 cliPrintLine("\r\nCLI");
6681 #endif
6682 setArmingDisabled(ARMING_DISABLED_CLI);
6684 cliPrompt();
6686 #ifdef USE_CLI_BATCH
6687 resetCommandBatch();
6688 #endif
6691 #endif // USE_CLI