Changed 'timer' output to list timers based on alternate function.
[betaflight.git] / src / main / cli / cli.c
blob5381de4d5d52d853cebfeedade0a59126f15f2d6
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 uint8_t cliMode = 0;
34 #ifndef EEPROM_IN_RAM
35 extern uint8_t __config_start; // configured via linker script when building binaries.
36 extern uint8_t __config_end;
37 #endif
39 #ifdef USE_CLI
41 #include "blackbox/blackbox.h"
43 #include "build/build_config.h"
44 #include "build/debug.h"
45 #include "build/version.h"
47 #include "cli/settings.h"
49 #include "cms/cms.h"
51 #include "common/axis.h"
52 #include "common/color.h"
53 #include "common/maths.h"
54 #include "common/printf.h"
55 #include "common/printf_serial.h"
56 #include "common/strtol.h"
57 #include "common/time.h"
58 #include "common/typeconversion.h"
59 #include "common/utils.h"
61 #include "config/config_eeprom.h"
62 #include "config/feature.h"
64 #include "drivers/accgyro/accgyro.h"
65 #include "drivers/adc.h"
66 #include "drivers/buf_writer.h"
67 #include "drivers/bus_spi.h"
68 #include "drivers/dma_reqmap.h"
69 #include "drivers/camera_control.h"
70 #include "drivers/compass/compass.h"
71 #include "drivers/display.h"
72 #include "drivers/dma.h"
73 #include "drivers/flash.h"
74 #include "drivers/inverter.h"
75 #include "drivers/io.h"
76 #include "drivers/io_impl.h"
77 #include "drivers/light_led.h"
78 #include "drivers/pwm_output.h"
79 #include "drivers/rangefinder/rangefinder_hcsr04.h"
80 #include "drivers/sdcard.h"
81 #include "drivers/sensor.h"
82 #include "drivers/serial.h"
83 #include "drivers/serial_escserial.h"
84 #include "drivers/sound_beeper.h"
85 #include "drivers/stack_check.h"
86 #include "drivers/system.h"
87 #include "drivers/time.h"
88 #include "drivers/timer.h"
89 #include "drivers/transponder_ir.h"
90 #include "drivers/usb_msc.h"
91 #include "drivers/vtx_common.h"
92 #include "drivers/vtx_table.h"
94 #include "fc/board_info.h"
95 #include "fc/config.h"
96 #include "fc/controlrate_profile.h"
97 #include "fc/core.h"
98 #include "fc/rc.h"
99 #include "fc/rc_adjustments.h"
100 #include "fc/rc_controls.h"
101 #include "fc/runtime_config.h"
103 #include "flight/failsafe.h"
104 #include "flight/imu.h"
105 #include "flight/mixer.h"
106 #include "flight/pid.h"
107 #include "flight/position.h"
108 #include "flight/servos.h"
110 #include "io/asyncfatfs/asyncfatfs.h"
111 #include "io/beeper.h"
112 #include "io/flashfs.h"
113 #include "io/gimbal.h"
114 #include "io/gps.h"
115 #include "io/ledstrip.h"
116 #include "io/serial.h"
117 #include "io/transponder_ir.h"
118 #include "io/usb_msc.h"
119 #include "io/vtx_control.h"
120 #include "io/vtx.h"
122 #include "msp/msp.h"
123 #include "msp/msp_box.h"
124 #include "msp/msp_protocol.h"
126 #include "osd/osd.h"
128 #include "pg/adc.h"
129 #include "pg/beeper.h"
130 #include "pg/beeper_dev.h"
131 #include "pg/board.h"
132 #include "pg/bus_i2c.h"
133 #include "pg/bus_spi.h"
134 #include "pg/gyrodev.h"
135 #include "pg/max7456.h"
136 #include "pg/mco.h"
137 #include "pg/pinio.h"
138 #include "pg/pg.h"
139 #include "pg/pg_ids.h"
140 #include "pg/rx.h"
141 #include "pg/rx_spi_cc2500.h"
142 #include "pg/rx_spi.h"
143 #include "pg/rx_pwm.h"
144 #include "pg/serial_uart.h"
145 #include "pg/sdio.h"
146 #include "pg/timerio.h"
147 #include "pg/usb.h"
148 #include "pg/vtx_table.h"
150 #include "rx/rx.h"
151 #include "rx/spektrum.h"
152 #include "rx/rx_spi_common.h"
154 #include "scheduler/scheduler.h"
156 #include "sensors/acceleration.h"
157 #include "sensors/adcinternal.h"
158 #include "sensors/barometer.h"
159 #include "sensors/battery.h"
160 #include "sensors/boardalignment.h"
161 #include "sensors/compass.h"
162 #include "sensors/esc_sensor.h"
163 #include "sensors/gyro.h"
164 #include "sensors/sensors.h"
166 #include "telemetry/frsky_hub.h"
167 #include "telemetry/telemetry.h"
169 #include "cli.h"
171 static serialPort_t *cliPort;
173 #ifdef STM32F1
174 #define CLI_IN_BUFFER_SIZE 128
175 #else
176 // Space required to set array parameters
177 #define CLI_IN_BUFFER_SIZE 256
178 #endif
179 #define CLI_OUT_BUFFER_SIZE 64
181 static bufWriter_t *cliWriter;
182 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
184 static char cliBuffer[CLI_IN_BUFFER_SIZE];
185 static uint32_t bufferIndex = 0;
187 static bool configIsInCopy = false;
189 #define CURRENT_PROFILE_INDEX -1
190 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
191 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
193 static bool featureMaskIsCopied = false;
194 static uint32_t featureMaskCopy;
196 #ifdef USE_CLI_BATCH
197 static bool commandBatchActive = false;
198 static bool commandBatchError = false;
199 #endif
201 #if defined(USE_BOARD_INFO)
202 static bool boardInformationUpdated = false;
203 #if defined(USE_SIGNATURE)
204 static bool signatureUpdated = false;
205 #endif
206 #endif // USE_BOARD_INFO
208 static const char* const emptyName = "-";
209 static const char* const emptyString = "";
211 #ifndef USE_QUAD_MIXER_ONLY
212 // sync this with mixerMode_e
213 static const char * const mixerNames[] = {
214 "TRI", "QUADP", "QUADX", "BI",
215 "GIMBAL", "Y6", "HEX6",
216 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
217 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
218 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
219 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
221 #endif
223 // sync this with features_e
224 static const char * const featureNames[] = {
225 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
226 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
227 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
228 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
229 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
230 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
233 // sync this with rxFailsafeChannelMode_e
234 static const char rxFailsafeModeCharacters[] = "ahs";
236 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
237 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
238 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
241 #if defined(USE_SENSOR_NAMES)
242 // sync this with sensors_e
243 static const char * const sensorTypeNames[] = {
244 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
247 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
249 static const char * const *sensorHardwareNames[] = {
250 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
252 #endif // USE_SENSOR_NAMES
254 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
255 extern uint32_t readDoneCount;
256 extern uint32_t inputBuffer[DSHOT_TELEMETRY_INPUT_LEN];
257 extern uint32_t setDirectionMicros;
258 #endif
260 typedef enum dumpFlags_e {
261 DUMP_MASTER = (1 << 0),
262 DUMP_PROFILE = (1 << 1),
263 DUMP_RATES = (1 << 2),
264 DUMP_ALL = (1 << 3),
265 DO_DIFF = (1 << 4),
266 SHOW_DEFAULTS = (1 << 5),
267 HIDE_UNUSED = (1 << 6),
268 HARDWARE_ONLY = (1 << 7),
269 BARE = (1 << 8),
270 } dumpFlags_t;
272 typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
275 static void backupPgConfig(const pgRegistry_t *pg)
277 memcpy(pg->copy, pg->address, pg->size);
280 static void restorePgConfig(const pgRegistry_t *pg)
282 memcpy(pg->address, pg->copy, pg->size);
285 static void backupConfigs(void)
287 // make copies of configs to do differencing
288 PG_FOREACH(pg) {
289 backupPgConfig(pg);
292 configIsInCopy = true;
295 static void restoreConfigs(void)
297 PG_FOREACH(pg) {
298 restorePgConfig(pg);
301 configIsInCopy = false;
304 static void backupAndResetConfigs(void)
306 backupConfigs();
307 // reset all configs to defaults to do differencing
308 resetConfigs();
311 static void cliPrint(const char *str)
313 while (*str) {
314 bufWriterAppend(cliWriter, *str++);
316 bufWriterFlush(cliWriter);
319 static void cliPrintLinefeed(void)
321 cliPrint("\r\n");
324 static void cliPrintLine(const char *str)
326 cliPrint(str);
327 cliPrintLinefeed();
330 #ifdef MINIMAL_CLI
331 #define cliPrintHashLine(str)
332 #else
333 static void cliPrintHashLine(const char *str)
335 cliPrint("\r\n# ");
336 cliPrintLine(str);
338 #endif
340 static void cliPutp(void *p, char ch)
342 bufWriterAppend(p, ch);
345 static void cliPrintfva(const char *format, va_list va)
347 tfp_format(cliWriter, cliPutp, format, va);
348 bufWriterFlush(cliWriter);
351 static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
353 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
354 va_list va;
355 va_start(va, format);
356 cliPrintfva(format, va);
357 va_end(va);
358 cliPrintLinefeed();
359 return true;
360 } else {
361 return false;
365 static void cliWrite(uint8_t ch)
367 bufWriterAppend(cliWriter, ch);
370 static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
372 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
373 cliWrite('#');
375 va_list va;
376 va_start(va, format);
377 cliPrintfva(format, va);
378 va_end(va);
379 cliPrintLinefeed();
380 return true;
381 } else {
382 return false;
386 static void cliPrintf(const char *format, ...)
388 va_list va;
389 va_start(va, format);
390 cliPrintfva(format, va);
391 va_end(va);
395 static void cliPrintLinef(const char *format, ...)
397 va_list va;
398 va_start(va, format);
399 cliPrintfva(format, va);
400 va_end(va);
401 cliPrintLinefeed();
404 static void cliPrintErrorVa(const char *format, va_list va)
406 cliPrint("###ERROR: ");
407 cliPrintfva(format, va);
408 va_end(va);
409 cliPrint("###");
411 #ifdef USE_CLI_BATCH
412 if (commandBatchActive) {
413 commandBatchError = true;
415 #endif
418 static void cliPrintError(const char *format, ...)
420 va_list va;
421 va_start(va, format);
422 cliPrintErrorVa(format, va);
425 static void cliPrintErrorLinef(const char *format, ...)
427 va_list va;
428 va_start(va, format);
429 cliPrintErrorVa(format, va);
430 cliPrintLinefeed();
433 static void getMinMax(const clivalue_t *var, int *min, int *max)
435 switch (var->type & VALUE_TYPE_MASK) {
436 case VAR_UINT8:
437 case VAR_UINT16:
438 *min = var->config.minmaxUnsigned.min;
439 *max = var->config.minmaxUnsigned.max;
441 break;
442 default:
443 *min = var->config.minmax.min;
444 *max = var->config.minmax.max;
446 break;
450 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
452 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
453 for (int i = 0; i < var->config.array.length; i++) {
454 switch (var->type & VALUE_TYPE_MASK) {
455 default:
456 case VAR_UINT8:
457 // uint8_t array
458 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
459 break;
461 case VAR_INT8:
462 // int8_t array
463 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
464 break;
466 case VAR_UINT16:
467 // uin16_t array
468 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
469 break;
471 case VAR_INT16:
472 // int16_t array
473 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
474 break;
477 if (i < var->config.array.length - 1) {
478 cliPrint(",");
481 } else {
482 int value = 0;
484 switch (var->type & VALUE_TYPE_MASK) {
485 case VAR_UINT8:
486 value = *(uint8_t *)valuePointer;
488 break;
489 case VAR_INT8:
490 value = *(int8_t *)valuePointer;
492 break;
493 case VAR_UINT16:
494 value = *(uint16_t *)valuePointer;
496 break;
497 case VAR_INT16:
498 value = *(int16_t *)valuePointer;
500 break;
501 case VAR_UINT32:
502 value = *(uint32_t *)valuePointer;
504 break;
507 bool valueIsCorrupted = false;
508 switch (var->type & VALUE_MODE_MASK) {
509 case MODE_DIRECT:
510 if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
511 cliPrintf("%u", (uint32_t)value);
512 if ((uint32_t)value > var->config.u32Max) {
513 valueIsCorrupted = true;
514 } else if (full) {
515 cliPrintf(" 0 %u", var->config.u32Max);
517 } else {
518 int min;
519 int max;
520 getMinMax(var, &min, &max);
522 cliPrintf("%d", value);
523 if ((value < min) || (value > max)) {
524 valueIsCorrupted = true;
525 } else if (full) {
526 cliPrintf(" %d %d", min, max);
529 break;
530 case MODE_LOOKUP:
531 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
532 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
533 } else {
534 valueIsCorrupted = true;
536 break;
537 case MODE_BITSET:
538 if (value & 1 << var->config.bitpos) {
539 cliPrintf("ON");
540 } else {
541 cliPrintf("OFF");
543 break;
544 case MODE_STRING:
545 cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
546 break;
549 if (valueIsCorrupted) {
550 cliPrintLinefeed();
551 cliPrintError("CORRUPTED CONFIG: %s = %d", var->name, value);
557 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
559 bool result = true;
560 int elementCount = 1;
561 uint32_t mask = 0xffffffff;
563 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
564 elementCount = var->config.array.length;
566 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
567 mask = 1 << var->config.bitpos;
569 for (int i = 0; i < elementCount; i++) {
570 switch (var->type & VALUE_TYPE_MASK) {
571 case VAR_UINT8:
572 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
573 break;
575 case VAR_INT8:
576 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
577 break;
579 case VAR_UINT16:
580 result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
581 break;
582 case VAR_INT16:
583 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
584 break;
585 case VAR_UINT32:
586 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
587 break;
591 return result;
594 static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
596 if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
597 cliPrintHashLine(headingStr);
598 return NULL;
599 } else {
600 return headingStr;
604 static uint8_t getPidProfileIndexToUse()
606 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
609 static uint8_t getRateProfileIndexToUse()
611 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
615 static uint16_t getValueOffset(const clivalue_t *value)
617 switch (value->type & VALUE_SECTION_MASK) {
618 case MASTER_VALUE:
619 case HARDWARE_VALUE:
620 return value->offset;
621 case PROFILE_VALUE:
622 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
623 case PROFILE_RATE_VALUE:
624 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
626 return 0;
629 void *cliGetValuePointer(const clivalue_t *value)
631 const pgRegistry_t* rec = pgFind(value->pgn);
632 if (configIsInCopy) {
633 return CONST_CAST(void *, rec->copy + getValueOffset(value));
634 } else {
635 return CONST_CAST(void *, rec->address + getValueOffset(value));
639 const void *cliGetDefaultPointer(const clivalue_t *value)
641 const pgRegistry_t* rec = pgFind(value->pgn);
642 return rec->address + getValueOffset(value);
645 static const char *dumpPgValue(const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
647 const pgRegistry_t *pg = pgFind(value->pgn);
648 #ifdef DEBUG
649 if (!pg) {
650 cliPrintLinef("VALUE %s ERROR", value->name);
651 return; // if it's not found, the pgn shouldn't be in the value table!
653 #endif
655 const char *format = "set %s = ";
656 const char *defaultFormat = "#set %s = ";
657 const int valueOffset = getValueOffset(value);
658 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
660 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
661 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
662 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
663 cliPrintf(defaultFormat, value->name);
664 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
665 cliPrintLinefeed();
667 cliPrintf(format, value->name);
668 printValuePointer(value, pg->copy + valueOffset, false);
669 cliPrintLinefeed();
671 return headingStr;
674 static void dumpAllValues(uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
676 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
678 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
679 const clivalue_t *value = &valueTable[i];
680 bufWriterFlush(cliWriter);
681 if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
682 headingStr = dumpPgValue(value, dumpMask, headingStr);
687 static void cliPrintVar(const clivalue_t *var, bool full)
689 const void *ptr = cliGetValuePointer(var);
691 printValuePointer(var, ptr, full);
694 static void cliPrintVarRange(const clivalue_t *var)
696 switch (var->type & VALUE_MODE_MASK) {
697 case (MODE_DIRECT): {
698 switch (var->type & VALUE_TYPE_MASK) {
699 case VAR_UINT32:
700 cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
702 break;
703 case VAR_UINT8:
704 case VAR_UINT16:
705 cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
707 break;
708 default:
709 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
711 break;
714 break;
715 case (MODE_LOOKUP): {
716 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
717 cliPrint("Allowed values: ");
718 bool firstEntry = true;
719 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
720 if (tableEntry->values[i]) {
721 if (!firstEntry) {
722 cliPrint(", ");
724 cliPrintf("%s", tableEntry->values[i]);
725 firstEntry = false;
728 cliPrintLinefeed();
730 break;
731 case (MODE_ARRAY): {
732 cliPrintLinef("Array length: %d", var->config.array.length);
734 break;
735 case (MODE_BITSET): {
736 cliPrintLinef("Allowed values: OFF, ON");
738 break;
742 static void cliSetVar(const clivalue_t *var, const uint32_t value)
744 void *ptr = cliGetValuePointer(var);
745 uint32_t workValue;
746 uint32_t mask;
748 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
749 switch (var->type & VALUE_TYPE_MASK) {
750 case VAR_UINT8:
751 mask = (1 << var->config.bitpos) & 0xff;
752 if (value) {
753 workValue = *(uint8_t *)ptr | mask;
754 } else {
755 workValue = *(uint8_t *)ptr & ~mask;
757 *(uint8_t *)ptr = workValue;
758 break;
760 case VAR_UINT16:
761 mask = (1 << var->config.bitpos) & 0xffff;
762 if (value) {
763 workValue = *(uint16_t *)ptr | mask;
764 } else {
765 workValue = *(uint16_t *)ptr & ~mask;
767 *(uint16_t *)ptr = workValue;
768 break;
770 case VAR_UINT32:
771 mask = 1 << var->config.bitpos;
772 if (value) {
773 workValue = *(uint32_t *)ptr | mask;
774 } else {
775 workValue = *(uint32_t *)ptr & ~mask;
777 *(uint32_t *)ptr = workValue;
778 break;
780 } else {
781 switch (var->type & VALUE_TYPE_MASK) {
782 case VAR_UINT8:
783 *(uint8_t *)ptr = value;
784 break;
786 case VAR_INT8:
787 *(int8_t *)ptr = value;
788 break;
790 case VAR_UINT16:
791 *(uint16_t *)ptr = value;
792 break;
794 case VAR_INT16:
795 *(int16_t *)ptr = value;
796 break;
798 case VAR_UINT32:
799 *(uint32_t *)ptr = value;
800 break;
805 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
806 static void cliRepeat(char ch, uint8_t len)
808 for (int i = 0; i < len; i++) {
809 bufWriterAppend(cliWriter, ch);
811 cliPrintLinefeed();
813 #endif
815 static void cliPrompt(void)
817 cliPrint("\r\n# ");
820 static void cliShowParseError(void)
822 cliPrintErrorLinef("PARSE ERROR");
825 static void cliShowArgumentRangeError(char *name, int min, int max)
827 cliPrintErrorLinef("%s NOT BETWEEN %d AND %d", name, min, max);
830 static const char *nextArg(const char *currentArg)
832 const char *ptr = strchr(currentArg, ' ');
833 while (ptr && *ptr == ' ') {
834 ptr++;
837 return ptr;
840 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
842 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
843 ptr = nextArg(ptr);
844 if (ptr) {
845 int val = atoi(ptr);
846 val = CHANNEL_VALUE_TO_STEP(val);
847 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
848 if (argIndex == 0) {
849 range->startStep = val;
850 } else {
851 range->endStep = val;
853 (*validArgumentCount)++;
858 return ptr;
861 // Check if a string's length is zero
862 static bool isEmpty(const char *string)
864 return (string == NULL || *string == '\0') ? true : false;
867 static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
869 // print out rxConfig failsafe settings
870 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
871 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
872 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
873 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
874 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
875 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
876 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
877 if (requireValue) {
878 const char *format = "rxfail %u %c %d";
879 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
880 channel,
881 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
882 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
884 cliDumpPrintLinef(dumpMask, equalsDefault, format,
885 channel,
886 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
887 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
889 } else {
890 const char *format = "rxfail %u %c";
891 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
892 channel,
893 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
895 cliDumpPrintLinef(dumpMask, equalsDefault, format,
896 channel,
897 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
903 static void cliRxFailsafe(char *cmdline)
905 uint8_t channel;
906 char buf[3];
908 if (isEmpty(cmdline)) {
909 // print out rxConfig failsafe settings
910 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
911 cliRxFailsafe(itoa(channel, buf, 10));
913 } else {
914 const char *ptr = cmdline;
915 channel = atoi(ptr++);
916 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
918 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
920 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
921 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
922 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
924 ptr = nextArg(ptr);
925 if (ptr) {
926 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
927 if (p) {
928 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
929 mode = rxFailsafeModesTable[type][requestedMode];
930 } else {
931 mode = RX_FAILSAFE_MODE_INVALID;
933 if (mode == RX_FAILSAFE_MODE_INVALID) {
934 cliShowParseError();
935 return;
938 requireValue = mode == RX_FAILSAFE_MODE_SET;
940 ptr = nextArg(ptr);
941 if (ptr) {
942 if (!requireValue) {
943 cliShowParseError();
944 return;
946 uint16_t value = atoi(ptr);
947 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
948 if (value > MAX_RXFAIL_RANGE_STEP) {
949 cliPrintLine("Value out of range");
950 return;
953 channelFailsafeConfig->step = value;
954 } else if (requireValue) {
955 cliShowParseError();
956 return;
958 channelFailsafeConfig->mode = mode;
961 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
963 // double use of cliPrintf below
964 // 1. acknowledge interpretation on command,
965 // 2. query current setting on single item,
967 if (requireValue) {
968 cliPrintLinef("rxfail %u %c %d",
969 channel,
970 modeCharacter,
971 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
973 } else {
974 cliPrintLinef("rxfail %u %c",
975 channel,
976 modeCharacter
979 } else {
980 cliShowArgumentRangeError("CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
985 static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
987 const char *format = "aux %u %u %u %u %u %u %u";
988 // print out aux channel settings
989 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
990 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
991 const modeActivationCondition_t *mac = &modeActivationConditions[i];
992 bool equalsDefault = false;
993 if (defaultModeActivationConditions) {
994 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
995 equalsDefault = !isModeActivationConditionConfigured(mac, macDefault);
996 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
997 const box_t *box = findBoxByBoxId(macDefault->modeId);
998 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
999 if (box) {
1000 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1002 box->permanentId,
1003 macDefault->auxChannelIndex,
1004 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
1005 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
1006 macDefault->modeLogic,
1007 linkedTo ? linkedTo->permanentId : 0
1011 const box_t *box = findBoxByBoxId(mac->modeId);
1012 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
1013 if (box) {
1014 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1016 box->permanentId,
1017 mac->auxChannelIndex,
1018 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1019 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1020 mac->modeLogic,
1021 linkedTo ? linkedTo->permanentId : 0
1027 static void cliAux(char *cmdline)
1029 int i, val = 0;
1030 const char *ptr;
1032 if (isEmpty(cmdline)) {
1033 printAux(DUMP_MASTER, modeActivationConditions(0), NULL, NULL);
1034 } else {
1035 ptr = cmdline;
1036 i = atoi(ptr++);
1037 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
1038 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
1039 uint8_t validArgumentCount = 0;
1040 ptr = nextArg(ptr);
1041 if (ptr) {
1042 val = atoi(ptr);
1043 const box_t *box = findBoxByPermanentId(val);
1044 if (box) {
1045 mac->modeId = box->boxId;
1046 validArgumentCount++;
1049 ptr = nextArg(ptr);
1050 if (ptr) {
1051 val = atoi(ptr);
1052 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1053 mac->auxChannelIndex = val;
1054 validArgumentCount++;
1057 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
1058 ptr = nextArg(ptr);
1059 if (ptr) {
1060 val = atoi(ptr);
1061 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
1062 mac->modeLogic = val;
1063 validArgumentCount++;
1066 ptr = nextArg(ptr);
1067 if (ptr) {
1068 val = atoi(ptr);
1069 const box_t *box = findBoxByPermanentId(val);
1070 if (box) {
1071 mac->linkedTo = box->boxId;
1072 validArgumentCount++;
1075 if (validArgumentCount == 4) { // for backwards compatibility
1076 mac->modeLogic = MODELOGIC_OR;
1077 mac->linkedTo = 0;
1078 } else if (validArgumentCount == 5) { // for backwards compatibility
1079 mac->linkedTo = 0;
1080 } else if (validArgumentCount != 6) {
1081 memset(mac, 0, sizeof(modeActivationCondition_t));
1083 analyzeModeActivationConditions();
1084 cliPrintLinef( "aux %u %u %u %u %u %u %u",
1086 findBoxByBoxId(mac->modeId)->permanentId,
1087 mac->auxChannelIndex,
1088 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
1089 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
1090 mac->modeLogic,
1091 findBoxByBoxId(mac->linkedTo)->permanentId
1093 } else {
1094 cliShowArgumentRangeError("INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
1099 static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
1101 const char *format = "serial %d %d %ld %ld %ld %ld";
1102 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1103 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
1104 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
1105 continue;
1107 bool equalsDefault = false;
1108 if (serialConfigDefault) {
1109 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
1110 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1111 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1112 serialConfigDefault->portConfigs[i].identifier,
1113 serialConfigDefault->portConfigs[i].functionMask,
1114 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
1115 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
1116 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
1117 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
1120 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1121 serialConfig->portConfigs[i].identifier,
1122 serialConfig->portConfigs[i].functionMask,
1123 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
1124 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
1125 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
1126 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1131 static void cliSerial(char *cmdline)
1133 const char *format = "serial %d %d %ld %ld %ld %ld";
1134 if (isEmpty(cmdline)) {
1135 printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
1136 return;
1138 serialPortConfig_t portConfig;
1139 memset(&portConfig, 0 , sizeof(portConfig));
1141 serialPortConfig_t *currentConfig;
1143 uint8_t validArgumentCount = 0;
1145 const char *ptr = cmdline;
1147 int val = atoi(ptr++);
1148 currentConfig = serialFindPortConfiguration(val);
1149 if (currentConfig) {
1150 portConfig.identifier = val;
1151 validArgumentCount++;
1154 ptr = nextArg(ptr);
1155 if (ptr) {
1156 val = atoi(ptr);
1157 portConfig.functionMask = val & 0xFFFF;
1158 validArgumentCount++;
1161 for (int i = 0; i < 4; i ++) {
1162 ptr = nextArg(ptr);
1163 if (!ptr) {
1164 break;
1167 val = atoi(ptr);
1169 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1170 if (baudRates[baudRateIndex] != (uint32_t) val) {
1171 break;
1174 switch (i) {
1175 case 0:
1176 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1177 continue;
1179 portConfig.msp_baudrateIndex = baudRateIndex;
1180 break;
1181 case 1:
1182 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1183 continue;
1185 portConfig.gps_baudrateIndex = baudRateIndex;
1186 break;
1187 case 2:
1188 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1189 continue;
1191 portConfig.telemetry_baudrateIndex = baudRateIndex;
1192 break;
1193 case 3:
1194 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1195 continue;
1197 portConfig.blackbox_baudrateIndex = baudRateIndex;
1198 break;
1201 validArgumentCount++;
1204 if (validArgumentCount < 6) {
1205 cliShowParseError();
1206 return;
1209 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1211 cliDumpPrintLinef(0, false, format,
1212 portConfig.identifier,
1213 portConfig.functionMask,
1214 baudRates[portConfig.msp_baudrateIndex],
1215 baudRates[portConfig.gps_baudrateIndex],
1216 baudRates[portConfig.telemetry_baudrateIndex],
1217 baudRates[portConfig.blackbox_baudrateIndex]
1222 #if defined(USE_SERIAL_PASSTHROUGH)
1223 static void cbCtrlLine(void *context, uint16_t ctrl)
1225 #ifdef USE_PINIO
1226 int contextValue = (int)(long)context;
1227 if (contextValue) {
1228 pinioSet(contextValue - 1, !(ctrl & CTRL_LINE_STATE_DTR));
1229 } else
1230 #endif /* USE_PINIO */
1231 UNUSED(context);
1233 if (!(ctrl & CTRL_LINE_STATE_DTR)) {
1234 systemReset();
1238 static void cliSerialPassthrough(char *cmdline)
1240 if (isEmpty(cmdline)) {
1241 cliShowParseError();
1242 return;
1245 int id = -1;
1246 uint32_t baud = 0;
1247 bool enableBaudCb = false;
1248 int pinioDtr = 0;
1249 bool resetOnDtr = false;
1250 unsigned mode = 0;
1251 char *saveptr;
1252 char* tok = strtok_r(cmdline, " ", &saveptr);
1253 int index = 0;
1255 while (tok != NULL) {
1256 switch (index) {
1257 case 0:
1258 id = atoi(tok);
1259 break;
1260 case 1:
1261 baud = atoi(tok);
1262 break;
1263 case 2:
1264 if (strcasestr(tok, "rx")) {
1265 mode |= MODE_RX;
1267 if (strcasestr(tok, "tx")) {
1268 mode |= MODE_TX;
1270 break;
1271 case 3:
1272 if (strncasecmp(tok, "reset", strlen(tok)) == 0) {
1273 resetOnDtr = true;
1274 #ifdef USE_PINIO
1275 } else {
1276 pinioDtr = atoi(tok);
1277 #endif /* USE_PINIO */
1280 break;
1282 index++;
1283 tok = strtok_r(NULL, " ", &saveptr);
1286 if (baud == 0) {
1287 enableBaudCb = true;
1290 cliPrintf("Port %d ", id);
1291 serialPort_t *passThroughPort;
1292 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
1293 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
1294 if (enableBaudCb) {
1295 // Set default baud
1296 baud = 57600;
1299 if (!mode) {
1300 mode = MODE_RXTX;
1303 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
1304 baud, mode,
1305 SERIAL_NOT_INVERTED);
1306 if (!passThroughPort) {
1307 cliPrintLine("could not be opened.");
1308 return;
1311 if (enableBaudCb) {
1312 cliPrintf("opened, default baud = %d.\r\n", baud);
1313 } else {
1314 cliPrintf("opened, baud = %d.\r\n", baud);
1316 } else {
1317 passThroughPort = passThroughPortUsage->serialPort;
1318 // If the user supplied a mode, override the port's mode, otherwise
1319 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1320 // Set the baud rate if specified
1321 if (baud) {
1322 cliPrintf("already open, setting baud = %d.\n\r", baud);
1323 serialSetBaudRate(passThroughPort, baud);
1324 } else {
1325 cliPrintf("already open, baud = %d.\n\r", passThroughPort->baudRate);
1328 if (mode && passThroughPort->mode != mode) {
1329 cliPrintf("Mode changed from %d to %d.\r\n",
1330 passThroughPort->mode, mode);
1331 serialSetMode(passThroughPort, mode);
1334 // If this port has a rx callback associated we need to remove it now.
1335 // Otherwise no data will be pushed in the serial port buffer!
1336 if (passThroughPort->rxCallback) {
1337 passThroughPort->rxCallback = 0;
1341 // If no baud rate is specified allow to be set via USB
1342 if (enableBaudCb) {
1343 cliPrintLine("Baud rate change over USB enabled.");
1344 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1345 // baud rate over USB without setting it using the serialpassthrough command
1346 serialSetBaudRateCb(cliPort, serialSetBaudRate, passThroughPort);
1349 char *resetMessage = "";
1350 if (resetOnDtr) {
1351 resetMessage = "or drop DTR ";
1354 cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
1356 if (resetOnDtr
1357 #ifdef USE_PINIO
1358 || pinioDtr
1359 #endif /* USE_PINIO */
1361 // Register control line state callback
1362 serialSetCtrlLineStateCb(cliPort, cbCtrlLine, (void *)(intptr_t)(pinioDtr));
1365 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
1367 #endif
1369 static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
1371 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1372 // print out adjustment ranges channel settings
1373 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1374 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1375 const adjustmentRange_t *ar = &adjustmentRanges[i];
1376 bool equalsDefault = false;
1377 if (defaultAdjustmentRanges) {
1378 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1379 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1380 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1381 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1383 arDefault->adjustmentIndex,
1384 arDefault->auxChannelIndex,
1385 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1386 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1387 arDefault->adjustmentConfig,
1388 arDefault->auxSwitchChannelIndex,
1389 arDefault->adjustmentCenter,
1390 arDefault->adjustmentScale
1393 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1395 ar->adjustmentIndex,
1396 ar->auxChannelIndex,
1397 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1398 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1399 ar->adjustmentConfig,
1400 ar->auxSwitchChannelIndex,
1401 ar->adjustmentCenter,
1402 ar->adjustmentScale
1407 static void cliAdjustmentRange(char *cmdline)
1409 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1410 int i, val = 0;
1411 const char *ptr;
1413 if (isEmpty(cmdline)) {
1414 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
1415 } else {
1416 ptr = cmdline;
1417 i = atoi(ptr++);
1418 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1419 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1420 uint8_t validArgumentCount = 0;
1422 ptr = nextArg(ptr);
1423 if (ptr) {
1424 val = atoi(ptr);
1425 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1426 ar->adjustmentIndex = val;
1427 validArgumentCount++;
1430 ptr = nextArg(ptr);
1431 if (ptr) {
1432 val = atoi(ptr);
1433 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1434 ar->auxChannelIndex = val;
1435 validArgumentCount++;
1439 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1441 ptr = nextArg(ptr);
1442 if (ptr) {
1443 val = atoi(ptr);
1444 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1445 ar->adjustmentConfig = val;
1446 validArgumentCount++;
1449 ptr = nextArg(ptr);
1450 if (ptr) {
1451 val = atoi(ptr);
1452 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1453 ar->auxSwitchChannelIndex = val;
1454 validArgumentCount++;
1458 if (validArgumentCount != 6) {
1459 memset(ar, 0, sizeof(adjustmentRange_t));
1460 cliShowParseError();
1461 return;
1464 // Optional arguments
1465 ar->adjustmentCenter = 0;
1466 ar->adjustmentScale = 0;
1468 ptr = nextArg(ptr);
1469 if (ptr) {
1470 val = atoi(ptr);
1471 ar->adjustmentCenter = val;
1472 validArgumentCount++;
1474 ptr = nextArg(ptr);
1475 if (ptr) {
1476 val = atoi(ptr);
1477 ar->adjustmentScale = val;
1478 validArgumentCount++;
1481 activeAdjustmentRangeReset();
1483 cliDumpPrintLinef(0, false, format,
1485 ar->adjustmentIndex,
1486 ar->auxChannelIndex,
1487 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1488 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1489 ar->adjustmentConfig,
1490 ar->auxSwitchChannelIndex,
1491 ar->adjustmentCenter,
1492 ar->adjustmentScale
1495 } else {
1496 cliShowArgumentRangeError("INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1501 #ifndef USE_QUAD_MIXER_ONLY
1502 static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
1504 const char *format = "mmix %d %s %s %s %s";
1505 char buf0[FTOA_BUFFER_LENGTH];
1506 char buf1[FTOA_BUFFER_LENGTH];
1507 char buf2[FTOA_BUFFER_LENGTH];
1508 char buf3[FTOA_BUFFER_LENGTH];
1509 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1510 if (customMotorMixer[i].throttle == 0.0f)
1511 break;
1512 const float thr = customMotorMixer[i].throttle;
1513 const float roll = customMotorMixer[i].roll;
1514 const float pitch = customMotorMixer[i].pitch;
1515 const float yaw = customMotorMixer[i].yaw;
1516 bool equalsDefault = false;
1517 if (defaultCustomMotorMixer) {
1518 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1519 const float rollDefault = defaultCustomMotorMixer[i].roll;
1520 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1521 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1522 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1524 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1525 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1527 ftoa(thrDefault, buf0),
1528 ftoa(rollDefault, buf1),
1529 ftoa(pitchDefault, buf2),
1530 ftoa(yawDefault, buf3));
1532 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1534 ftoa(thr, buf0),
1535 ftoa(roll, buf1),
1536 ftoa(pitch, buf2),
1537 ftoa(yaw, buf3));
1540 #endif // USE_QUAD_MIXER_ONLY
1542 static void cliMotorMix(char *cmdline)
1544 #ifdef USE_QUAD_MIXER_ONLY
1545 UNUSED(cmdline);
1546 #else
1547 int check = 0;
1548 uint8_t len;
1549 const char *ptr;
1551 if (isEmpty(cmdline)) {
1552 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1553 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1554 // erase custom mixer
1555 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1556 customMotorMixerMutable(i)->throttle = 0.0f;
1558 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1559 ptr = nextArg(cmdline);
1560 if (ptr) {
1561 len = strlen(ptr);
1562 for (uint32_t i = 0; ; i++) {
1563 if (mixerNames[i] == NULL) {
1564 cliPrintErrorLinef("INVALID NAME");
1565 break;
1567 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1568 mixerLoadMix(i, customMotorMixerMutable(0));
1569 cliPrintLinef("Loaded %s", mixerNames[i]);
1570 cliMotorMix("");
1571 break;
1575 } else {
1576 ptr = cmdline;
1577 uint32_t i = atoi(ptr); // get motor number
1578 if (i < MAX_SUPPORTED_MOTORS) {
1579 ptr = nextArg(ptr);
1580 if (ptr) {
1581 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1582 check++;
1584 ptr = nextArg(ptr);
1585 if (ptr) {
1586 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1587 check++;
1589 ptr = nextArg(ptr);
1590 if (ptr) {
1591 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1592 check++;
1594 ptr = nextArg(ptr);
1595 if (ptr) {
1596 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1597 check++;
1599 if (check != 4) {
1600 cliShowParseError();
1601 } else {
1602 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
1604 } else {
1605 cliShowArgumentRangeError("INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
1608 #endif
1611 static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
1613 const char *format = "rxrange %u %u %u";
1614 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1615 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1616 bool equalsDefault = false;
1617 if (defaultChannelRangeConfigs) {
1618 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1619 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1620 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1622 defaultChannelRangeConfigs[i].min,
1623 defaultChannelRangeConfigs[i].max
1626 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1628 channelRangeConfigs[i].min,
1629 channelRangeConfigs[i].max
1634 static void cliRxRange(char *cmdline)
1636 const char *format = "rxrange %u %u %u";
1637 int i, validArgumentCount = 0;
1638 const char *ptr;
1640 if (isEmpty(cmdline)) {
1641 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
1642 } else if (strcasecmp(cmdline, "reset") == 0) {
1643 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1644 } else {
1645 ptr = cmdline;
1646 i = atoi(ptr);
1647 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1648 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1650 ptr = nextArg(ptr);
1651 if (ptr) {
1652 rangeMin = atoi(ptr);
1653 validArgumentCount++;
1656 ptr = nextArg(ptr);
1657 if (ptr) {
1658 rangeMax = atoi(ptr);
1659 validArgumentCount++;
1662 if (validArgumentCount != 2) {
1663 cliShowParseError();
1664 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1665 cliShowParseError();
1666 } else {
1667 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1668 channelRangeConfig->min = rangeMin;
1669 channelRangeConfig->max = rangeMax;
1670 cliDumpPrintLinef(0, false, format,
1672 channelRangeConfig->min,
1673 channelRangeConfig->max
1677 } else {
1678 cliShowArgumentRangeError("CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
1683 #ifdef USE_LED_STRIP_STATUS_MODE
1684 static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
1686 const char *format = "led %u %s";
1687 char ledConfigBuffer[20];
1688 char ledConfigDefaultBuffer[20];
1689 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1690 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1691 ledConfig_t ledConfig = ledConfigs[i];
1692 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1693 bool equalsDefault = false;
1694 if (defaultLedConfigs) {
1695 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1696 equalsDefault = ledConfig == ledConfigDefault;
1697 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1698 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1699 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1701 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1705 static void cliLed(char *cmdline)
1707 const char *format = "led %u %s";
1708 char ledConfigBuffer[20];
1709 int i;
1710 const char *ptr;
1712 if (isEmpty(cmdline)) {
1713 printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
1714 } else {
1715 ptr = cmdline;
1716 i = atoi(ptr);
1717 if (i < LED_MAX_STRIP_LENGTH) {
1718 ptr = nextArg(cmdline);
1719 if (parseLedStripConfig(i, ptr)) {
1720 generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1721 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1722 } else {
1723 cliShowParseError();
1725 } else {
1726 cliShowArgumentRangeError("INDEX", 0, LED_MAX_STRIP_LENGTH - 1);
1731 static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
1733 const char *format = "color %u %d,%u,%u";
1734 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1735 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1736 const hsvColor_t *color = &colors[i];
1737 bool equalsDefault = false;
1738 if (defaultColors) {
1739 const hsvColor_t *colorDefault = &defaultColors[i];
1740 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1741 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1742 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1744 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1748 static void cliColor(char *cmdline)
1750 const char *format = "color %u %d,%u,%u";
1751 if (isEmpty(cmdline)) {
1752 printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
1753 } else {
1754 const char *ptr = cmdline;
1755 const int i = atoi(ptr);
1756 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1757 ptr = nextArg(cmdline);
1758 if (parseColor(i, ptr)) {
1759 const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
1760 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1761 } else {
1762 cliShowParseError();
1764 } else {
1765 cliShowArgumentRangeError("INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1770 static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
1772 const char *format = "mode_color %u %u %u";
1773 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1774 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1775 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1776 int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
1777 bool equalsDefault = false;
1778 if (defaultLedStripConfig) {
1779 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1780 equalsDefault = colorIndex == colorIndexDefault;
1781 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1782 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1784 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1788 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1789 const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
1790 bool equalsDefault = false;
1791 if (defaultLedStripConfig) {
1792 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1793 equalsDefault = colorIndex == colorIndexDefault;
1794 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1795 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1797 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1800 const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
1801 bool equalsDefault = false;
1802 if (defaultLedStripConfig) {
1803 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1804 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1805 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1806 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1808 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1811 static void cliModeColor(char *cmdline)
1813 if (isEmpty(cmdline)) {
1814 printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
1815 } else {
1816 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1817 int args[ARGS_COUNT];
1818 int argNo = 0;
1819 char *saveptr;
1820 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1821 while (ptr && argNo < ARGS_COUNT) {
1822 args[argNo++] = atoi(ptr);
1823 ptr = strtok_r(NULL, " ", &saveptr);
1826 if (ptr != NULL || argNo != ARGS_COUNT) {
1827 cliShowParseError();
1828 return;
1831 int modeIdx = args[MODE];
1832 int funIdx = args[FUNCTION];
1833 int color = args[COLOR];
1834 if (!setModeColor(modeIdx, funIdx, color)) {
1835 cliShowParseError();
1836 return;
1838 // values are validated
1839 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1842 #endif
1844 #ifdef USE_SERVOS
1845 static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
1847 // print out servo settings
1848 const char *format = "servo %u %d %d %d %d %d";
1849 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1850 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1851 const servoParam_t *servoConf = &servoParams[i];
1852 bool equalsDefault = false;
1853 if (defaultServoParams) {
1854 const servoParam_t *defaultServoConf = &defaultServoParams[i];
1855 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
1856 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
1857 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1859 defaultServoConf->min,
1860 defaultServoConf->max,
1861 defaultServoConf->middle,
1862 defaultServoConf->rate,
1863 defaultServoConf->forwardFromChannel
1866 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1868 servoConf->min,
1869 servoConf->max,
1870 servoConf->middle,
1871 servoConf->rate,
1872 servoConf->forwardFromChannel
1875 // print servo directions
1876 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1877 const char *format = "smix reverse %d %d r";
1878 const servoParam_t *servoConf = &servoParams[i];
1879 const servoParam_t *servoConfDefault = &defaultServoParams[i];
1880 if (defaultServoParams) {
1881 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
1882 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1883 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
1884 if (servoConfDefault->reversedSources & (1 << channel)) {
1885 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
1887 if (servoConf->reversedSources & (1 << channel)) {
1888 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
1891 } else {
1892 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1893 if (servoConf->reversedSources & (1 << channel)) {
1894 cliDumpPrintLinef(dumpMask, true, format, i , channel);
1901 static void cliServo(char *cmdline)
1903 const char *format = "servo %u %d %d %d %d %d";
1904 enum { SERVO_ARGUMENT_COUNT = 6 };
1905 int16_t arguments[SERVO_ARGUMENT_COUNT];
1907 servoParam_t *servo;
1909 int i;
1910 char *ptr;
1912 if (isEmpty(cmdline)) {
1913 printServo(DUMP_MASTER, servoParams(0), NULL, NULL);
1914 } else {
1915 int validArgumentCount = 0;
1917 ptr = cmdline;
1919 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1921 // If command line doesn't fit the format, don't modify the config
1922 while (*ptr) {
1923 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1924 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1925 cliShowParseError();
1926 return;
1929 arguments[validArgumentCount++] = atoi(ptr);
1931 do {
1932 ptr++;
1933 } while (*ptr >= '0' && *ptr <= '9');
1934 } else if (*ptr == ' ') {
1935 ptr++;
1936 } else {
1937 cliShowParseError();
1938 return;
1942 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
1944 i = arguments[INDEX];
1946 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1947 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1948 cliShowParseError();
1949 return;
1952 servo = servoParamsMutable(i);
1954 if (
1955 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
1956 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
1957 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1958 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1959 arguments[RATE] < -100 || arguments[RATE] > 100 ||
1960 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1962 cliShowParseError();
1963 return;
1966 servo->min = arguments[MIN];
1967 servo->max = arguments[MAX];
1968 servo->middle = arguments[MIDDLE];
1969 servo->rate = arguments[RATE];
1970 servo->forwardFromChannel = arguments[FORWARD];
1972 cliDumpPrintLinef(0, false, format,
1974 servo->min,
1975 servo->max,
1976 servo->middle,
1977 servo->rate,
1978 servo->forwardFromChannel
1983 #endif
1985 #ifdef USE_SERVOS
1986 static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
1988 const char *format = "smix %d %d %d %d %d %d %d %d";
1989 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
1990 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1991 const servoMixer_t customServoMixer = customServoMixers[i];
1992 if (customServoMixer.rate == 0) {
1993 break;
1996 bool equalsDefault = false;
1997 if (defaultCustomServoMixers) {
1998 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1999 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
2001 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2002 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2004 customServoMixerDefault.targetChannel,
2005 customServoMixerDefault.inputSource,
2006 customServoMixerDefault.rate,
2007 customServoMixerDefault.speed,
2008 customServoMixerDefault.min,
2009 customServoMixerDefault.max,
2010 customServoMixerDefault.box
2013 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2015 customServoMixer.targetChannel,
2016 customServoMixer.inputSource,
2017 customServoMixer.rate,
2018 customServoMixer.speed,
2019 customServoMixer.min,
2020 customServoMixer.max,
2021 customServoMixer.box
2026 static void cliServoMix(char *cmdline)
2028 int args[8], check = 0;
2029 int len = strlen(cmdline);
2031 if (len == 0) {
2032 printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
2033 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
2034 // erase custom mixer
2035 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
2036 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
2037 servoParamsMutable(i)->reversedSources = 0;
2039 } else if (strncasecmp(cmdline, "load", 4) == 0) {
2040 const char *ptr = nextArg(cmdline);
2041 if (ptr) {
2042 len = strlen(ptr);
2043 for (uint32_t i = 0; ; i++) {
2044 if (mixerNames[i] == NULL) {
2045 cliPrintErrorLinef("INVALID NAME");
2046 break;
2048 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
2049 servoMixerLoadMix(i);
2050 cliPrintLinef("Loaded %s", mixerNames[i]);
2051 cliServoMix("");
2052 break;
2056 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
2057 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
2058 char *ptr = strchr(cmdline, ' ');
2060 if (ptr == NULL) {
2061 cliPrintf("s");
2062 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
2063 cliPrintf("\ti%d", inputSource);
2064 cliPrintLinefeed();
2066 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
2067 cliPrintf("%d", servoIndex);
2068 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
2069 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
2071 cliPrintLinefeed();
2073 return;
2076 char *saveptr;
2077 ptr = strtok_r(ptr, " ", &saveptr);
2078 while (ptr != NULL && check < ARGS_COUNT - 1) {
2079 args[check++] = atoi(ptr);
2080 ptr = strtok_r(NULL, " ", &saveptr);
2083 if (ptr == NULL || check != ARGS_COUNT - 1) {
2084 cliShowParseError();
2085 return;
2088 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
2089 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
2090 && (*ptr == 'r' || *ptr == 'n')) {
2091 if (*ptr == 'r') {
2092 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
2093 } else {
2094 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
2096 } else {
2097 cliShowParseError();
2098 return;
2101 cliServoMix("reverse");
2102 } else {
2103 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
2104 char *saveptr;
2105 char *ptr = strtok_r(cmdline, " ", &saveptr);
2106 while (ptr != NULL && check < ARGS_COUNT) {
2107 args[check++] = atoi(ptr);
2108 ptr = strtok_r(NULL, " ", &saveptr);
2111 if (ptr != NULL || check != ARGS_COUNT) {
2112 cliShowParseError();
2113 return;
2116 int32_t i = args[RULE];
2117 if (i >= 0 && i < MAX_SERVO_RULES &&
2118 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2119 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2120 args[RATE] >= -100 && args[RATE] <= 100 &&
2121 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2122 args[MIN] >= 0 && args[MIN] <= 100 &&
2123 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
2124 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
2125 customServoMixersMutable(i)->targetChannel = args[TARGET];
2126 customServoMixersMutable(i)->inputSource = args[INPUT];
2127 customServoMixersMutable(i)->rate = args[RATE];
2128 customServoMixersMutable(i)->speed = args[SPEED];
2129 customServoMixersMutable(i)->min = args[MIN];
2130 customServoMixersMutable(i)->max = args[MAX];
2131 customServoMixersMutable(i)->box = args[BOX];
2132 cliServoMix("");
2133 } else {
2134 cliShowParseError();
2138 #endif
2140 #ifdef USE_SDCARD
2142 static void cliWriteBytes(const uint8_t *buffer, int count)
2144 while (count > 0) {
2145 cliWrite(*buffer);
2146 buffer++;
2147 count--;
2151 static void cliSdInfo(char *cmdline)
2153 UNUSED(cmdline);
2155 cliPrint("SD card: ");
2157 if (!sdcard_isInserted()) {
2158 cliPrintLine("None inserted");
2159 return;
2162 if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
2163 cliPrintLine("Startup failed");
2164 return;
2167 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2169 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2170 metadata->manufacturerID,
2171 metadata->numBlocks / 2, /* One block is half a kB */
2172 metadata->productionMonth,
2173 metadata->productionYear,
2174 metadata->productRevisionMajor,
2175 metadata->productRevisionMinor
2178 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2180 cliPrint("'\r\n" "Filesystem: ");
2182 switch (afatfs_getFilesystemState()) {
2183 case AFATFS_FILESYSTEM_STATE_READY:
2184 cliPrint("Ready");
2185 break;
2186 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2187 cliPrint("Initializing");
2188 break;
2189 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2190 case AFATFS_FILESYSTEM_STATE_FATAL:
2191 cliPrint("Fatal");
2193 switch (afatfs_getLastError()) {
2194 case AFATFS_ERROR_BAD_MBR:
2195 cliPrint(" - no FAT MBR partitions");
2196 break;
2197 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2198 cliPrint(" - bad FAT header");
2199 break;
2200 case AFATFS_ERROR_GENERIC:
2201 case AFATFS_ERROR_NONE:
2202 ; // Nothing more detailed to print
2203 break;
2205 break;
2207 cliPrintLinefeed();
2210 #endif
2212 #ifdef USE_FLASH_CHIP
2214 static void cliFlashInfo(char *cmdline)
2216 const flashGeometry_t *layout = flashGetGeometry();
2218 UNUSED(cmdline);
2220 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2221 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2223 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2224 const flashPartition_t *partition;
2225 if (index == 0) {
2226 cliPrintLine("Paritions:");
2228 partition = flashPartitionFindByIndex(index);
2229 if (!partition) {
2230 break;
2232 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2234 #ifdef USE_FLASHFS
2235 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2237 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2238 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2239 flashfsGetOffset()
2241 #endif
2245 static void cliFlashErase(char *cmdline)
2247 UNUSED(cmdline);
2249 if (!flashfsIsSupported()) {
2250 return;
2253 #ifndef MINIMAL_CLI
2254 uint32_t i = 0;
2255 cliPrintLine("Erasing, please wait ... ");
2256 #else
2257 cliPrintLine("Erasing,");
2258 #endif
2260 bufWriterFlush(cliWriter);
2261 flashfsEraseCompletely();
2263 while (!flashfsIsReady()) {
2264 #ifndef MINIMAL_CLI
2265 cliPrintf(".");
2266 if (i++ > 120) {
2267 i=0;
2268 cliPrintLinefeed();
2271 bufWriterFlush(cliWriter);
2272 #endif
2273 delay(100);
2275 beeper(BEEPER_BLACKBOX_ERASE);
2276 cliPrintLinefeed();
2277 cliPrintLine("Done.");
2280 #ifdef USE_FLASH_TOOLS
2282 static void cliFlashVerify(char *cmdline)
2284 UNUSED(cmdline);
2286 cliPrintLine("Verifying");
2287 if (flashfsVerifyEntireFlash()) {
2288 cliPrintLine("Success");
2289 } else {
2290 cliPrintLine("Failed");
2294 static void cliFlashWrite(char *cmdline)
2296 const uint32_t address = atoi(cmdline);
2297 const char *text = strchr(cmdline, ' ');
2299 if (!text) {
2300 cliShowParseError();
2301 } else {
2302 flashfsSeekAbs(address);
2303 flashfsWrite((uint8_t*)text, strlen(text), true);
2304 flashfsFlushSync();
2306 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2310 static void cliFlashRead(char *cmdline)
2312 uint32_t address = atoi(cmdline);
2314 const char *nextArg = strchr(cmdline, ' ');
2316 if (!nextArg) {
2317 cliShowParseError();
2318 } else {
2319 uint32_t length = atoi(nextArg);
2321 cliPrintLinef("Reading %u bytes at %u:", length, address);
2323 uint8_t buffer[32];
2324 while (length > 0) {
2325 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2327 for (int i = 0; i < bytesRead; i++) {
2328 cliWrite(buffer[i]);
2331 length -= bytesRead;
2332 address += bytesRead;
2334 if (bytesRead == 0) {
2335 //Assume we reached the end of the volume or something fatal happened
2336 break;
2339 cliPrintLinefeed();
2343 #endif
2344 #endif
2346 #ifdef USE_VTX_CONTROL
2347 static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
2349 // print out vtx channel settings
2350 const char *format = "vtx %u %u %u %u %u %u %u";
2351 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2352 bool equalsDefault = false;
2353 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2354 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2355 if (vtxConfigDefault) {
2356 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2357 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2358 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2359 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2361 cacDefault->auxChannelIndex,
2362 cacDefault->band,
2363 cacDefault->channel,
2364 cacDefault->power,
2365 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2366 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2369 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2371 cac->auxChannelIndex,
2372 cac->band,
2373 cac->channel,
2374 cac->power,
2375 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2376 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2381 static void cliVtx(char *cmdline)
2383 const char *format = "vtx %u %u %u %u %u %u %u";
2384 int i, val = 0;
2385 const char *ptr;
2387 if (isEmpty(cmdline)) {
2388 printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
2389 } else {
2390 ptr = cmdline;
2391 i = atoi(ptr++);
2392 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2393 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2394 uint8_t validArgumentCount = 0;
2395 ptr = nextArg(ptr);
2396 if (ptr) {
2397 val = atoi(ptr);
2398 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2399 cac->auxChannelIndex = val;
2400 validArgumentCount++;
2403 ptr = nextArg(ptr);
2404 if (ptr) {
2405 val = atoi(ptr);
2406 // FIXME Use VTX API to get max
2407 if (val >= 0 && val <= VTX_SETTINGS_MAX_BAND) {
2408 cac->band = val;
2409 validArgumentCount++;
2412 ptr = nextArg(ptr);
2413 if (ptr) {
2414 val = atoi(ptr);
2415 // FIXME Use VTX API to get max
2416 if (val >= 0 && val <= VTX_SETTINGS_MAX_CHANNEL) {
2417 cac->channel = val;
2418 validArgumentCount++;
2421 ptr = nextArg(ptr);
2422 if (ptr) {
2423 val = atoi(ptr);
2424 // FIXME Use VTX API to get max
2425 if (val >= 0 && val < VTX_SETTINGS_POWER_COUNT) {
2426 cac->power= val;
2427 validArgumentCount++;
2430 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2432 if (validArgumentCount != 6) {
2433 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2434 cliShowParseError();
2435 } else {
2436 cliDumpPrintLinef(0, false, format,
2438 cac->auxChannelIndex,
2439 cac->band,
2440 cac->channel,
2441 cac->power,
2442 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2443 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2446 } else {
2447 cliShowArgumentRangeError("INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2452 #endif // VTX_CONTROL
2454 #ifdef USE_VTX_TABLE
2456 static char *formatVtxTableBandFrequency(const uint16_t *frequency, int channels)
2458 static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 1];
2459 char freqtmp[5 + 1];
2460 freqbuf[0] = 0;
2461 for (int channel = 0; channel < channels; channel++) {
2462 tfp_sprintf(freqtmp, " %4d", frequency[channel]);
2463 strcat(freqbuf, freqtmp);
2465 return freqbuf;
2468 static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2470 char *fmt = "vtxtable band %d %s %c%s";
2471 bool equalsDefault = false;
2473 if (defaultConfig) {
2474 equalsDefault = true;
2475 if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
2476 equalsDefault = false;
2478 if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
2479 equalsDefault = false;
2481 for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
2482 if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
2483 equalsDefault = false;
2486 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2487 char *freqbuf = formatVtxTableBandFrequency(defaultConfig->frequency[band], defaultConfig->channels);
2488 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
2491 char *freqbuf = formatVtxTableBandFrequency(currentConfig->frequency[band], currentConfig->channels);
2492 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
2493 return headingStr;
2496 static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
2498 // (max 4 digit + 1 space) per level
2499 static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
2500 char pwrtmp[5 + 1];
2501 pwrbuf[0] = 0;
2502 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2503 tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
2504 strcat(pwrbuf, pwrtmp);
2506 return pwrbuf;
2509 static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2511 char *fmt = "vtxtable powervalues %s";
2512 bool equalsDefault = false;
2513 if (defaultConfig) {
2514 equalsDefault = true;
2515 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2516 if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
2517 equalsDefault = false;
2520 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2521 char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
2522 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2525 char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
2526 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2527 return headingStr;
2530 static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
2532 static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
2533 char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
2534 pwrbuf[0] = 0;
2535 for (int pwrindex = 0; pwrindex < count; pwrindex++) {
2536 strcat(pwrbuf, " ");
2537 strcpy(pwrtmp, labels[pwrindex]);
2538 // trim trailing space
2539 char *sp;
2540 while ((sp = strchr(pwrtmp, ' '))) {
2541 *sp = 0;
2543 strcat(pwrbuf, pwrtmp);
2545 return pwrbuf;
2548 static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2550 char *fmt = "vtxtable powerlabels%s";
2551 bool equalsDefault = false;
2552 if (defaultConfig) {
2553 equalsDefault = true;
2554 for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
2555 if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
2556 equalsDefault = false;
2559 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2560 char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
2561 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2564 char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
2565 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
2566 return headingStr;
2569 static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
2571 bool equalsDefault;
2572 char *fmt;
2574 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2576 // bands
2577 equalsDefault = false;
2578 fmt = "vtxtable bands %d";
2579 if (defaultConfig) {
2580 equalsDefault = (defaultConfig->bands == currentConfig->bands);
2581 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2582 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
2584 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
2586 // channels
2587 equalsDefault = false;
2588 fmt = "vtxtable channels %d";
2589 if (defaultConfig) {
2590 equalsDefault = (defaultConfig->channels == currentConfig->channels);
2591 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2592 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
2594 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
2596 // band
2598 for (int band = 0; band < currentConfig->bands; band++) {
2599 headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
2602 // powerlevels
2604 equalsDefault = false;
2605 fmt = "vtxtable powerlevels %d";
2606 if (defaultConfig) {
2607 equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
2608 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2609 cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
2611 cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
2613 // powervalues
2615 // powerlabels
2616 headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
2617 headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
2620 static void cliVtxTable(char *cmdline)
2622 char *tok;
2623 char *saveptr;
2625 // Band number or nothing
2626 tok = strtok_r(cmdline, " ", &saveptr);
2628 if (!tok) {
2629 printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
2630 return;
2633 if (strcasecmp(tok, "bands") == 0) {
2634 tok = strtok_r(NULL, " ", &saveptr);
2635 int bands = atoi(tok);
2636 if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
2637 cliPrintErrorLinef("INVALID BAND COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_BANDS);
2638 return;
2640 if (bands < vtxTableConfigMutable()->bands) {
2641 for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
2642 vtxTableConfigClearBand(vtxTableConfigMutable(), i);
2645 vtxTableConfigMutable()->bands = bands;
2647 } else if (strcasecmp(tok, "channels") == 0) {
2648 tok = strtok_r(NULL, " ", &saveptr);
2650 int channels = atoi(tok);
2651 if (channels < 0 || channels > VTX_TABLE_MAX_BANDS) {
2652 cliPrintErrorLinef("INVALID CHANNEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_CHANNELS);
2653 return;
2655 if (channels < vtxTableConfigMutable()->channels) {
2656 for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
2657 vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
2660 vtxTableConfigMutable()->channels = channels;
2662 } else if (strcasecmp(tok, "powerlevels") == 0) {
2663 // Number of power levels
2664 tok = strtok_r(NULL, " ", &saveptr);
2665 if (tok) {
2666 int levels = atoi(tok);
2667 if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
2668 cliPrintErrorLinef("INVALID POWER LEVEL COUNT (SHOULD BE BETWEEN 0 AND %d)", VTX_TABLE_MAX_POWER_LEVELS);
2669 } else {
2670 if (levels < vtxTableConfigMutable()->powerLevels) {
2671 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
2672 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
2674 vtxTableConfigMutable()->powerLevels = levels;
2676 } else {
2677 // XXX Show current level count?
2679 return;
2681 } else if (strcasecmp(tok, "powervalues") == 0) {
2682 // Power values
2683 uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
2684 int count;
2685 int levels = vtxTableConfigMutable()->powerLevels;
2687 memset(power, 0, sizeof(power));
2689 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2690 int value = atoi(tok);
2691 power[count] = value;
2694 // Check remaining tokens
2696 if (count < levels) {
2697 cliPrintErrorLinef("NOT ENOUGH VALUES (EXPECTED %d)", levels);
2698 return;
2699 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2700 cliPrintErrorLinef("TOO MANY VALUES (EXPECTED %d)", levels);
2701 return;
2704 for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
2705 vtxTableConfigMutable()->powerValues[i] = power[i];
2708 } else if (strcasecmp(tok, "powerlabels") == 0) {
2709 // Power labels
2710 char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
2711 int levels = vtxTableConfigMutable()->powerLevels;
2712 int count;
2713 for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
2714 strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
2715 for (unsigned i = 0; i < strlen(label[count]); i++) {
2716 label[count][i] = toupper(label[count][i]);
2720 // Check remaining tokens
2722 if (count < levels) {
2723 cliPrintErrorLinef("NOT ENOUGH LABELS (EXPECTED %d)", levels);
2724 return;
2725 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2726 cliPrintErrorLinef("TOO MANY LABELS (EXPECTED %d)", levels);
2727 return;
2730 for (int i = 0; i < count; i++) {
2731 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
2733 } else if (strcasecmp(tok, "band") == 0) {
2735 int bands = vtxTableConfigMutable()->bands;
2737 tok = strtok_r(NULL, " ", &saveptr);
2738 if (!tok) {
2739 return;
2742 int band = atoi(tok);
2743 --band;
2745 if (band < 0 || band >= bands) {
2746 cliPrintErrorLinef("INVALID BAND NUMBER %s (EXPECTED 1-%d)", tok, bands);
2747 return;
2750 // Band name
2751 tok = strtok_r(NULL, " ", &saveptr);
2753 if (!tok) {
2754 return;
2757 char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
2758 memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
2759 strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
2760 for (unsigned i = 0; i < strlen(bandname); i++) {
2761 bandname[i] = toupper(bandname[i]);
2764 // Band letter
2765 tok = strtok_r(NULL, " ", &saveptr);
2767 if (!tok) {
2768 return;
2771 char bandletter = toupper(tok[0]);
2773 uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
2774 int channel = 0;
2775 int channels = vtxTableConfigMutable()->channels;
2777 for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
2778 int freq = atoi(tok);
2779 if (freq < 0) {
2780 cliPrintErrorLinef("INVALID FREQUENCY %s", tok);
2781 return;
2783 bandfreq[channel] = freq;
2786 if (channel < channels) {
2787 cliPrintErrorLinef("NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
2788 return;
2789 } else if ((tok = strtok_r(NULL, " ", &saveptr))) {
2790 cliPrintErrorLinef("TOO MANY FREQUENCIES (EXPECTED %d)", channels);
2791 return;
2794 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
2795 vtxTableConfigMutable()->bandLetters[band] = bandletter;
2797 for (int i = 0; i < channel; i++) {
2798 vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
2800 } else {
2801 // Bad subcommand
2802 cliPrintErrorLinef("INVALID SUBCOMMAND %s", tok);
2805 #endif // USE_VTX_TABLE
2807 static void printName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
2809 const bool equalsDefault = strlen(pilotConfig->name) == 0;
2810 cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->name);
2813 static void cliName(char *cmdline)
2815 const unsigned int len = strlen(cmdline);
2816 bool updated = false;
2817 if (len > 0) {
2818 memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
2819 if (strncmp(cmdline, emptyName, len)) {
2820 strncpy(pilotConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
2822 updated = true;
2824 printName(DUMP_MASTER, pilotConfig());
2825 if (updated) {
2826 cliPrintLine("###WARNING: This command will be removed. Use 'set name = ' instead.###");
2830 #if defined(USE_BOARD_INFO)
2832 #define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
2834 static void printBoardName(dumpFlags_t dumpMask)
2836 if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
2837 cliPrintLinef("board_name %s", getBoardName());
2841 static void cliBoardName(char *cmdline)
2843 const unsigned int len = strlen(cmdline);
2844 if (len > 0 && boardInformationIsSet() && (len != strlen(getBoardName()) || strncmp(getBoardName(), cmdline, len))) {
2845 cliPrintErrorLinef(ERROR_MESSAGE, "BOARD_NAME", getBoardName());
2846 } else {
2847 if (len > 0) {
2848 setBoardName(cmdline);
2849 boardInformationUpdated = true;
2851 printBoardName(DUMP_ALL);
2855 static void printManufacturerId(dumpFlags_t dumpMask)
2857 if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
2858 cliPrintLinef("manufacturer_id %s", getManufacturerId());
2862 static void cliManufacturerId(char *cmdline)
2864 const unsigned int len = strlen(cmdline);
2865 if (len > 0 && boardInformationIsSet() && (len != strlen(getManufacturerId()) || strncmp(getManufacturerId(), cmdline, len))) {
2866 cliPrintErrorLinef(ERROR_MESSAGE, "MANUFACTURER_ID", getManufacturerId());
2867 } else {
2868 if (len > 0) {
2869 setManufacturerId(cmdline);
2870 boardInformationUpdated = true;
2872 printManufacturerId(DUMP_ALL);
2876 #if defined(USE_SIGNATURE)
2877 static void writeSignature(char *signatureStr, uint8_t *signature)
2879 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
2880 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
2884 static void cliSignature(char *cmdline)
2886 const int len = strlen(cmdline);
2888 uint8_t signature[SIGNATURE_LENGTH] = {0};
2889 if (len > 0) {
2890 if (len != 2 * SIGNATURE_LENGTH) {
2891 cliPrintErrorLinef("INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
2893 return;
2896 #define BLOCK_SIZE 2
2897 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
2898 char temp[BLOCK_SIZE + 1];
2899 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
2900 temp[BLOCK_SIZE] = '\0';
2901 char *end;
2902 unsigned result = strtoul(temp, &end, 16);
2903 if (end == &temp[BLOCK_SIZE]) {
2904 signature[i] = result;
2905 } else {
2906 cliPrintErrorLinef("INVALID CHARACTER FOUND: %c", end[0]);
2908 return;
2911 #undef BLOCK_SIZE
2914 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
2915 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
2916 writeSignature(signatureStr, getSignature());
2917 cliPrintErrorLinef(ERROR_MESSAGE, "SIGNATURE", signatureStr);
2918 } else {
2919 if (len > 0) {
2920 setSignature(signature);
2922 signatureUpdated = true;
2924 writeSignature(signatureStr, getSignature());
2925 } else if (signatureUpdated || signatureIsSet()) {
2926 writeSignature(signatureStr, getSignature());
2929 cliPrintLinef("signature %s", signatureStr);
2932 #endif
2934 #undef ERROR_MESSAGE
2936 #endif // USE_BOARD_INFO
2938 static void cliMcuId(char *cmdline)
2940 UNUSED(cmdline);
2942 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
2945 static uint32_t getFeatureMask(const uint32_t featureMask)
2947 if (featureMaskIsCopied) {
2948 return featureMaskCopy;
2949 } else {
2950 return featureMask;
2954 static void printFeature(dumpFlags_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault, const char *headingStr)
2956 const uint32_t mask = getFeatureMask(featureConfig->enabledFeatures);
2957 const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2958 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
2959 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
2960 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
2961 const char *format = "feature -%s";
2962 const bool equalsDefault = (~defaultMask | mask) & (1 << i);
2963 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2964 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2965 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
2968 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
2969 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
2970 const char *format = "feature %s";
2971 if (defaultMask & (1 << i)) {
2972 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2974 if (mask & (1 << i)) {
2975 const bool equalsDefault = (defaultMask | ~mask) & (1 << i);
2976 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
2977 cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
2983 static void cliFeature(char *cmdline)
2985 uint32_t len = strlen(cmdline);
2986 const uint32_t mask = getFeatureMask(featureMask());
2987 if (len == 0) {
2988 cliPrint("Enabled: ");
2989 for (uint32_t i = 0; ; i++) {
2990 if (featureNames[i] == NULL) {
2991 break;
2993 if (mask & (1 << i)) {
2994 cliPrintf("%s ", featureNames[i]);
2997 cliPrintLinefeed();
2998 } else if (strncasecmp(cmdline, "list", len) == 0) {
2999 cliPrint("Available:");
3000 for (uint32_t i = 0; ; i++) {
3001 if (featureNames[i] == NULL)
3002 break;
3003 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
3004 cliPrintf(" %s", featureNames[i]);
3006 cliPrintLinefeed();
3007 return;
3008 } else {
3009 if (!featureMaskIsCopied) {
3010 featureMaskCopy = featureMask();
3011 featureMaskIsCopied = true;
3013 uint32_t feature;
3015 bool remove = false;
3016 if (cmdline[0] == '-') {
3017 // remove feature
3018 remove = true;
3019 cmdline++; // skip over -
3020 len--;
3023 for (uint32_t i = 0; ; i++) {
3024 if (featureNames[i] == NULL) {
3025 cliPrintErrorLinef("INVALID NAME");
3026 break;
3029 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
3030 feature = 1 << i;
3031 #ifndef USE_GPS
3032 if (feature & FEATURE_GPS) {
3033 cliPrintLine("unavailable");
3034 break;
3036 #endif
3037 #ifndef USE_RANGEFINDER
3038 if (feature & FEATURE_RANGEFINDER) {
3039 cliPrintLine("unavailable");
3040 break;
3042 #endif
3043 if (remove) {
3044 featureClear(feature, &featureMaskCopy);
3045 cliPrint("Disabled");
3046 } else {
3047 featureSet(feature, &featureMaskCopy);
3048 cliPrint("Enabled");
3050 cliPrintLinef(" %s", featureNames[i]);
3051 break;
3057 #if defined(USE_BEEPER)
3058 static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
3060 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3061 const uint8_t beeperCount = beeperTableEntryCount();
3062 for (int32_t i = 0; i < beeperCount - 1; i++) {
3063 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3064 const char *formatOff = "%s -%s";
3065 const char *formatOn = "%s %s";
3066 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
3067 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
3068 const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
3069 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3070 cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
3075 static void processBeeperCommand(char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
3077 uint32_t len = strlen(cmdline);
3078 uint8_t beeperCount = beeperTableEntryCount();
3080 if (len == 0) {
3081 cliPrintf("Disabled:");
3082 for (int32_t i = 0; ; i++) {
3083 if (i == beeperCount - 1) {
3084 if (*offFlags == 0)
3085 cliPrint(" none");
3086 break;
3089 if (beeperModeMaskForTableIndex(i) & *offFlags)
3090 cliPrintf(" %s", beeperNameForTableIndex(i));
3092 cliPrintLinefeed();
3093 } else if (strncasecmp(cmdline, "list", len) == 0) {
3094 cliPrint("Available:");
3095 for (uint32_t i = 0; i < beeperCount; i++) {
3096 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
3097 cliPrintf(" %s", beeperNameForTableIndex(i));
3100 cliPrintLinefeed();
3101 } else {
3102 bool remove = false;
3103 if (cmdline[0] == '-') {
3104 remove = true; // this is for beeper OFF condition
3105 cmdline++;
3106 len--;
3109 for (uint32_t i = 0; ; i++) {
3110 if (i == beeperCount) {
3111 cliPrintErrorLinef("INVALID NAME");
3112 break;
3114 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
3115 if (remove) { // beeper off
3116 if (i == BEEPER_ALL - 1) {
3117 *offFlags = allowedFlags;
3118 } else {
3119 *offFlags |= beeperModeMaskForTableIndex(i);
3121 cliPrint("Disabled");
3123 else { // beeper on
3124 if (i == BEEPER_ALL - 1) {
3125 *offFlags = 0;
3126 } else {
3127 *offFlags &= ~beeperModeMaskForTableIndex(i);
3129 cliPrint("Enabled");
3131 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3132 break;
3138 #if defined(USE_DSHOT)
3139 static void cliBeacon(char *cmdline)
3141 processBeeperCommand(cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
3143 #endif
3145 static void cliBeeper(char *cmdline)
3147 processBeeperCommand(cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
3149 #endif
3151 #ifdef USE_RX_SPI
3152 void cliRxSpiBind(char *cmdline){
3153 UNUSED(cmdline);
3154 switch (rxSpiConfig()->rx_spi_protocol) {
3155 default:
3156 cliPrint("Not supported.");
3157 break;
3158 #if defined(USE_RX_FRSKY_SPI)
3159 #if defined(USE_RX_FRSKY_SPI_D)
3160 case RX_SPI_FRSKY_D:
3161 #endif
3162 #if defined(USE_RX_FRSKY_SPI_X)
3163 case RX_SPI_FRSKY_X:
3164 case RX_SPI_FRSKY_X_LBT:
3165 #endif
3166 #endif // USE_RX_FRSKY_SPI
3167 #ifdef USE_RX_SFHSS_SPI
3168 case RX_SPI_SFHSS:
3169 #endif
3170 #ifdef USE_RX_FLYSKY
3171 case RX_SPI_A7105_FLYSKY:
3172 case RX_SPI_A7105_FLYSKY_2A:
3173 #endif
3174 #ifdef USE_RX_SPEKTRUM
3175 case RX_SPI_CYRF6936_DSM:
3176 #endif
3177 #if defined(USE_RX_FRSKY_SPI) || defined(USE_RX_SFHSS_SPI) || defined(USE_RX_FLYSKY) || defined(USE_RX_SPEKTRUM)
3178 rxSpiBind();
3179 cliPrint("Binding...");
3180 break;
3181 #endif
3184 #endif
3186 static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
3188 bool equalsDefault = true;
3189 char buf[16];
3190 char bufDefault[16];
3191 uint32_t i;
3193 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
3194 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3195 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3196 if (defaultRxConfig) {
3197 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3198 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3201 buf[i] = '\0';
3203 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
3204 const char *formatMap = "map %s";
3205 if (defaultRxConfig) {
3206 bufDefault[i] = '\0';
3207 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3209 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3213 static void cliMap(char *cmdline)
3215 uint32_t i;
3216 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
3218 uint32_t len = strlen(cmdline);
3219 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
3221 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3222 buf[i] = toupper((unsigned char)cmdline[i]);
3224 buf[i] = '\0';
3226 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3227 buf[i] = toupper((unsigned char)cmdline[i]);
3229 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
3230 continue;
3232 cliShowParseError();
3233 return;
3235 parseRcChannels(buf, rxConfigMutable());
3236 } else if (len > 0) {
3237 cliShowParseError();
3238 return;
3241 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
3242 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3245 buf[i] = '\0';
3246 cliPrintLinef("map %s", buf);
3249 static char *skipSpace(char *buffer)
3251 while (*(buffer) == ' ') {
3252 buffer++;
3255 return buffer;
3258 static char *checkCommand(char *cmdline, const char *command)
3260 if (!strncasecmp(cmdline, command, strlen(command)) // command names match
3261 && (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
3262 return skipSpace(cmdline + strlen(command) + 1);
3263 } else {
3264 return 0;
3268 static void cliRebootEx(bool bootLoader)
3270 cliPrint("\r\nRebooting");
3271 bufWriterFlush(cliWriter);
3272 waitForSerialPortToFinishTransmitting(cliPort);
3273 stopPwmAllMotors();
3274 if (bootLoader) {
3275 systemResetToBootloader();
3276 return;
3278 systemReset();
3281 static void cliReboot(void)
3283 cliRebootEx(false);
3286 static void cliBootloader(char *cmdline)
3288 UNUSED(cmdline);
3290 cliPrintHashLine("restarting in bootloader mode");
3291 cliRebootEx(true);
3294 static void cliExit(char *cmdline)
3296 UNUSED(cmdline);
3298 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
3299 bufWriterFlush(cliWriter);
3301 *cliBuffer = '\0';
3302 bufferIndex = 0;
3303 cliMode = 0;
3304 // incase a motor was left running during motortest, clear it here
3305 mixerResetDisarmedMotors();
3306 cliReboot();
3308 cliWriter = NULL;
3311 #ifdef USE_GPS
3312 static void cliGpsPassthrough(char *cmdline)
3314 UNUSED(cmdline);
3316 gpsEnablePassthrough(cliPort);
3318 #endif
3320 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
3321 static void cliPrintGyroRegisters(uint8_t whichSensor)
3323 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
3324 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
3325 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
3328 static void cliDumpGyroRegisters(char *cmdline)
3330 #ifdef USE_MULTI_GYRO
3331 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3332 cliPrintLinef("\r\n# Gyro 1");
3333 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3335 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
3336 cliPrintLinef("\r\n# Gyro 2");
3337 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
3339 #else
3340 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
3341 #endif
3342 UNUSED(cmdline);
3344 #endif
3347 static int parseOutputIndex(char *pch, bool allowAllEscs) {
3348 int outputIndex = atoi(pch);
3349 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
3350 cliPrintLinef("Using output %d.", outputIndex);
3351 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
3352 cliPrintLinef("Using all outputs.");
3353 } else {
3354 cliPrintErrorLinef("INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
3356 return -1;
3359 return outputIndex;
3362 #if defined(USE_DSHOT)
3363 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3365 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
3366 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
3367 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
3369 enum {
3370 ESC_INFO_KISS_V1,
3371 ESC_INFO_KISS_V2,
3372 ESC_INFO_BLHELI32
3375 #define ESC_INFO_VERSION_POSITION 12
3377 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
3379 bool escInfoReceived = false;
3380 if (bytesRead > ESC_INFO_VERSION_POSITION) {
3381 uint8_t escInfoVersion;
3382 uint8_t frameLength;
3383 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
3384 escInfoVersion = ESC_INFO_BLHELI32;
3385 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
3386 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
3387 escInfoVersion = ESC_INFO_KISS_V2;
3388 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
3389 } else {
3390 escInfoVersion = ESC_INFO_KISS_V1;
3391 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
3394 if (bytesRead == frameLength) {
3395 escInfoReceived = true;
3397 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
3398 uint8_t firmwareVersion = 0;
3399 uint8_t firmwareSubVersion = 0;
3400 uint8_t escType = 0;
3401 switch (escInfoVersion) {
3402 case ESC_INFO_KISS_V1:
3403 firmwareVersion = escInfoBuffer[12];
3404 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
3405 escType = (escInfoBuffer[13] & 0xe0) >> 5;
3407 break;
3408 case ESC_INFO_KISS_V2:
3409 firmwareVersion = escInfoBuffer[13];
3410 firmwareSubVersion = escInfoBuffer[14];
3411 escType = escInfoBuffer[15];
3413 break;
3414 case ESC_INFO_BLHELI32:
3415 firmwareVersion = escInfoBuffer[13];
3416 firmwareSubVersion = escInfoBuffer[14];
3417 escType = escInfoBuffer[15];
3419 break;
3422 cliPrint("ESC Type: ");
3423 switch (escInfoVersion) {
3424 case ESC_INFO_KISS_V1:
3425 case ESC_INFO_KISS_V2:
3426 switch (escType) {
3427 case 1:
3428 cliPrintLine("KISS8A");
3430 break;
3431 case 2:
3432 cliPrintLine("KISS16A");
3434 break;
3435 case 3:
3436 cliPrintLine("KISS24A");
3438 break;
3439 case 5:
3440 cliPrintLine("KISS Ultralite");
3442 break;
3443 default:
3444 cliPrintLine("unknown");
3446 break;
3449 break;
3450 case ESC_INFO_BLHELI32:
3452 char *escType = (char *)(escInfoBuffer + 31);
3453 escType[32] = 0;
3454 cliPrintLine(escType);
3457 break;
3460 cliPrint("MCU Serial No: 0x");
3461 for (int i = 0; i < 12; i++) {
3462 if (i && (i % 3 == 0)) {
3463 cliPrint("-");
3465 cliPrintf("%02x", escInfoBuffer[i]);
3467 cliPrintLinefeed();
3469 switch (escInfoVersion) {
3470 case ESC_INFO_KISS_V1:
3471 case ESC_INFO_KISS_V2:
3472 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
3474 break;
3475 case ESC_INFO_BLHELI32:
3476 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
3478 break;
3480 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
3481 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
3482 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
3483 if (escInfoVersion == ESC_INFO_BLHELI32) {
3484 uint8_t setting = escInfoBuffer[18];
3485 cliPrint("Low voltage Limit: ");
3486 switch (setting) {
3487 case 0:
3488 cliPrintLine("off");
3490 break;
3491 case 255:
3492 cliPrintLine("unsupported");
3494 break;
3495 default:
3496 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
3498 break;
3501 setting = escInfoBuffer[19];
3502 cliPrint("Current Limit: ");
3503 switch (setting) {
3504 case 0:
3505 cliPrintLine("off");
3507 break;
3508 case 255:
3509 cliPrintLine("unsupported");
3511 break;
3512 default:
3513 cliPrintLinef("%d", setting);
3515 break;
3518 for (int i = 0; i < 4; i++) {
3519 setting = escInfoBuffer[i + 20];
3520 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
3524 } else {
3525 cliPrintErrorLinef("CHECKSUM ERROR.");
3530 if (!escInfoReceived) {
3531 cliPrintLine("No Info.");
3535 static void executeEscInfoCommand(uint8_t escIndex)
3537 cliPrintLinef("Info for ESC %d:", escIndex);
3539 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
3541 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
3543 pwmWriteDshotCommand(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, true);
3545 delay(10);
3547 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
3549 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
3551 static void cliDshotProg(char *cmdline)
3553 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
3554 cliShowParseError();
3556 return;
3559 char *saveptr;
3560 char *pch = strtok_r(cmdline, " ", &saveptr);
3561 int pos = 0;
3562 int escIndex = 0;
3563 bool firstCommand = true;
3564 while (pch != NULL) {
3565 switch (pos) {
3566 case 0:
3567 escIndex = parseOutputIndex(pch, true);
3568 if (escIndex == -1) {
3569 return;
3572 break;
3573 default:
3575 int command = atoi(pch);
3576 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
3577 if (firstCommand) {
3578 pwmDisableMotors();
3580 if (command == DSHOT_CMD_ESC_INFO) {
3581 delay(5); // Wait for potential ESC telemetry transmission to finish
3582 } else {
3583 delay(1);
3586 firstCommand = false;
3589 if (command != DSHOT_CMD_ESC_INFO) {
3590 pwmWriteDshotCommand(escIndex, getMotorCount(), command, true);
3591 } else {
3592 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
3593 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
3594 if (escIndex != ALL_MOTORS) {
3595 executeEscInfoCommand(escIndex);
3596 } else {
3597 for (uint8_t i = 0; i < getMotorCount(); i++) {
3598 executeEscInfoCommand(i);
3601 } else
3602 #endif
3604 cliPrintLine("Not supported.");
3608 cliPrintLinef("Command Sent: %d", command);
3610 } else {
3611 cliPrintErrorLinef("INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3615 break;
3618 pos++;
3619 pch = strtok_r(NULL, " ", &saveptr);
3622 pwmEnableMotors();
3624 #endif // USE_DSHOT
3626 #ifdef USE_ESCSERIAL
3627 static void cliEscPassthrough(char *cmdline)
3629 if (isEmpty(cmdline)) {
3630 cliShowParseError();
3632 return;
3635 char *saveptr;
3636 char *pch = strtok_r(cmdline, " ", &saveptr);
3637 int pos = 0;
3638 uint8_t mode = 0;
3639 int escIndex = 0;
3640 while (pch != NULL) {
3641 switch (pos) {
3642 case 0:
3643 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3644 mode = PROTOCOL_SIMONK;
3645 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3646 mode = PROTOCOL_BLHELI;
3647 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3648 mode = PROTOCOL_KISS;
3649 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3650 mode = PROTOCOL_KISSALL;
3651 } else {
3652 cliShowParseError();
3654 return;
3656 break;
3657 case 1:
3658 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
3659 if (escIndex == -1) {
3660 return;
3663 break;
3664 default:
3665 cliShowParseError();
3667 return;
3669 break;
3672 pos++;
3673 pch = strtok_r(NULL, " ", &saveptr);
3676 if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
3677 cliPrintErrorLinef("Error starting ESC connection");
3680 #endif
3682 #ifndef USE_QUAD_MIXER_ONLY
3683 static void cliMixer(char *cmdline)
3685 int len;
3687 len = strlen(cmdline);
3689 if (len == 0) {
3690 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3691 return;
3692 } else if (strncasecmp(cmdline, "list", len) == 0) {
3693 cliPrint("Available:");
3694 for (uint32_t i = 0; ; i++) {
3695 if (mixerNames[i] == NULL)
3696 break;
3697 cliPrintf(" %s", mixerNames[i]);
3699 cliPrintLinefeed();
3700 return;
3703 for (uint32_t i = 0; ; i++) {
3704 if (mixerNames[i] == NULL) {
3705 cliPrintErrorLinef("INVALID NAME");
3706 return;
3708 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
3709 mixerConfigMutable()->mixerMode = i + 1;
3710 break;
3714 cliMixer("");
3716 #endif
3718 static void cliMotor(char *cmdline)
3720 if (isEmpty(cmdline)) {
3721 cliShowParseError();
3723 return;
3726 int motorIndex = 0;
3727 int motorValue = 0;
3729 char *saveptr;
3730 char *pch = strtok_r(cmdline, " ", &saveptr);
3731 int index = 0;
3732 while (pch != NULL) {
3733 switch (index) {
3734 case 0:
3735 motorIndex = parseOutputIndex(pch, true);
3736 if (motorIndex == -1) {
3737 return;
3740 break;
3741 case 1:
3742 motorValue = atoi(pch);
3744 break;
3746 index++;
3747 pch = strtok_r(NULL, " ", &saveptr);
3750 if (index == 2) {
3751 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
3752 cliShowArgumentRangeError("VALUE", 1000, 2000);
3753 } else {
3754 uint32_t motorOutputValue = convertExternalToMotor(motorValue);
3756 if (motorIndex != ALL_MOTORS) {
3757 motor_disarmed[motorIndex] = motorOutputValue;
3759 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
3760 } else {
3761 for (int i = 0; i < getMotorCount(); i++) {
3762 motor_disarmed[i] = motorOutputValue;
3765 cliPrintLinef("all motors: %d", motorOutputValue);
3768 } else {
3769 cliShowParseError();
3773 #ifndef MINIMAL_CLI
3774 static void cliPlaySound(char *cmdline)
3776 int i;
3777 const char *name;
3778 static int lastSoundIdx = -1;
3780 if (isEmpty(cmdline)) {
3781 i = lastSoundIdx + 1; //next sound index
3782 if ((name=beeperNameForTableIndex(i)) == NULL) {
3783 while (true) { //no name for index; try next one
3784 if (++i >= beeperTableEntryCount())
3785 i = 0; //if end then wrap around to first entry
3786 if ((name=beeperNameForTableIndex(i)) != NULL)
3787 break; //if name OK then play sound below
3788 if (i == lastSoundIdx + 1) { //prevent infinite loop
3789 cliPrintErrorLinef("ERROR PLAYING SOUND");
3790 return;
3794 } else { //index value was given
3795 i = atoi(cmdline);
3796 if ((name=beeperNameForTableIndex(i)) == NULL) {
3797 cliPrintLinef("No sound for index %d", i);
3798 return;
3801 lastSoundIdx = i;
3802 beeperSilence();
3803 cliPrintLinef("Playing sound %d: %s", i, name);
3804 beeper(beeperModeForTableIndex(i));
3806 #endif
3808 static void cliProfile(char *cmdline)
3810 if (isEmpty(cmdline)) {
3811 cliPrintLinef("profile %d", getPidProfileIndexToUse());
3812 return;
3813 } else {
3814 const int i = atoi(cmdline);
3815 if (i >= 0 && i < PID_PROFILE_COUNT) {
3816 changePidProfile(i);
3817 cliProfile("");
3818 } else {
3819 cliPrintErrorLinef("PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
3824 static void cliRateProfile(char *cmdline)
3826 if (isEmpty(cmdline)) {
3827 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
3828 return;
3829 } else {
3830 const int i = atoi(cmdline);
3831 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
3832 changeControlRateProfile(i);
3833 cliRateProfile("");
3834 } else {
3835 cliPrintErrorLinef("RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
3840 static void cliDumpPidProfile(uint8_t pidProfileIndex, dumpFlags_t dumpMask)
3842 if (pidProfileIndex >= PID_PROFILE_COUNT) {
3843 // Faulty values
3844 return;
3847 pidProfileIndexToUse = pidProfileIndex;
3849 cliPrintLinefeed();
3850 cliProfile("");
3852 char profileStr[10];
3853 tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
3854 dumpAllValues(PROFILE_VALUE, dumpMask, profileStr);
3856 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
3859 static void cliDumpRateProfile(uint8_t rateProfileIndex, dumpFlags_t dumpMask)
3861 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
3862 // Faulty values
3863 return;
3866 rateProfileIndexToUse = rateProfileIndex;
3868 cliPrintLinefeed();
3869 cliRateProfile("");
3871 char rateProfileStr[14];
3872 tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
3873 dumpAllValues(PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
3875 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
3878 #ifdef USE_CLI_BATCH
3879 static void cliPrintCommandBatchWarning(const char *warning)
3881 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
3882 if (warning) {
3883 cliPrintErrorLinef(warning);
3887 static void resetCommandBatch(void)
3889 commandBatchActive = false;
3890 commandBatchError = false;
3893 static void cliBatch(char *cmdline)
3895 if (strncasecmp(cmdline, "start", 5) == 0) {
3896 if (!commandBatchActive) {
3897 commandBatchActive = true;
3898 commandBatchError = false;
3900 cliPrintLine("Command batch started");
3901 } else if (strncasecmp(cmdline, "end", 3) == 0) {
3902 if (commandBatchActive && commandBatchError) {
3903 cliPrintCommandBatchWarning(NULL);
3904 } else {
3905 cliPrintLine("Command batch ended");
3907 resetCommandBatch();
3908 } else {
3909 cliPrintErrorLinef("Invalid option");
3912 #endif
3914 static void cliSave(char *cmdline)
3916 UNUSED(cmdline);
3918 #ifdef USE_CLI_BATCH
3919 if (commandBatchActive && commandBatchError) {
3920 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3921 resetCommandBatch();
3922 return;
3924 #endif
3926 cliPrintHashLine("saving");
3928 #if defined(USE_BOARD_INFO)
3929 if (boardInformationUpdated) {
3930 persistBoardInformation();
3932 #if defined(USE_SIGNATURE)
3933 if (signatureUpdated) {
3934 persistSignature();
3936 #endif
3937 #endif // USE_BOARD_INFO
3939 if (featureMaskIsCopied) {
3940 writeEEPROMWithFeatures(featureMaskCopy);
3941 } else {
3942 writeEEPROM();
3945 cliReboot();
3948 static void cliDefaults(char *cmdline)
3950 bool saveConfigs;
3952 if (isEmpty(cmdline)) {
3953 saveConfigs = true;
3954 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
3955 saveConfigs = false;
3956 } else {
3957 return;
3960 cliPrintHashLine("resetting to defaults");
3962 resetConfigs();
3964 #ifdef USE_CLI_BATCH
3965 // Reset only the error state and allow the batch active state to remain.
3966 // This way if a "defaults nosave" was issued after the "batch on" we'll
3967 // only reset the current error state but the batch will still be active
3968 // for subsequent commands.
3969 commandBatchError = false;
3970 #endif
3972 if (saveConfigs) {
3973 cliSave(NULL);
3977 void cliPrintVarDefault(const clivalue_t *value)
3979 const pgRegistry_t *pg = pgFind(value->pgn);
3980 if (pg) {
3981 const char *defaultFormat = "Default value: ";
3982 const int valueOffset = getValueOffset(value);
3983 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
3984 if (!equalsDefault) {
3985 cliPrintf(defaultFormat, value->name);
3986 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
3987 cliPrintLinefeed();
3992 STATIC_UNIT_TESTED void cliGet(char *cmdline)
3994 const clivalue_t *val;
3995 int matchedCommands = 0;
3997 pidProfileIndexToUse = getCurrentPidProfileIndex();
3998 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
4000 backupAndResetConfigs();
4002 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4003 if (strcasestr(valueTable[i].name, cmdline)) {
4004 val = &valueTable[i];
4005 if (matchedCommands > 0) {
4006 cliPrintLinefeed();
4008 cliPrintf("%s = ", valueTable[i].name);
4009 cliPrintVar(val, 0);
4010 cliPrintLinefeed();
4011 switch (val->type & VALUE_SECTION_MASK) {
4012 case PROFILE_VALUE:
4013 cliProfile("");
4015 break;
4016 case PROFILE_RATE_VALUE:
4017 cliRateProfile("");
4019 break;
4020 default:
4022 break;
4024 cliPrintVarRange(val);
4025 cliPrintVarDefault(val);
4027 matchedCommands++;
4031 restoreConfigs();
4033 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4034 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4036 if (matchedCommands) {
4037 return;
4040 cliPrintErrorLinef("INVALID NAME");
4043 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
4045 while (*(bufEnd - 1) == ' ') {
4046 bufEnd--;
4049 return bufEnd - bufBegin;
4052 uint16_t cliGetSettingIndex(char *name, uint8_t length)
4054 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4055 const char *settingName = valueTable[i].name;
4057 // ensure exact match when setting to prevent setting variables with shorter names
4058 if (strncasecmp(name, settingName, strlen(settingName)) == 0 && length == strlen(settingName)) {
4059 return i;
4062 return valueTableEntryCount;
4065 STATIC_UNIT_TESTED void cliSet(char *cmdline)
4067 const uint32_t len = strlen(cmdline);
4068 char *eqptr;
4070 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
4071 cliPrintLine("Current settings: ");
4073 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
4074 const clivalue_t *val = &valueTable[i];
4075 cliPrintf("%s = ", valueTable[i].name);
4076 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
4077 cliPrintLinefeed();
4079 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
4080 // has equals
4082 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
4084 // skip the '=' and any ' ' characters
4085 eqptr++;
4086 eqptr = skipSpace(eqptr);
4088 const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
4089 if (index >= valueTableEntryCount) {
4090 cliPrintErrorLinef("INVALID NAME");
4091 return;
4093 const clivalue_t *val = &valueTable[index];
4095 bool valueChanged = false;
4096 int16_t value = 0;
4097 switch (val->type & VALUE_MODE_MASK) {
4098 case MODE_DIRECT: {
4099 if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
4100 uint32_t value = strtoul(eqptr, NULL, 10);
4102 if (value <= val->config.u32Max) {
4103 cliSetVar(val, value);
4104 valueChanged = true;
4106 } else {
4107 int value = atoi(eqptr);
4109 int min;
4110 int max;
4111 getMinMax(val, &min, &max);
4113 if (value >= min && value <= max) {
4114 cliSetVar(val, value);
4115 valueChanged = true;
4120 break;
4121 case MODE_LOOKUP:
4122 case MODE_BITSET: {
4123 int tableIndex;
4124 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
4125 tableIndex = TABLE_OFF_ON;
4126 } else {
4127 tableIndex = val->config.lookup.tableIndex;
4129 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
4130 bool matched = false;
4131 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
4132 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
4134 if (matched) {
4135 value = tableValueIndex;
4137 cliSetVar(val, value);
4138 valueChanged = true;
4143 break;
4145 case MODE_ARRAY: {
4146 const uint8_t arrayLength = val->config.array.length;
4147 char *valPtr = eqptr;
4149 int i = 0;
4150 while (i < arrayLength && valPtr != NULL) {
4151 // skip spaces
4152 valPtr = skipSpace(valPtr);
4154 // process substring starting at valPtr
4155 // note: no need to copy substrings for atoi()
4156 // it stops at the first character that cannot be converted...
4157 switch (val->type & VALUE_TYPE_MASK) {
4158 default:
4159 case VAR_UINT8:
4161 // fetch data pointer
4162 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
4163 // store value
4164 *data = (uint8_t)atoi((const char*) valPtr);
4167 break;
4168 case VAR_INT8:
4170 // fetch data pointer
4171 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
4172 // store value
4173 *data = (int8_t)atoi((const char*) valPtr);
4176 break;
4177 case VAR_UINT16:
4179 // fetch data pointer
4180 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
4181 // store value
4182 *data = (uint16_t)atoi((const char*) valPtr);
4185 break;
4186 case VAR_INT16:
4188 // fetch data pointer
4189 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
4190 // store value
4191 *data = (int16_t)atoi((const char*) valPtr);
4194 break;
4197 // find next comma (or end of string)
4198 valPtr = strchr(valPtr, ',') + 1;
4200 i++;
4204 // mark as changed
4205 valueChanged = true;
4207 break;
4208 case MODE_STRING: {
4209 char *valPtr = eqptr;
4210 valPtr = skipSpace(valPtr);
4212 const unsigned int len = strlen(valPtr);
4213 const uint8_t min = val->config.string.minlength;
4214 const uint8_t max = val->config.string.maxlength;
4215 const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
4216 strlen((char *)cliGetValuePointer(val)) == 0 ||
4217 strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
4219 if (updatable && len > 0 && len <= max) {
4220 memset((char *)cliGetValuePointer(val), 0, max);
4221 if (len >= min && strncmp(valPtr, emptyName, len)) {
4222 strncpy((char *)cliGetValuePointer(val), valPtr, len);
4224 valueChanged = true;
4225 } else {
4226 cliPrintErrorLinef("STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
4229 break;
4232 if (valueChanged) {
4233 cliPrintf("%s set to ", val->name);
4234 cliPrintVar(val, 0);
4235 } else {
4236 cliPrintErrorLinef("INVALID VALUE");
4237 cliPrintVarRange(val);
4240 return;
4241 } else {
4242 // no equals, check for matching variables.
4243 cliGet(cmdline);
4247 static void cliStatus(char *cmdline)
4249 UNUSED(cmdline);
4251 // MCU type, clock, vrefint, core temperature
4253 cliPrintf("MCU %s Clock=%dMHz", MCU_TYPE_NAME, (SystemCoreClock / 1000000));
4255 #ifdef STM32F4
4256 // Only F4 is capable of switching between HSE/HSI (for now)
4257 int sysclkSource = SystemSYSCLKSource();
4259 const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
4260 const char *PLLSource[] = { "-HSI", "-HSE" };
4262 int pllSource;
4264 if (sysclkSource >= 2) {
4265 pllSource = SystemPLLSource();
4268 cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
4269 #endif
4271 #ifdef USE_ADC_INTERNAL
4272 uint16_t vrefintMv = getVrefMv();
4273 int16_t coretemp = getCoreTemperatureCelsius();
4274 cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
4275 #else
4276 cliPrintLinefeed();
4277 #endif
4279 // Stack and config sizes and usages
4281 cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
4282 #ifdef STACK_CHECK
4283 cliPrintf(", Stack used: %d", stackUsedSize());
4284 #endif
4285 cliPrintLinefeed();
4287 #ifdef EEPROM_IN_RAM
4288 #define CONFIG_SIZE EEPROM_SIZE
4289 #else
4290 #define CONFIG_SIZE (&__config_end - &__config_start)
4291 #endif
4292 cliPrintLinef("Config size: %d, Max available config: %d", getEEPROMConfigSize(), CONFIG_SIZE);
4294 // Sensors
4296 #if defined(USE_SENSOR_NAMES)
4297 const uint32_t detectedSensorsMask = sensorsMask();
4298 for (uint32_t i = 0; ; i++) {
4299 if (sensorTypeNames[i] == NULL) {
4300 break;
4302 const uint32_t mask = (1 << i);
4303 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
4304 const uint8_t sensorHardwareIndex = detectedSensors[i];
4305 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
4306 if (i) {
4307 cliPrint(", ");
4309 cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
4310 #if defined(USE_ACC)
4311 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
4312 cliPrintf(".%c", acc.dev.revisionCode);
4314 #endif
4317 cliPrintLinefeed();
4318 #endif /* USE_SENSOR_NAMES */
4320 // Uptime and wall clock
4322 cliPrintf("System Uptime: %d seconds", millis() / 1000);
4324 #ifdef USE_RTC_TIME
4325 char buf[FORMATTED_DATE_TIME_BUFSIZE];
4326 dateTime_t dt;
4327 if (rtcGetDateTime(&dt)) {
4328 dateTimeFormatLocal(buf, &dt);
4329 cliPrintf(", Current Time: %s", buf);
4331 #endif
4332 cliPrintLinefeed();
4334 // Run status
4336 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
4337 const int rxRate = currentRxRefreshRate == 0 ? 0 : (int)(1000000.0f / ((float)currentRxRefreshRate));
4338 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
4339 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
4340 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
4342 // Battery meter
4344 cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
4346 // Other devices and status
4348 #ifdef USE_I2C
4349 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
4350 #else
4351 const uint16_t i2cErrorCounter = 0;
4352 #endif
4353 cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
4355 #ifdef USE_SDCARD
4356 cliSdInfo(NULL);
4357 #endif
4359 cliPrint("Arming disable flags:");
4360 armingDisableFlags_e flags = getArmingDisableFlags();
4361 while (flags) {
4362 const int bitpos = ffs(flags) - 1;
4363 flags &= ~(1 << bitpos);
4364 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
4366 cliPrintLinefeed();
4369 #if defined(USE_TASK_STATISTICS)
4370 static void cliTasks(char *cmdline)
4372 UNUSED(cmdline);
4373 int maxLoadSum = 0;
4374 int averageLoadSum = 0;
4376 #ifndef MINIMAL_CLI
4377 if (systemConfig()->task_statistics) {
4378 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
4379 } else {
4380 cliPrintLine("Task list");
4382 #endif
4383 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
4384 cfTaskInfo_t taskInfo;
4385 getTaskInfo(taskId, &taskInfo);
4386 if (taskInfo.isEnabled) {
4387 int taskFrequency;
4388 int subTaskFrequency = 0;
4389 if (taskId == TASK_GYROPID) {
4390 subTaskFrequency = taskInfo.movingAverageCycleTime == 0.0f ? 0.0f : (int)(1000000.0f / (taskInfo.movingAverageCycleTime));
4391 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
4392 if (pidConfig()->pid_process_denom > 1) {
4393 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4394 } else {
4395 taskFrequency = subTaskFrequency;
4396 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
4398 } else {
4399 taskFrequency = taskInfo.averageDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.averageDeltaTime));
4400 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
4402 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
4403 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
4404 if (taskId != TASK_SERIAL) {
4405 maxLoadSum += maxLoad;
4406 averageLoadSum += averageLoad;
4408 if (systemConfig()->task_statistics) {
4409 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
4410 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
4411 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
4412 } else {
4413 cliPrintLinef("%6d", taskFrequency);
4415 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
4416 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
4419 schedulerResetTaskMaxExecutionTime(taskId);
4422 if (systemConfig()->task_statistics) {
4423 cfCheckFuncInfo_t checkFuncInfo;
4424 getCheckFuncInfo(&checkFuncInfo);
4425 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
4426 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
4429 #endif
4431 static void cliVersion(char *cmdline)
4433 UNUSED(cmdline);
4435 cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
4436 FC_FIRMWARE_NAME,
4437 targetName,
4438 systemConfig()->boardIdentifier,
4439 FC_VERSION_STRING,
4440 buildDate,
4441 buildTime,
4442 shortGitRevision,
4443 MSP_API_VERSION_STRING
4445 #ifdef FEATURE_CUT_LEVEL
4446 cliPrintLinef(" / FEATURE CUT LEVEL %d", FEATURE_CUT_LEVEL);
4447 #else
4448 cliPrintLinefeed();
4449 #endif
4452 #ifdef USE_RC_SMOOTHING_FILTER
4453 static void cliRcSmoothing(char *cmdline)
4455 UNUSED(cmdline);
4456 rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
4457 cliPrint("# RC Smoothing Type: ");
4458 if (rxConfig()->rc_smoothing_type == RC_SMOOTHING_TYPE_FILTER) {
4459 cliPrintLine("FILTER");
4460 uint16_t avgRxFrameMs = rcSmoothingData->averageFrameTimeUs;
4461 if (rcSmoothingAutoCalculate()) {
4462 cliPrint("# Detected RX frame rate: ");
4463 if (avgRxFrameMs == 0) {
4464 cliPrintLine("NO SIGNAL");
4465 } else {
4466 cliPrintLinef("%d.%dms", avgRxFrameMs / 1000, avgRxFrameMs % 1000);
4469 cliPrint("# Input filter type: ");
4470 cliPrintLinef(lookupTables[TABLE_RC_SMOOTHING_INPUT_TYPE].values[rcSmoothingData->inputFilterType]);
4471 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingData->inputCutoffFrequency);
4472 if (rcSmoothingData->inputCutoffSetting == 0) {
4473 cliPrintLine("(auto)");
4474 } else {
4475 cliPrintLine("(manual)");
4477 cliPrint("# Derivative filter type: ");
4478 cliPrintLinef(lookupTables[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE].values[rcSmoothingData->derivativeFilterType]);
4479 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingData->derivativeCutoffFrequency);
4480 if (rcSmoothingData->derivativeFilterType == RC_SMOOTHING_DERIVATIVE_OFF) {
4481 cliPrintLine("off)");
4482 } else {
4483 if (rcSmoothingData->derivativeCutoffSetting == 0) {
4484 cliPrintLine("auto)");
4485 } else {
4486 cliPrintLine("manual)");
4489 } else {
4490 cliPrintLine("INTERPOLATION");
4493 #endif // USE_RC_SMOOTHING_FILTER
4495 #if defined(USE_RESOURCE_MGMT)
4497 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
4499 typedef struct {
4500 const uint8_t owner;
4501 pgn_t pgn;
4502 uint8_t stride;
4503 uint8_t offset;
4504 const uint8_t maxIndex;
4505 } cliResourceValue_t;
4507 // Handy macros for keeping the table tidy.
4508 // DEFS : Single entry
4509 // DEFA : Array of uint8_t (stride = 1)
4510 // DEFW : Wider stride case; array of structs.
4512 #define DEFS(owner, pgn, type, member) \
4513 { owner, pgn, 0, offsetof(type, member), 0 }
4515 #define DEFA(owner, pgn, type, member, max) \
4516 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
4518 #define DEFW(owner, pgn, type, member, max) \
4519 { owner, pgn, sizeof(type), offsetof(type, member), max }
4521 const cliResourceValue_t resourceTable[] = {
4522 #ifdef USE_BEEPER
4523 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
4524 #endif
4525 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
4526 #ifdef USE_SERVOS
4527 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
4528 #endif
4529 #if defined(USE_PPM)
4530 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
4531 #endif
4532 #if defined(USE_PWM)
4533 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
4534 #endif
4535 #ifdef USE_RANGEFINDER_HCSR04
4536 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
4537 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
4538 #endif
4539 #ifdef USE_LED_STRIP
4540 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
4541 #endif
4542 #ifdef USE_UART
4543 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
4544 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
4545 #endif
4546 #ifdef USE_INVERTER
4547 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
4548 #endif
4549 #ifdef USE_I2C
4550 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
4551 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
4552 #endif
4553 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
4554 #ifdef USE_SPEKTRUM_BIND
4555 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
4556 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
4557 #endif
4558 #ifdef USE_TRANSPONDER
4559 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
4560 #endif
4561 #ifdef USE_SPI
4562 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
4563 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
4564 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
4565 #endif
4566 #ifdef USE_ESCSERIAL
4567 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
4568 #endif
4569 #ifdef USE_CAMERA_CONTROL
4570 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
4571 #endif
4572 #ifdef USE_ADC
4573 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
4574 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
4575 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
4576 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
4577 #endif
4578 #ifdef USE_BARO
4579 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
4580 #endif
4581 #ifdef USE_MAG
4582 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
4583 #ifdef USE_MAG_DATA_READY_SIGNAL
4584 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
4585 #endif
4586 #endif
4587 #ifdef USE_SDCARD_SPI
4588 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
4589 #endif
4590 #ifdef USE_SDCARD
4591 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
4592 #endif
4593 #ifdef USE_PINIO
4594 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
4595 #endif
4596 #if defined(USE_USB_MSC)
4597 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
4598 #endif
4599 #ifdef USE_FLASH_CHIP
4600 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
4601 #endif
4602 #ifdef USE_MAX7456
4603 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
4604 #endif
4605 #ifdef USE_RX_SPI
4606 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
4607 DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
4608 DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
4609 DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
4610 #if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
4611 DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
4612 DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
4613 #if defined(USE_RX_CC2500_SPI_DIVERSITY)
4614 DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
4615 #endif
4616 #endif
4617 #endif
4618 #ifdef USE_GYRO_EXTI
4619 DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
4620 #endif
4621 DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
4622 #ifdef USE_USB_DETECT
4623 DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
4624 #endif
4625 #ifdef USE_VTX_RTC6705
4626 DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
4627 DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
4628 DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
4629 DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
4630 #endif
4633 #undef DEFS
4634 #undef DEFA
4635 #undef DEFW
4637 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
4639 const pgRegistry_t* rec = pgFind(value.pgn);
4640 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
4643 static void printResource(dumpFlags_t dumpMask, const char *headingStr)
4645 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
4646 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
4647 const char* owner = ownerNames[resourceTable[i].owner];
4648 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
4649 const void *currentConfig;
4650 const void *defaultConfig;
4651 if (configIsInCopy) {
4652 currentConfig = pg->copy;
4653 defaultConfig = pg->address;
4654 } else {
4655 currentConfig = pg->address;
4656 defaultConfig = NULL;
4659 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
4660 const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
4661 ioTag_t ioTagDefault = NULL;
4662 if (defaultConfig) {
4663 ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
4666 const bool equalsDefault = ioTag == ioTagDefault;
4667 const char *format = "resource %s %d %c%02d";
4668 const char *formatUnassigned = "resource %s %d NONE";
4669 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
4670 if (ioTagDefault) {
4671 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
4672 } else if (defaultConfig) {
4673 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
4675 if (ioTag) {
4676 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
4677 } else if (!(dumpMask & HIDE_UNUSED)) {
4678 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
4684 static void printResourceOwner(uint8_t owner, uint8_t index)
4686 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
4688 if (resourceTable[owner].maxIndex > 0) {
4689 cliPrintf(" %d", RESOURCE_INDEX(index));
4693 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
4695 if (!newTag) {
4696 return;
4699 const char * format = "\r\nNOTE: %c%02d already assigned to ";
4700 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
4701 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
4702 ioTag_t *tag = getIoTag(resourceTable[r], i);
4703 if (*tag == newTag) {
4704 bool cleared = false;
4705 if (r == resourceIndex) {
4706 if (i == index) {
4707 continue;
4709 *tag = IO_TAG_NONE;
4710 cleared = true;
4713 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
4715 printResourceOwner(r, i);
4717 if (cleared) {
4718 cliPrintf(". ");
4719 printResourceOwner(r, i);
4720 cliPrintf(" disabled");
4723 cliPrintLine(".");
4729 static bool strToPin(char *pch, ioTag_t *tag)
4731 if (strcasecmp(pch, "NONE") == 0) {
4732 *tag = IO_TAG_NONE;
4733 return true;
4734 } else {
4735 unsigned pin = 0;
4736 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
4738 if (port < 8) {
4739 pch++;
4740 pin = atoi(pch);
4741 if (pin < 16) {
4742 *tag = DEFIO_TAG_MAKE(port, pin);
4743 return true;
4747 return false;
4750 static void printDma(void)
4752 cliPrintLinefeed();
4754 #ifdef MINIMAL_CLI
4755 cliPrintLine("DMA:");
4756 #else
4757 cliPrintLine("Currently active DMA:");
4758 cliRepeat('-', 20);
4759 #endif
4760 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
4761 const char* owner;
4762 owner = ownerNames[dmaGetOwner(i)];
4764 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
4765 uint8_t resourceIndex = dmaGetResourceIndex(i);
4766 if (resourceIndex > 0) {
4767 cliPrintLinef(" %s %d", owner, resourceIndex);
4768 } else {
4769 cliPrintLinef(" %s", owner);
4774 #ifdef USE_DMA_SPEC
4776 typedef struct dmaoptEntry_s {
4777 char *device;
4778 dmaPeripheral_e peripheral;
4779 pgn_t pgn;
4780 uint8_t stride;
4781 uint8_t offset;
4782 uint8_t maxIndex;
4783 } dmaoptEntry_t;
4785 // Handy macros for keeping the table tidy.
4786 // DEFS : Single entry
4787 // DEFA : Array of uint8_t (stride = 1)
4788 // DEFW : Wider stride case; array of structs.
4790 #define DEFS(device, peripheral, pgn, type, member) \
4791 { device, peripheral, pgn, 0, offsetof(type, member), 0 }
4793 #define DEFA(device, peripheral, pgn, type, member, max) \
4794 { device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max }
4796 #define DEFW(device, peripheral, pgn, type, member, max) \
4797 { device, peripheral, pgn, sizeof(type), offsetof(type, member), max }
4799 dmaoptEntry_t dmaoptEntryTable[] = {
4800 DEFW("SPI_TX", DMA_PERIPH_SPI_TX, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT),
4801 DEFW("SPI_RX", DMA_PERIPH_SPI_RX, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT),
4802 DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT),
4803 DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
4804 DEFA("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, UARTDEV_CONFIG_MAX),
4805 DEFA("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, UARTDEV_CONFIG_MAX),
4808 #undef DEFS
4809 #undef DEFA
4810 #undef DEFW
4812 #define DMA_OPT_UI_INDEX(i) ((i) + 1)
4813 #define DMA_OPT_STRING_BUFSIZE 5
4815 static void optToString(int optval, char *buf)
4817 if (optval == DMA_OPT_UNUSED) {
4818 memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
4819 } else {
4820 tfp_sprintf(buf, "%d", optval);
4824 static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
4826 if (dmaopt != DMA_OPT_UNUSED) {
4827 printValue(dumpMask, equalsDefault,
4828 "dma %s %d %d",
4829 entry->device, DMA_OPT_UI_INDEX(index), dmaopt);
4831 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
4832 dmaCode_t dmaCode = 0;
4833 if (dmaChannelSpec) {
4834 dmaCode = dmaChannelSpec->code;
4836 printValue(dumpMask, equalsDefault,
4837 "# %s %d: DMA%d Stream %d Channel %d",
4838 entry->device, DMA_OPT_UI_INDEX(index), DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
4839 } else if (!(dumpMask & HIDE_UNUSED)) {
4840 printValue(dumpMask, equalsDefault,
4841 "dma %s %d NONE",
4842 entry->device, DMA_OPT_UI_INDEX(index));
4846 static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
4848 const pgRegistry_t* pg = pgFind(entry->pgn);
4849 const void *currentConfig;
4850 const void *defaultConfig;
4852 if (configIsInCopy) {
4853 currentConfig = pg->copy;
4854 defaultConfig = pg->address;
4855 } else {
4856 currentConfig = pg->address;
4857 defaultConfig = NULL;
4860 dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
4861 dmaoptValue_t defaultOpt;
4863 if (defaultConfig) {
4864 defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
4865 } else {
4866 defaultOpt = DMA_OPT_UNUSED;
4869 bool equalsDefault = currentOpt == defaultOpt;
4870 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
4872 if (defaultConfig) {
4873 printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
4876 printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
4877 return headingStr;
4880 #if defined(USE_TIMER_MGMT)
4881 static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
4883 const char *format = "dma pin %c%02d %d";
4885 if (dmaopt != DMA_OPT_UNUSED) {
4886 const bool printDetails = printValue(dumpMask, equalsDefault, format,
4887 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
4888 dmaopt
4891 if (printDetails) {
4892 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
4893 dmaCode_t dmaCode = 0;
4894 if (dmaChannelSpec) {
4895 dmaCode = dmaChannelSpec->code;
4896 printValue(dumpMask, false,
4897 "# pin %c%02d: DMA%d Stream %d Channel %d",
4898 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
4899 DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
4903 } else if (!(dumpMask & HIDE_UNUSED)) {
4904 printValue(dumpMask, equalsDefault,
4905 "dma pin %c%02d NONE",
4906 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
4911 static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
4913 const ioTag_t ioTag = currentConfig[index].ioTag;
4915 if (!ioTag) {
4916 return headingStr;
4919 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
4920 const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
4922 dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
4923 bool equalsDefault = defaultDmaopt == dmaopt;
4924 if (defaultConfig) {
4925 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
4926 if (defaultConfig[i].ioTag == ioTag) {
4927 defaultDmaopt = defaultConfig[i].dmaopt;
4929 // 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.
4930 equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
4932 tagsInUse[index] = true;
4934 break;
4939 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
4941 if (defaultConfig) {
4942 printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
4945 printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
4946 return headingStr;
4948 #endif
4950 static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
4952 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
4953 for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
4954 dmaoptEntry_t *entry = &dmaoptEntryTable[i];
4955 for (int index = 0; index < entry->maxIndex; index++) {
4956 headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
4960 #if defined(USE_TIMER_MGMT)
4961 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
4962 const timerIOConfig_t *currentConfig;
4963 const timerIOConfig_t *defaultConfig;
4965 if (configIsInCopy) {
4966 currentConfig = (timerIOConfig_t *)pg->copy;
4967 defaultConfig = (timerIOConfig_t *)pg->address;
4968 } else {
4969 currentConfig = (timerIOConfig_t *)pg->address;
4970 defaultConfig = NULL;
4973 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
4974 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
4975 headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
4978 if (defaultConfig) {
4979 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
4980 if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
4981 const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
4982 headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
4983 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
4985 printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
4989 #endif
4992 static void cliDmaopt(char *cmdline)
4994 char *pch = NULL;
4995 char *saveptr;
4997 // Peripheral name or command option
4998 pch = strtok_r(cmdline, " ", &saveptr);
4999 if (!pch) {
5000 printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
5002 return;
5003 } else if (strcasecmp(pch, "list") == 0) {
5004 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5006 return;
5009 dmaoptEntry_t *entry = NULL;
5010 for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
5011 if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
5012 entry = &dmaoptEntryTable[i];
5016 if (!entry && strcasecmp(pch, "pin") != 0) {
5017 cliPrintErrorLinef("BAD DEVICE: %s", pch);
5018 return;
5021 // Index
5022 dmaoptValue_t orgval = DMA_OPT_UNUSED;
5024 int index = 0;
5025 dmaoptValue_t *optaddr = NULL;
5027 ioTag_t ioTag = IO_TAG_NONE;
5028 #if defined(USE_TIMER_MGMT)
5029 timerIOConfig_t *timerIoConfig = NULL;
5030 #endif
5031 const timerHardware_t *timer = NULL;
5032 pch = strtok_r(NULL, " ", &saveptr);
5033 if (entry) {
5034 index = atoi(pch) - 1;
5035 if (index < 0 || index >= entry->maxIndex) {
5036 cliPrintErrorLinef("BAD INDEX: '%s'", pch ? pch : "");
5037 return;
5040 const pgRegistry_t* pg = pgFind(entry->pgn);
5041 const void *currentConfig;
5042 if (configIsInCopy) {
5043 currentConfig = pg->copy;
5044 } else {
5045 currentConfig = pg->address;
5047 optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
5048 orgval = *optaddr;
5049 } else {
5050 // It's a pin
5051 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
5052 cliPrintErrorLinef("INVALID PIN: '%s'", pch ? pch : "");
5054 return;
5057 orgval = dmaoptByTag(ioTag);
5058 #if defined(USE_TIMER_MGMT)
5059 timerIoConfig = timerIoConfigByTag(ioTag);
5060 #endif
5061 timer = timerGetByTag(ioTag);
5064 // opt or list
5065 pch = strtok_r(NULL, " ", &saveptr);
5066 if (!pch) {
5067 if (entry) {
5068 printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
5069 } else {
5070 printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
5073 return;
5074 } else if (strcasecmp(pch, "list") == 0) {
5075 // Show possible opts
5076 const dmaChannelSpec_t *dmaChannelSpec;
5077 if (entry) {
5078 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
5079 cliPrintLinef("# %d: DMA%d Stream %d channel %d", opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5081 } else {
5082 for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
5083 cliPrintLinef("# %d: DMA%d Stream %d channel %d", opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
5087 return;
5088 } else if (pch) {
5089 int optval;
5090 if (strcasecmp(pch, "none") == 0) {
5091 optval = DMA_OPT_UNUSED;
5092 } else {
5093 optval = atoi(pch);
5095 if (entry) {
5096 if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
5097 cliPrintErrorLinef("INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
5099 return;
5101 } else {
5102 if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
5103 cliPrintErrorLinef("INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5105 return;
5110 char optvalString[DMA_OPT_STRING_BUFSIZE];
5111 optToString(optval, optvalString);
5113 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5114 optToString(orgval, orgvalString);
5116 if (optval != orgval) {
5117 if (entry) {
5118 *optaddr = optval;
5120 cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
5121 } else {
5122 #if defined(USE_TIMER_MGMT)
5123 timerIoConfig->dmaopt = optval;
5124 #endif
5126 cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5128 } else {
5129 if (entry) {
5130 cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
5131 } else {
5132 cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
5137 #endif // USE_DMA_SPEC
5139 static void cliDma(char* cmdline)
5141 int len = strlen(cmdline);
5142 if (len && strncasecmp(cmdline, "show", len) == 0) {
5143 printDma();
5145 return;
5148 #if defined(USE_DMA_SPEC)
5149 cliDmaopt(cmdline);
5150 #else
5151 cliShowParseError();
5152 #endif
5155 static void cliResource(char *cmdline)
5157 char *pch = NULL;
5158 char *saveptr;
5160 pch = strtok_r(cmdline, " ", &saveptr);
5161 if (!pch) {
5162 printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
5164 return;
5165 } else if (strcasecmp(pch, "show") == 0) {
5166 #ifdef MINIMAL_CLI
5167 cliPrintLine("IO");
5168 #else
5169 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
5170 cliRepeat('-', 20);
5171 #endif
5172 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
5173 const char* owner;
5174 owner = ownerNames[ioRecs[i].owner];
5176 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
5177 if (ioRecs[i].index > 0) {
5178 cliPrintf(" %d", ioRecs[i].index);
5180 cliPrintLinefeed();
5183 pch = strtok_r(NULL, " ", &saveptr);
5184 if (strcasecmp(pch, "all") == 0) {
5185 cliDma("show");
5188 return;
5191 uint8_t resourceIndex = 0;
5192 int index = 0;
5193 for (resourceIndex = 0; ; resourceIndex++) {
5194 if (resourceIndex >= ARRAYLEN(resourceTable)) {
5195 cliPrintErrorLinef("INVALID RESOURCE NAME: '%s'", pch);
5196 return;
5199 const char * resourceName = ownerNames[resourceTable[resourceIndex].owner];
5200 if (strncasecmp(pch, resourceName, strlen(resourceName)) == 0) {
5201 break;
5205 pch = strtok_r(NULL, " ", &saveptr);
5206 index = atoi(pch);
5208 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
5209 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
5210 cliShowArgumentRangeError("INDEX", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
5211 return;
5213 index -= 1;
5215 pch = strtok_r(NULL, " ", &saveptr);
5218 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
5220 if (strlen(pch) > 0) {
5221 if (strToPin(pch, tag)) {
5222 if (*tag == IO_TAG_NONE) {
5223 #ifdef MINIMAL_CLI
5224 cliPrintLine("Freed");
5225 #else
5226 cliPrintLine("Resource is freed");
5227 #endif
5228 return;
5229 } else {
5230 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
5231 if (rec) {
5232 resourceCheck(resourceIndex, index, *tag);
5233 #ifdef MINIMAL_CLI
5234 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5235 #else
5236 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
5237 #endif
5238 } else {
5239 cliShowParseError();
5241 return;
5246 cliShowParseError();
5249 #endif // USE_RESOURCE_MGMT
5251 #ifdef USE_TIMER_MGMT
5252 static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
5254 const char *format = "timer %c%02d af%d";
5255 const char *emptyFormat = "timer %c%02d NONE";
5257 if (timerIndex > 0) {
5258 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
5259 const bool printDetails = printValue(dumpMask, equalsDefault, format,
5260 IO_GPIOPortIdxByTag(ioTag) + 'A',
5261 IO_GPIOPinIdxByTag(ioTag),
5262 timer->alternateFunction
5264 if (printDetails) {
5265 printValue(dumpMask, false,
5266 "# pin %c%02d: TIM%d CH%d%s (AF%d)",
5267 IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
5268 timerGetTIMNumber(timer->tim),
5269 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5270 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
5271 timer->alternateFunction
5274 } else {
5275 printValue(dumpMask, equalsDefault, emptyFormat,
5276 IO_GPIOPortIdxByTag(ioTag) + 'A',
5277 IO_GPIOPinIdxByTag(ioTag)
5282 static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
5284 const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
5285 const timerIOConfig_t *currentConfig;
5286 const timerIOConfig_t *defaultConfig;
5288 headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
5289 if (configIsInCopy) {
5290 currentConfig = (timerIOConfig_t *)pg->copy;
5291 defaultConfig = (timerIOConfig_t *)pg->address;
5292 } else {
5293 currentConfig = (timerIOConfig_t *)pg->address;
5294 defaultConfig = NULL;
5297 bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
5298 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5299 const ioTag_t ioTag = currentConfig[i].ioTag;
5301 if (!ioTag) {
5302 continue;
5305 const uint8_t timerIndex = currentConfig[i].index;
5307 uint8_t defaultTimerIndex = 0;
5308 if (defaultConfig) {
5309 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5310 if (defaultConfig[i].ioTag == ioTag) {
5311 defaultTimerIndex = defaultConfig[i].index;
5312 tagsInUse[i] = true;
5314 break;
5319 const bool equalsDefault = defaultTimerIndex == timerIndex;
5320 headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
5321 if (defaultConfig && defaultTimerIndex) {
5322 printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
5325 printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
5328 if (defaultConfig) {
5329 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5330 if (!tagsInUse[i] && defaultConfig[i].ioTag) {
5331 headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
5332 printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
5334 printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
5340 #define TIMER_INDEX_UNDEFINED -1
5341 #define TIMER_AF_STRING_BUFSIZE 5
5343 static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
5345 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
5346 if (!timer) {
5347 memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
5348 } else {
5349 tfp_sprintf(buf, "af%d", timer->alternateFunction);
5353 static void cliTimer(char *cmdline)
5355 int len = strlen(cmdline);
5357 if (len == 0) {
5358 printTimer(DUMP_MASTER, NULL);
5360 return;
5361 } else if (strncasecmp(cmdline, "list", len) == 0) {
5362 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5364 return;
5365 } else if (strncasecmp(cmdline, "show", len) == 0) {
5366 cliPrintErrorLinef("NOT IMPLEMENTED YET");
5368 return;
5371 char *pch = NULL;
5372 char *saveptr;
5374 ioTag_t ioTag = IO_TAG_NONE;
5375 pch = strtok_r(cmdline, " ", &saveptr);
5376 if (!pch || !strToPin(pch, &ioTag)) {
5377 cliShowParseError();
5379 return;
5380 } else if (!IOGetByTag(ioTag)) {
5381 cliPrintErrorLinef("PIN NOT USED ON BOARD.");
5383 return;
5386 int timerIOIndex = TIMER_INDEX_UNDEFINED;
5387 bool isExistingTimerOpt = false;
5388 /* find existing entry, or go for next available */
5389 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
5390 if (timerIOConfig(i)->ioTag == ioTag) {
5391 timerIOIndex = i;
5392 isExistingTimerOpt = true;
5394 break;
5397 /* first available empty slot */
5398 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
5399 timerIOIndex = i;
5403 if (timerIOIndex < 0) {
5404 cliPrintErrorLinef("PIN TIMER MAP FULL.");
5406 return;
5409 pch = strtok_r(NULL, " ", &saveptr);
5410 if (pch) {
5411 int timerIndex;
5412 if (strcasecmp(pch, "list") == 0) {
5413 /* output the list of available options */
5414 const timerHardware_t *timer;
5415 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5416 cliPrintLinef("# af%d: TIM%d CH%d%s",
5417 timer->alternateFunction,
5418 timerGetTIMNumber(timer->tim),
5419 CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
5420 timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
5424 return;
5425 } else if (strcasecmp(pch, "none") == 0) {
5426 timerIndex = TIMER_INDEX_UNDEFINED;
5427 } else if (strncasecmp(pch, "af", 2) == 0) {
5428 unsigned alternateFunction = atoi(&pch[2]);
5430 const timerHardware_t *timer;
5431 for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
5432 if (timer->alternateFunction == alternateFunction) {
5433 timerIndex = index;
5435 break;
5439 if (!timer) {
5440 cliPrintErrorLinef("INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5442 return;
5444 } else {
5445 timerIndex = atoi(pch);
5447 const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex + 1);
5449 if (!timer) {
5450 cliPrintErrorLinef("INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
5452 return;
5456 int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
5457 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
5458 timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
5459 timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
5461 char optvalString[DMA_OPT_STRING_BUFSIZE];
5462 alternateFunctionToString(ioTag, timerIndex, optvalString);
5464 char orgvalString[DMA_OPT_STRING_BUFSIZE];
5465 alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
5467 if (timerIndex == oldTimerIndex) {
5468 cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
5469 } else {
5470 cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
5473 return;
5474 } else {
5475 printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
5477 return;
5480 #endif
5482 #ifdef USE_DSHOT_TELEMETRY
5483 static void cliDshotTelemetryInfo(char *cmdline)
5485 UNUSED(cmdline);
5487 if (useDshotTelemetry) {
5488 cliPrintLinef("Dshot reads: %u", readDoneCount);
5489 cliPrintLinef("Dshot invalid pkts: %u", dshotInvalidPacketCount);
5490 extern uint32_t setDirectionMicros;
5491 cliPrintLinef("Dshot irq micros: %u", setDirectionMicros);
5492 cliPrintLinefeed();
5494 #ifdef USE_DSHOT_TELEMETRY_STATS
5495 cliPrintLine("Motor RPM Invalid");
5496 cliPrintLine("===== ===== =======");
5497 #else
5498 cliPrintLine("Motor RPM");
5499 cliPrintLine("===== =====");
5500 #endif
5501 for (uint8_t i = 0; i < getMotorCount(); i++) {
5502 cliPrintf("%5d %5d ", i, (int)getDshotTelemetry(i));
5503 #ifdef USE_DSHOT_TELEMETRY_STATS
5504 if (isDshotMotorTelemetryActive(i)) {
5505 const int calcPercent = getDshotTelemetryMotorInvalidPercent(i);
5506 cliPrintLinef("%3d.%02d%%", calcPercent / 100, calcPercent % 100);
5507 } else {
5508 cliPrintLine("NO DATA");
5510 #else
5511 cliPrintLinefeed();
5512 #endif
5514 cliPrintLinefeed();
5516 const bool proshot = (motorConfig()->dev.motorPwmProtocol == PWM_TYPE_PROSHOT1000);
5517 const int modulo = proshot ? MOTOR_NIBBLE_LENGTH_PROSHOT : MOTOR_BITLENGTH;
5518 const int len = proshot ? 8 : DSHOT_TELEMETRY_INPUT_LEN;
5519 for (int i = 0; i < len; i++) {
5520 cliPrintf("%u ", (int)inputBuffer[i]);
5522 cliPrintLinefeed();
5523 for (int i = 1; i < len; i+=2) {
5524 cliPrintf("%u ", (int)(inputBuffer[i] + modulo - inputBuffer[i-1]) % modulo);
5526 cliPrintLinefeed();
5527 } else {
5528 cliPrintLine("Dshot telemetry not enabled");
5531 #endif
5533 static void printConfig(char *cmdline, bool doDiff)
5535 dumpFlags_t dumpMask = DUMP_MASTER;
5536 char *options;
5537 if ((options = checkCommand(cmdline, "master"))) {
5538 dumpMask = DUMP_MASTER; // only
5539 } else if ((options = checkCommand(cmdline, "profile"))) {
5540 dumpMask = DUMP_PROFILE; // only
5541 } else if ((options = checkCommand(cmdline, "rates"))) {
5542 dumpMask = DUMP_RATES; // only
5543 } else if ((options = checkCommand(cmdline, "hardware"))) {
5544 dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
5545 } else if ((options = checkCommand(cmdline, "all"))) {
5546 dumpMask = DUMP_ALL; // all profiles and rates
5547 } else {
5548 options = cmdline;
5551 if (doDiff) {
5552 dumpMask = dumpMask | DO_DIFF;
5555 if (checkCommand(options, "defaults")) {
5556 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
5557 } else if (checkCommand(options, "bare")) {
5558 dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
5561 backupAndResetConfigs();
5563 #ifdef USE_CLI_BATCH
5564 bool batchModeEnabled = false;
5565 #endif
5566 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
5567 cliPrintHashLine("version");
5568 cliVersion(NULL);
5570 if (!(dumpMask & BARE)) {
5571 #ifdef USE_CLI_BATCH
5572 cliPrintHashLine("start the command batch");
5573 cliPrintLine("batch start");
5574 batchModeEnabled = true;
5575 #endif
5577 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
5578 cliPrintHashLine("reset configuration to default settings");
5579 cliPrintLine("defaults nosave");
5583 #if defined(USE_BOARD_INFO)
5584 cliPrintLinefeed();
5585 printBoardName(dumpMask);
5586 printManufacturerId(dumpMask);
5587 #endif
5589 if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
5590 cliMcuId(NULL);
5591 #if defined(USE_SIGNATURE)
5592 cliSignature("");
5593 #endif
5596 if (!(dumpMask & HARDWARE_ONLY)) {
5597 printName(dumpMask, &pilotConfig_Copy);
5600 #ifdef USE_RESOURCE_MGMT
5601 printResource(dumpMask, "resources");
5602 #if defined(USE_TIMER_MGMT)
5603 printTimer(dumpMask, "timer");
5604 #endif
5605 #ifdef USE_DMA_SPEC
5606 printDmaopt(dumpMask, "dma");
5607 #endif
5608 #endif
5610 if (!(dumpMask & HARDWARE_ONLY)) {
5611 #ifndef USE_QUAD_MIXER_ONLY
5612 const char *mixerHeadingStr = "mixer";
5613 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
5614 mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
5615 const char *formatMixer = "mixer %s";
5616 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
5617 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
5619 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
5621 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
5623 #ifdef USE_SERVOS
5624 printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
5626 const char *servoMixHeadingStr = "servo mixer";
5627 if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
5628 cliPrintHashLine(servoMixHeadingStr);
5629 cliPrintLine("smix reset\r\n");
5630 servoMixHeadingStr = NULL;
5632 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
5633 #endif
5634 #endif
5636 printFeature(dumpMask, &featureConfig_Copy, featureConfig(), "feature");
5638 #if defined(USE_BEEPER)
5639 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
5641 #if defined(USE_DSHOT)
5642 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
5643 #endif
5644 #endif // USE_BEEPER
5646 printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
5648 printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
5650 #ifdef USE_LED_STRIP_STATUS_MODE
5651 printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
5653 printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
5655 printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
5656 #endif
5658 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
5660 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
5662 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
5664 #ifdef USE_VTX_CONTROL
5665 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
5666 #endif
5668 #ifdef USE_VTX_TABLE
5669 printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
5670 #endif
5672 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
5675 if (dumpMask & HARDWARE_ONLY) {
5676 dumpAllValues(HARDWARE_VALUE, dumpMask, "master");
5677 } else {
5678 dumpAllValues(MASTER_VALUE, dumpMask, "master");
5680 if (dumpMask & DUMP_ALL) {
5681 for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
5682 cliDumpPidProfile(pidProfileIndex, dumpMask);
5685 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
5687 if (!(dumpMask & BARE)) {
5688 cliPrintHashLine("restore original profile selection");
5690 cliProfile("");
5693 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
5695 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
5696 cliDumpRateProfile(rateIndex, dumpMask);
5699 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
5701 if (!(dumpMask & BARE)) {
5702 cliPrintHashLine("restore original rateprofile selection");
5704 cliRateProfile("");
5706 cliPrintHashLine("save configuration");
5707 cliPrint("save");
5708 #ifdef USE_CLI_BATCH
5709 batchModeEnabled = false;
5710 #endif
5713 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
5714 } else {
5715 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
5717 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
5720 } else if (dumpMask & DUMP_PROFILE) {
5721 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
5722 } else if (dumpMask & DUMP_RATES) {
5723 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
5726 #ifdef USE_CLI_BATCH
5727 if (batchModeEnabled) {
5728 cliPrintHashLine("end the command batch");
5729 cliPrintLine("batch end");
5731 #endif
5733 // restore configs from copies
5734 restoreConfigs();
5737 static void cliDump(char *cmdline)
5739 printConfig(cmdline, false);
5742 static void cliDiff(char *cmdline)
5744 printConfig(cmdline, true);
5747 #if defined(USE_USB_MSC)
5748 static void cliMsc(char *cmdline)
5750 if (mscCheckFilesystemReady()) {
5751 #ifdef USE_RTC_TIME
5752 int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
5753 if (!isEmpty(cmdline)) {
5754 timezoneOffsetMinutes = atoi(cmdline);
5755 if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
5756 cliPrintErrorLinef("INVALID TIMEZONE OFFSET");
5757 return;
5760 #else
5761 int timezoneOffsetMinutes = 0;
5762 UNUSED(cmdline);
5763 #endif
5764 cliPrintHashLine("Restarting in mass storage mode");
5765 cliPrint("\r\nRebooting");
5766 bufWriterFlush(cliWriter);
5767 waitForSerialPortToFinishTransmitting(cliPort);
5768 stopPwmAllMotors();
5770 systemResetToMsc(timezoneOffsetMinutes);
5771 } else {
5772 cliPrintHashLine("Storage not present or failed to initialize!");
5775 #endif
5778 typedef struct {
5779 const char *name;
5780 #ifndef MINIMAL_CLI
5781 const char *description;
5782 const char *args;
5783 #endif
5784 void (*func)(char *cmdline);
5785 } clicmd_t;
5787 #ifndef MINIMAL_CLI
5788 #define CLI_COMMAND_DEF(name, description, args, method) \
5790 name , \
5791 description , \
5792 args , \
5793 method \
5795 #else
5796 #define CLI_COMMAND_DEF(name, description, args, method) \
5798 name, \
5799 method \
5801 #endif
5803 static void cliHelp(char *cmdline);
5805 // should be sorted a..z for bsearch()
5806 const clicmd_t cmdTable[] = {
5807 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
5808 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
5809 #ifdef USE_CLI_BATCH
5810 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
5811 #endif
5812 #if defined(USE_BEEPER)
5813 #if defined(USE_DSHOT)
5814 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
5815 "\t<->[name]", cliBeacon),
5816 #endif
5817 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
5818 "\t<->[name]", cliBeeper),
5819 #endif // USE_BEEPER
5820 #ifdef USE_RX_SPI
5821 CLI_COMMAND_DEF("bind_rx_spi", "initiate binding for RX SPI", NULL, cliRxSpiBind),
5822 #endif
5823 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL, cliBootloader),
5824 #if defined(USE_BOARD_INFO)
5825 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
5826 #endif
5827 #ifdef USE_LED_STRIP_STATUS_MODE
5828 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
5829 #endif
5830 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults),
5831 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
5832 #ifdef USE_RESOURCE_MGMT
5833 #ifdef USE_DMA_SPEC
5834 CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
5835 #else
5836 CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
5837 #endif
5838 #endif
5839 #ifdef USE_DSHOT_TELEMETRY
5840 CLI_COMMAND_DEF("dshot_telemetry_info", "disply dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
5841 #endif
5842 #ifdef USE_DSHOT
5843 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
5844 #endif
5845 CLI_COMMAND_DEF("dump", "dump configuration",
5846 "[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
5847 #ifdef USE_ESCSERIAL
5848 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
5849 #endif
5850 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
5851 CLI_COMMAND_DEF("feature", "configure features",
5852 "list\r\n"
5853 "\t<->[name]", cliFeature),
5854 #ifdef USE_FLASHFS
5855 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
5856 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
5857 #ifdef USE_FLASH_TOOLS
5858 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
5859 CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
5860 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
5861 #endif
5862 #endif
5863 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
5864 #ifdef USE_GPS
5865 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
5866 #endif
5867 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
5868 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
5869 #endif
5870 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
5871 #ifdef USE_LED_STRIP_STATUS_MODE
5872 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
5873 #endif
5874 #if defined(USE_BOARD_INFO)
5875 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
5876 #endif
5877 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
5878 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
5879 #ifndef USE_QUAD_MIXER_ONLY
5880 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
5881 #endif
5882 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
5883 #ifdef USE_LED_STRIP_STATUS_MODE
5884 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
5885 #endif
5886 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
5887 #ifdef USE_USB_MSC
5888 #ifdef USE_RTC_TIME
5889 CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
5890 #else
5891 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
5892 #endif
5893 #endif
5894 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
5895 #ifndef MINIMAL_CLI
5896 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
5897 #endif
5898 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
5899 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
5900 #ifdef USE_RC_SMOOTHING_FILTER
5901 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
5902 #endif // USE_RC_SMOOTHING_FILTER
5903 #ifdef USE_RESOURCE_MGMT
5904 CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
5905 #endif
5906 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
5907 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
5908 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
5909 #ifdef USE_SDCARD
5910 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
5911 #endif
5912 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
5913 #if defined(USE_SERIAL_PASSTHROUGH)
5914 #if defined(USE_PINIO)
5915 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [dtr pinio|'reset']", cliSerialPassthrough),
5916 #else
5917 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] ['reset']", cliSerialPassthrough),
5918 #endif
5919 #endif
5920 #ifdef USE_SERVOS
5921 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
5922 #endif
5923 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
5924 #if defined(USE_SIGNATURE)
5925 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
5926 #endif
5927 #ifdef USE_SERVOS
5928 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
5929 "\treset\r\n"
5930 "\tload <mixer>\r\n"
5931 "\treverse <servo> <source> r|n", cliServoMix),
5932 #endif
5933 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
5934 #if defined(USE_TASK_STATISTICS)
5935 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
5936 #endif
5937 #ifdef USE_TIMER_MGMT
5938 CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<altenate function>|none|<option(deprecated)>] | list | show", cliTimer),
5939 #endif
5940 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
5941 #ifdef USE_VTX_CONTROL
5942 #ifdef MINIMAL_CLI
5943 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
5944 #else
5945 CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
5946 #endif
5947 #endif
5948 #ifdef USE_VTX_TABLE
5949 CLI_COMMAND_DEF("vtxtable", "vtx frequency able", "<band> <bandname> <bandletter> <freq> ... <freq>\r\n", cliVtxTable),
5950 #endif
5953 static void cliHelp(char *cmdline)
5955 UNUSED(cmdline);
5957 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
5958 cliPrint(cmdTable[i].name);
5959 #ifndef MINIMAL_CLI
5960 if (cmdTable[i].description) {
5961 cliPrintf(" - %s", cmdTable[i].description);
5963 if (cmdTable[i].args) {
5964 cliPrintf("\r\n\t%s", cmdTable[i].args);
5966 #endif
5967 cliPrintLinefeed();
5971 void cliProcess(void)
5973 if (!cliWriter) {
5974 return;
5977 // Be a little bit tricky. Flush the last inputs buffer, if any.
5978 bufWriterFlush(cliWriter);
5980 while (serialRxBytesWaiting(cliPort)) {
5981 uint8_t c = serialRead(cliPort);
5982 if (c == '\t' || c == '?') {
5983 // do tab completion
5984 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
5985 uint32_t i = bufferIndex;
5986 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
5987 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
5988 continue;
5989 if (!pstart)
5990 pstart = cmd;
5991 pend = cmd;
5993 if (pstart) { /* Buffer matches one or more commands */
5994 for (; ; bufferIndex++) {
5995 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
5996 break;
5997 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
5998 /* Unambiguous -- append a space */
5999 cliBuffer[bufferIndex++] = ' ';
6000 cliBuffer[bufferIndex] = '\0';
6001 break;
6003 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
6006 if (!bufferIndex || pstart != pend) {
6007 /* Print list of ambiguous matches */
6008 cliPrint("\r\033[K");
6009 for (cmd = pstart; cmd <= pend; cmd++) {
6010 cliPrint(cmd->name);
6011 cliWrite('\t');
6013 cliPrompt();
6014 i = 0; /* Redraw prompt */
6016 for (; i < bufferIndex; i++)
6017 cliWrite(cliBuffer[i]);
6018 } else if (!bufferIndex && c == 4) { // CTRL-D
6019 cliExit(cliBuffer);
6020 return;
6021 } else if (c == 12) { // NewPage / CTRL-L
6022 // clear screen
6023 cliPrint("\033[2J\033[1;1H");
6024 cliPrompt();
6025 } else if (bufferIndex && (c == '\n' || c == '\r')) {
6026 // enter pressed
6027 cliPrintLinefeed();
6029 // Strip comment starting with # from line
6030 char *p = cliBuffer;
6031 p = strchr(p, '#');
6032 if (NULL != p) {
6033 bufferIndex = (uint32_t)(p - cliBuffer);
6036 // Strip trailing whitespace
6037 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
6038 bufferIndex--;
6041 // Process non-empty lines
6042 if (bufferIndex > 0) {
6043 cliBuffer[bufferIndex] = 0; // null terminate
6045 const clicmd_t *cmd;
6046 char *options;
6047 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
6048 if ((options = checkCommand(cliBuffer, cmd->name))) {
6049 break;
6052 if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
6053 cmd->func(options);
6054 } else {
6055 cliPrintError("UNKNOWN COMMAND, TRY 'HELP'");
6057 bufferIndex = 0;
6060 memset(cliBuffer, 0, sizeof(cliBuffer));
6062 // 'exit' will reset this flag, so we don't need to print prompt again
6063 if (!cliMode)
6064 return;
6066 cliPrompt();
6067 } else if (c == 127) {
6068 // backspace
6069 if (bufferIndex) {
6070 cliBuffer[--bufferIndex] = 0;
6071 cliPrint("\010 \010");
6073 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
6074 if (!bufferIndex && c == ' ')
6075 continue; // Ignore leading spaces
6076 cliBuffer[bufferIndex++] = c;
6077 cliWrite(c);
6082 void cliEnter(serialPort_t *serialPort)
6084 cliMode = 1;
6085 cliPort = serialPort;
6086 setPrintfSerialPort(cliPort);
6087 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
6089 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
6091 #ifndef MINIMAL_CLI
6092 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
6093 #else
6094 cliPrintLine("\r\nCLI");
6095 #endif
6096 setArmingDisabled(ARMING_DISABLED_CLI);
6098 cliPrompt();
6100 #ifdef USE_CLI_BATCH
6101 resetCommandBatch();
6102 #endif
6105 void cliInit(const serialConfig_t *serialConfig)
6107 UNUSED(serialConfig);
6109 #endif // USE_CLI