Add CLI validation of parameters and print a corrupted value message
[betaflight.git] / src / main / interface / cli.c
blob299a2c354faf62eee445a15db53cc1ec9f20af1b
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 "cms/cms.h"
49 #include "common/axis.h"
50 #include "common/color.h"
51 #include "common/maths.h"
52 #include "common/printf.h"
53 #include "common/strtol.h"
54 #include "common/time.h"
55 #include "common/typeconversion.h"
56 #include "common/utils.h"
58 #include "config/config_eeprom.h"
59 #include "config/feature.h"
61 #include "drivers/accgyro/accgyro.h"
62 #include "drivers/adc.h"
63 #include "drivers/buf_writer.h"
64 #include "drivers/bus_spi.h"
65 #include "drivers/camera_control.h"
66 #include "drivers/compass/compass.h"
67 #include "drivers/display.h"
68 #include "drivers/dma.h"
69 #include "drivers/flash.h"
70 #include "drivers/inverter.h"
71 #include "drivers/io.h"
72 #include "drivers/io_impl.h"
73 #include "drivers/light_led.h"
74 #include "drivers/rangefinder/rangefinder_hcsr04.h"
75 #include "drivers/sdcard.h"
76 #include "drivers/sensor.h"
77 #include "drivers/serial.h"
78 #include "drivers/serial_escserial.h"
79 #include "drivers/sound_beeper.h"
80 #include "drivers/stack_check.h"
81 #include "drivers/system.h"
82 #include "drivers/time.h"
83 #include "drivers/timer.h"
84 #include "drivers/transponder_ir.h"
85 #include "drivers/usb_msc.h"
86 #include "drivers/vtx_common.h"
88 #include "fc/board_info.h"
89 #include "fc/config.h"
90 #include "fc/controlrate_profile.h"
91 #include "fc/core.h"
92 #include "fc/rc.h"
93 #include "fc/rc_adjustments.h"
94 #include "fc/rc_controls.h"
95 #include "fc/runtime_config.h"
97 #include "flight/failsafe.h"
98 #include "flight/imu.h"
99 #include "flight/mixer.h"
100 #include "flight/pid.h"
101 #include "flight/position.h"
102 #include "flight/servos.h"
104 #include "interface/cli.h"
105 #include "interface/msp.h"
106 #include "interface/msp_box.h"
107 #include "interface/msp_protocol.h"
108 #include "interface/settings.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/osd.h"
117 #include "io/serial.h"
118 #include "io/transponder_ir.h"
119 #include "io/usb_msc.h"
120 #include "io/vtx_control.h"
121 #include "io/vtx.h"
123 #include "pg/adc.h"
124 #include "pg/beeper.h"
125 #include "pg/beeper_dev.h"
126 #include "pg/board.h"
127 #include "pg/bus_i2c.h"
128 #include "pg/bus_spi.h"
129 #include "pg/max7456.h"
130 #include "pg/pinio.h"
131 #include "pg/pg.h"
132 #include "pg/pg_ids.h"
133 #include "pg/rx.h"
134 #include "pg/rx_spi.h"
135 #include "pg/rx_pwm.h"
136 #include "pg/timerio.h"
137 #include "pg/usb.h"
139 #include "rx/rx.h"
140 #include "rx/spektrum.h"
141 #include "rx/cc2500_frsky_common.h"
142 #include "rx/cc2500_frsky_x.h"
144 #include "scheduler/scheduler.h"
146 #include "sensors/acceleration.h"
147 #include "sensors/adcinternal.h"
148 #include "sensors/barometer.h"
149 #include "sensors/battery.h"
150 #include "sensors/boardalignment.h"
151 #include "sensors/compass.h"
152 #include "sensors/esc_sensor.h"
153 #include "sensors/gyro.h"
154 #include "sensors/sensors.h"
156 #include "telemetry/frsky_hub.h"
157 #include "telemetry/telemetry.h"
160 static serialPort_t *cliPort;
162 #ifdef STM32F1
163 #define CLI_IN_BUFFER_SIZE 128
164 #else
165 // Space required to set array parameters
166 #define CLI_IN_BUFFER_SIZE 256
167 #endif
168 #define CLI_OUT_BUFFER_SIZE 64
170 static bufWriter_t *cliWriter;
171 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
173 static char cliBuffer[CLI_IN_BUFFER_SIZE];
174 static uint32_t bufferIndex = 0;
176 static bool configIsInCopy = false;
178 #define CURRENT_PROFILE_INDEX -1
179 static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
180 static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
182 static bool featureMaskIsCopied = false;
183 static uint32_t featureMaskCopy;
185 #if defined(USE_BOARD_INFO)
186 static bool boardInformationUpdated = false;
187 #if defined(USE_SIGNATURE)
188 static bool signatureUpdated = false;
189 #endif
190 #endif // USE_BOARD_INFO
192 static const char* const emptyName = "-";
193 static const char* const emptyString = "";
195 #ifndef USE_QUAD_MIXER_ONLY
196 // sync this with mixerMode_e
197 static const char * const mixerNames[] = {
198 "TRI", "QUADP", "QUADX", "BI",
199 "GIMBAL", "Y6", "HEX6",
200 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
201 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
202 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
203 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
205 #endif
207 // sync this with features_e
208 static const char * const featureNames[] = {
209 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
210 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
211 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
212 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
213 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
214 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
217 // sync this with rxFailsafeChannelMode_e
218 static const char rxFailsafeModeCharacters[] = "ahs";
220 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
221 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_INVALID },
222 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
225 #if defined(USE_SENSOR_NAMES)
226 // sync this with sensors_e
227 static const char * const sensorTypeNames[] = {
228 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
231 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
233 static const char * const *sensorHardwareNames[] = {
234 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
236 #endif // USE_SENSOR_NAMES
238 static void backupPgConfig(const pgRegistry_t *pg)
240 memcpy(pg->copy, pg->address, pg->size);
243 static void restorePgConfig(const pgRegistry_t *pg)
245 memcpy(pg->address, pg->copy, pg->size);
248 static void backupConfigs(void)
250 // make copies of configs to do differencing
251 PG_FOREACH(pg) {
252 backupPgConfig(pg);
255 configIsInCopy = true;
258 static void restoreConfigs(void)
260 PG_FOREACH(pg) {
261 restorePgConfig(pg);
264 configIsInCopy = false;
267 static void backupAndResetConfigs(void)
269 backupConfigs();
270 // reset all configs to defaults to do differencing
271 resetConfigs();
274 static void cliPrint(const char *str)
276 while (*str) {
277 bufWriterAppend(cliWriter, *str++);
279 bufWriterFlush(cliWriter);
282 static void cliPrintLinefeed(void)
284 cliPrint("\r\n");
287 static void cliPrintLine(const char *str)
289 cliPrint(str);
290 cliPrintLinefeed();
293 #ifdef MINIMAL_CLI
294 #define cliPrintHashLine(str)
295 #else
296 static void cliPrintHashLine(const char *str)
298 cliPrint("\r\n# ");
299 cliPrintLine(str);
301 #endif
303 static void cliPutp(void *p, char ch)
305 bufWriterAppend(p, ch);
308 typedef enum {
309 DUMP_MASTER = (1 << 0),
310 DUMP_PROFILE = (1 << 1),
311 DUMP_RATES = (1 << 2),
312 DUMP_ALL = (1 << 3),
313 DO_DIFF = (1 << 4),
314 SHOW_DEFAULTS = (1 << 5),
315 HIDE_UNUSED = (1 << 6)
316 } dumpFlags_e;
318 static void cliPrintfva(const char *format, va_list va)
320 tfp_format(cliWriter, cliPutp, format, va);
321 bufWriterFlush(cliWriter);
324 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
326 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
327 va_list va;
328 va_start(va, format);
329 cliPrintfva(format, va);
330 va_end(va);
331 cliPrintLinefeed();
332 return true;
333 } else {
334 return false;
338 static void cliWrite(uint8_t ch)
340 bufWriterAppend(cliWriter, ch);
343 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
345 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
346 cliWrite('#');
348 va_list va;
349 va_start(va, format);
350 cliPrintfva(format, va);
351 va_end(va);
352 cliPrintLinefeed();
353 return true;
354 } else {
355 return false;
359 static void cliPrintf(const char *format, ...)
361 va_list va;
362 va_start(va, format);
363 cliPrintfva(format, va);
364 va_end(va);
368 static void cliPrintLinef(const char *format, ...)
370 va_list va;
371 va_start(va, format);
372 cliPrintfva(format, va);
373 va_end(va);
374 cliPrintLinefeed();
377 static void cliPrintErrorLinef(const char *format, ...)
379 cliPrint("###ERROR### ");
380 va_list va;
381 va_start(va, format);
382 cliPrintfva(format, va);
383 va_end(va);
384 cliPrintLinefeed();
387 static void cliPrintCorruptMessage(int value)
389 cliPrintf("%d ###CORRUPTED CONFIG###", value);
392 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
394 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
395 for (int i = 0; i < var->config.array.length; i++) {
396 switch (var->type & VALUE_TYPE_MASK) {
397 default:
398 case VAR_UINT8:
399 // uint8_t array
400 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
401 break;
403 case VAR_INT8:
404 // int8_t array
405 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
406 break;
408 case VAR_UINT16:
409 // uin16_t array
410 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
411 break;
413 case VAR_INT16:
414 // int16_t array
415 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
416 break;
419 if (i < var->config.array.length - 1) {
420 cliPrint(",");
423 } else {
424 int value = 0;
426 switch (var->type & VALUE_TYPE_MASK) {
427 case VAR_UINT8:
428 value = *(uint8_t *)valuePointer;
429 break;
431 case VAR_INT8:
432 value = *(int8_t *)valuePointer;
433 break;
435 case VAR_UINT16:
436 case VAR_INT16:
437 value = *(int16_t *)valuePointer;
438 break;
439 case VAR_UINT32:
440 value = *(uint32_t *)valuePointer;
441 break;
444 switch (var->type & VALUE_MODE_MASK) {
445 case MODE_DIRECT:
446 if ((value < var->config.minmax.min) || (value > var->config.minmax.max)) {
447 cliPrintCorruptMessage(value);
448 } else {
449 cliPrintf("%d", value);
450 if (full) {
451 cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
454 break;
455 case MODE_LOOKUP:
456 if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
457 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
458 } else {
459 cliPrintCorruptMessage(value);
461 break;
462 case MODE_BITSET:
463 if (value & 1 << var->config.bitpos) {
464 cliPrintf("ON");
465 } else {
466 cliPrintf("OFF");
473 static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
475 bool result = true;
476 int elementCount = 1;
477 uint32_t mask = 0xffffffff;
479 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
480 elementCount = var->config.array.length;
482 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
483 mask = 1 << var->config.bitpos;
485 for (int i = 0; i < elementCount; i++) {
486 switch (var->type & VALUE_TYPE_MASK) {
487 case VAR_UINT8:
488 result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
489 break;
491 case VAR_INT8:
492 result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
493 break;
495 case VAR_UINT16:
496 result = result && (((int16_t *)ptr)[i] & mask) == (((int16_t *)ptrDefault)[i] & mask);
497 break;
498 case VAR_INT16:
499 result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
500 break;
501 case VAR_UINT32:
502 result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
503 break;
507 return result;
510 static uint8_t getPidProfileIndexToUse()
512 return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
515 static uint8_t getRateProfileIndexToUse()
517 return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
521 static uint16_t getValueOffset(const clivalue_t *value)
523 switch (value->type & VALUE_SECTION_MASK) {
524 case MASTER_VALUE:
525 return value->offset;
526 case PROFILE_VALUE:
527 return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
528 case PROFILE_RATE_VALUE:
529 return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
531 return 0;
534 void *cliGetValuePointer(const clivalue_t *value)
536 const pgRegistry_t* rec = pgFind(value->pgn);
537 if (configIsInCopy) {
538 return CONST_CAST(void *, rec->copy + getValueOffset(value));
539 } else {
540 return CONST_CAST(void *, rec->address + getValueOffset(value));
544 const void *cliGetDefaultPointer(const clivalue_t *value)
546 const pgRegistry_t* rec = pgFind(value->pgn);
547 return rec->address + getValueOffset(value);
550 static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
552 const pgRegistry_t *pg = pgFind(value->pgn);
553 #ifdef DEBUG
554 if (!pg) {
555 cliPrintLinef("VALUE %s ERROR", value->name);
556 return; // if it's not found, the pgn shouldn't be in the value table!
558 #endif
560 const char *format = "set %s = ";
561 const char *defaultFormat = "#set %s = ";
562 const int valueOffset = getValueOffset(value);
563 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
565 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
566 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
567 cliPrintf(defaultFormat, value->name);
568 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
569 cliPrintLinefeed();
571 cliPrintf(format, value->name);
572 printValuePointer(value, pg->copy + valueOffset, false);
573 cliPrintLinefeed();
577 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
579 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
580 const clivalue_t *value = &valueTable[i];
581 bufWriterFlush(cliWriter);
582 if ((value->type & VALUE_SECTION_MASK) == valueSection) {
583 dumpPgValue(value, dumpMask);
588 static void cliPrintVar(const clivalue_t *var, bool full)
590 const void *ptr = cliGetValuePointer(var);
592 printValuePointer(var, ptr, full);
595 static void cliPrintVarRange(const clivalue_t *var)
597 switch (var->type & VALUE_MODE_MASK) {
598 case (MODE_DIRECT): {
599 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
601 break;
602 case (MODE_LOOKUP): {
603 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
604 cliPrint("Allowed values: ");
605 bool firstEntry = true;
606 for (unsigned i = 0; i < tableEntry->valueCount; i++) {
607 if (tableEntry->values[i]) {
608 if (!firstEntry) {
609 cliPrint(", ");
611 cliPrintf("%s", tableEntry->values[i]);
612 firstEntry = false;
615 cliPrintLinefeed();
617 break;
618 case (MODE_ARRAY): {
619 cliPrintLinef("Array length: %d", var->config.array.length);
621 break;
622 case (MODE_BITSET): {
623 cliPrintLinef("Allowed values: OFF, ON");
625 break;
629 static void cliSetVar(const clivalue_t *var, const int16_t value)
631 void *ptr = cliGetValuePointer(var);
632 uint32_t workValue;
633 uint32_t mask;
635 if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
636 switch (var->type & VALUE_TYPE_MASK) {
637 case VAR_UINT8:
638 mask = (1 << var->config.bitpos) & 0xff;
639 if (value) {
640 workValue = *(uint8_t *)ptr | mask;
641 } else {
642 workValue = *(uint8_t *)ptr & ~mask;
644 *(uint8_t *)ptr = workValue;
645 break;
647 case VAR_UINT16:
648 mask = (1 << var->config.bitpos) & 0xffff;
649 if (value) {
650 workValue = *(uint16_t *)ptr | mask;
651 } else {
652 workValue = *(uint16_t *)ptr & ~mask;
654 *(uint16_t *)ptr = workValue;
655 break;
657 case VAR_UINT32:
658 mask = 1 << var->config.bitpos;
659 if (value) {
660 workValue = *(uint32_t *)ptr | mask;
661 } else {
662 workValue = *(uint32_t *)ptr & ~mask;
664 *(uint32_t *)ptr = workValue;
665 break;
668 } else {
669 switch (var->type & VALUE_TYPE_MASK) {
670 case VAR_UINT8:
671 *(uint8_t *)ptr = value;
672 break;
674 case VAR_INT8:
675 *(int8_t *)ptr = value;
676 break;
678 case VAR_UINT16:
679 case VAR_INT16:
680 *(int16_t *)ptr = value;
681 break;
686 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
687 static void cliRepeat(char ch, uint8_t len)
689 for (int i = 0; i < len; i++) {
690 bufWriterAppend(cliWriter, ch);
692 cliPrintLinefeed();
694 #endif
696 static void cliPrompt(void)
698 cliPrint("\r\n# ");
701 static void cliShowParseError(void)
703 cliPrintErrorLinef("Parse error");
706 static void cliShowArgumentRangeError(char *name, int min, int max)
708 cliPrintErrorLinef("%s not between %d and %d", name, min, max);
711 static const char *nextArg(const char *currentArg)
713 const char *ptr = strchr(currentArg, ' ');
714 while (ptr && *ptr == ' ') {
715 ptr++;
718 return ptr;
721 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
723 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
724 ptr = nextArg(ptr);
725 if (ptr) {
726 int val = atoi(ptr);
727 val = CHANNEL_VALUE_TO_STEP(val);
728 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
729 if (argIndex == 0) {
730 range->startStep = val;
731 } else {
732 range->endStep = val;
734 (*validArgumentCount)++;
739 return ptr;
742 // Check if a string's length is zero
743 static bool isEmpty(const char *string)
745 return (string == NULL || *string == '\0') ? true : false;
748 static void printRxFailsafe(uint8_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs)
750 // print out rxConfig failsafe settings
751 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
752 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
753 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
754 const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
755 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
756 if (requireValue) {
757 const char *format = "rxfail %u %c %d";
758 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
759 channel,
760 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
761 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
763 cliDumpPrintLinef(dumpMask, equalsDefault, format,
764 channel,
765 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
766 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
768 } else {
769 const char *format = "rxfail %u %c";
770 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
771 channel,
772 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
774 cliDumpPrintLinef(dumpMask, equalsDefault, format,
775 channel,
776 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
782 static void cliRxFailsafe(char *cmdline)
784 uint8_t channel;
785 char buf[3];
787 if (isEmpty(cmdline)) {
788 // print out rxConfig failsafe settings
789 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
790 cliRxFailsafe(itoa(channel, buf, 10));
792 } else {
793 const char *ptr = cmdline;
794 channel = atoi(ptr++);
795 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
797 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
799 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
800 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
801 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
803 ptr = nextArg(ptr);
804 if (ptr) {
805 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
806 if (p) {
807 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
808 mode = rxFailsafeModesTable[type][requestedMode];
809 } else {
810 mode = RX_FAILSAFE_MODE_INVALID;
812 if (mode == RX_FAILSAFE_MODE_INVALID) {
813 cliShowParseError();
814 return;
817 requireValue = mode == RX_FAILSAFE_MODE_SET;
819 ptr = nextArg(ptr);
820 if (ptr) {
821 if (!requireValue) {
822 cliShowParseError();
823 return;
825 uint16_t value = atoi(ptr);
826 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
827 if (value > MAX_RXFAIL_RANGE_STEP) {
828 cliPrintLine("Value out of range");
829 return;
832 channelFailsafeConfig->step = value;
833 } else if (requireValue) {
834 cliShowParseError();
835 return;
837 channelFailsafeConfig->mode = mode;
840 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
842 // double use of cliPrintf below
843 // 1. acknowledge interpretation on command,
844 // 2. query current setting on single item,
846 if (requireValue) {
847 cliPrintLinef("rxfail %u %c %d",
848 channel,
849 modeCharacter,
850 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
852 } else {
853 cliPrintLinef("rxfail %u %c",
854 channel,
855 modeCharacter
858 } else {
859 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
864 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
866 const char *format = "aux %u %u %u %u %u %u %u";
867 // print out aux channel settings
868 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
869 const modeActivationCondition_t *mac = &modeActivationConditions[i];
870 bool equalsDefault = false;
871 if (defaultModeActivationConditions) {
872 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
873 equalsDefault = !memcmp(mac, macDefault, sizeof(*mac));
874 const box_t *box = findBoxByBoxId(macDefault->modeId);
875 const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
876 if (box) {
877 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
879 box->permanentId,
880 macDefault->auxChannelIndex,
881 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
882 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
883 macDefault->modeLogic,
884 linkedTo ? linkedTo->permanentId : 0
888 const box_t *box = findBoxByBoxId(mac->modeId);
889 const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
890 if (box) {
891 cliDumpPrintLinef(dumpMask, equalsDefault, format,
893 box->permanentId,
894 mac->auxChannelIndex,
895 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
896 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
897 mac->modeLogic,
898 linkedTo ? linkedTo->permanentId : 0
904 static void cliAux(char *cmdline)
906 int i, val = 0;
907 const char *ptr;
909 if (isEmpty(cmdline)) {
910 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
911 } else {
912 ptr = cmdline;
913 i = atoi(ptr++);
914 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
915 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
916 uint8_t validArgumentCount = 0;
917 ptr = nextArg(ptr);
918 if (ptr) {
919 val = atoi(ptr);
920 const box_t *box = findBoxByPermanentId(val);
921 if (box) {
922 mac->modeId = box->boxId;
923 validArgumentCount++;
926 ptr = nextArg(ptr);
927 if (ptr) {
928 val = atoi(ptr);
929 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
930 mac->auxChannelIndex = val;
931 validArgumentCount++;
934 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
935 ptr = nextArg(ptr);
936 if (ptr) {
937 val = atoi(ptr);
938 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
939 mac->modeLogic = val;
940 validArgumentCount++;
943 ptr = nextArg(ptr);
944 if (ptr) {
945 val = atoi(ptr);
946 const box_t *box = findBoxByPermanentId(val);
947 if (box) {
948 mac->linkedTo = box->boxId;
949 validArgumentCount++;
952 if (validArgumentCount == 4) { // for backwards compatibility
953 mac->modeLogic = MODELOGIC_OR;
954 } else if (validArgumentCount == 5) { // for backwards compatibility
955 mac->linkedTo = 0;
956 } else if (validArgumentCount != 6) {
957 memset(mac, 0, sizeof(modeActivationCondition_t));
959 cliPrintLinef( "aux %u %u %u %u %u %u %u",
961 mac->modeId,
962 mac->auxChannelIndex,
963 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
964 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
965 mac->modeLogic,
966 mac->linkedTo
968 } else {
969 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
974 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
976 const char *format = "serial %d %d %ld %ld %ld %ld";
977 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
978 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
979 continue;
981 bool equalsDefault = false;
982 if (serialConfigDefault) {
983 equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
984 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
985 serialConfigDefault->portConfigs[i].identifier,
986 serialConfigDefault->portConfigs[i].functionMask,
987 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
988 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
989 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
990 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
993 cliDumpPrintLinef(dumpMask, equalsDefault, format,
994 serialConfig->portConfigs[i].identifier,
995 serialConfig->portConfigs[i].functionMask,
996 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
997 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
998 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
999 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
1004 static void cliSerial(char *cmdline)
1006 const char *format = "serial %d %d %ld %ld %ld %ld";
1007 if (isEmpty(cmdline)) {
1008 printSerial(DUMP_MASTER, serialConfig(), NULL);
1009 return;
1011 serialPortConfig_t portConfig;
1012 memset(&portConfig, 0 , sizeof(portConfig));
1014 serialPortConfig_t *currentConfig;
1016 uint8_t validArgumentCount = 0;
1018 const char *ptr = cmdline;
1020 int val = atoi(ptr++);
1021 currentConfig = serialFindPortConfiguration(val);
1022 if (currentConfig) {
1023 portConfig.identifier = val;
1024 validArgumentCount++;
1027 ptr = nextArg(ptr);
1028 if (ptr) {
1029 val = atoi(ptr);
1030 portConfig.functionMask = val & 0xFFFF;
1031 validArgumentCount++;
1034 for (int i = 0; i < 4; i ++) {
1035 ptr = nextArg(ptr);
1036 if (!ptr) {
1037 break;
1040 val = atoi(ptr);
1042 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1043 if (baudRates[baudRateIndex] != (uint32_t) val) {
1044 break;
1047 switch (i) {
1048 case 0:
1049 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
1050 continue;
1052 portConfig.msp_baudrateIndex = baudRateIndex;
1053 break;
1054 case 1:
1055 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
1056 continue;
1058 portConfig.gps_baudrateIndex = baudRateIndex;
1059 break;
1060 case 2:
1061 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
1062 continue;
1064 portConfig.telemetry_baudrateIndex = baudRateIndex;
1065 break;
1066 case 3:
1067 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
1068 continue;
1070 portConfig.blackbox_baudrateIndex = baudRateIndex;
1071 break;
1074 validArgumentCount++;
1077 if (validArgumentCount < 6) {
1078 cliShowParseError();
1079 return;
1082 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1084 cliDumpPrintLinef(0, false, format,
1085 portConfig.identifier,
1086 portConfig.functionMask,
1087 baudRates[portConfig.msp_baudrateIndex],
1088 baudRates[portConfig.gps_baudrateIndex],
1089 baudRates[portConfig.telemetry_baudrateIndex],
1090 baudRates[portConfig.blackbox_baudrateIndex]
1095 #ifndef SKIP_SERIAL_PASSTHROUGH
1096 #ifdef USE_PINIO
1097 static void cbCtrlLine(void *context, uint16_t ctrl)
1099 int pinioDtr = (int)(long)context;
1101 pinioSet(pinioDtr, !(ctrl & CTRL_LINE_STATE_DTR));
1103 #endif /* USE_PINIO */
1105 static void cliSerialPassthrough(char *cmdline)
1107 if (isEmpty(cmdline)) {
1108 cliShowParseError();
1109 return;
1112 int id = -1;
1113 uint32_t baud = 0;
1114 bool enableBaudCb = false;
1115 #ifdef USE_PINIO
1116 int pinioDtr = 0;
1117 #endif /* USE_PINIO */
1118 unsigned mode = 0;
1119 char *saveptr;
1120 char* tok = strtok_r(cmdline, " ", &saveptr);
1121 int index = 0;
1123 while (tok != NULL) {
1124 switch (index) {
1125 case 0:
1126 id = atoi(tok);
1127 break;
1128 case 1:
1129 baud = atoi(tok);
1130 break;
1131 case 2:
1132 if (strstr(tok, "rx") || strstr(tok, "RX"))
1133 mode |= MODE_RX;
1134 if (strstr(tok, "tx") || strstr(tok, "TX"))
1135 mode |= MODE_TX;
1136 break;
1137 #ifdef USE_PINIO
1138 case 3:
1139 pinioDtr = atoi(tok);
1140 break;
1141 #endif /* USE_PINIO */
1143 index++;
1144 tok = strtok_r(NULL, " ", &saveptr);
1147 if (baud == 0) {
1148 enableBaudCb = true;
1151 cliPrintf("Port %d ", id);
1152 serialPort_t *passThroughPort;
1153 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
1154 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
1155 if (enableBaudCb) {
1156 // Set default baud
1157 baud = 57600;
1160 if (!mode) {
1161 mode = MODE_RXTX;
1164 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
1165 baud, mode,
1166 SERIAL_NOT_INVERTED);
1167 if (!passThroughPort) {
1168 cliPrintLine("could not be opened.");
1169 return;
1172 if (enableBaudCb) {
1173 cliPrintf("opened, default baud = %d.\r\n", baud);
1174 } else {
1175 cliPrintf("opened, baud = %d.\r\n", baud);
1177 } else {
1178 passThroughPort = passThroughPortUsage->serialPort;
1179 // If the user supplied a mode, override the port's mode, otherwise
1180 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1181 // Set the baud rate if specified
1182 if (baud) {
1183 cliPrintf("already open, setting baud = %d.\n\r", baud);
1184 serialSetBaudRate(passThroughPort, baud);
1185 } else {
1186 cliPrintf("already open, baud = %d.\n\r", passThroughPort->baudRate);
1189 if (mode && passThroughPort->mode != mode) {
1190 cliPrintf("Mode changed from %d to %d.\r\n",
1191 passThroughPort->mode, mode);
1192 serialSetMode(passThroughPort, mode);
1195 // If this port has a rx callback associated we need to remove it now.
1196 // Otherwise no data will be pushed in the serial port buffer!
1197 if (passThroughPort->rxCallback) {
1198 passThroughPort->rxCallback = 0;
1202 // If no baud rate is specified allow to be set via USB
1203 if (enableBaudCb) {
1204 cliPrintLine("Baud rate change over USB enabled.");
1205 // Register the right side baud rate setting routine with the left side which allows setting of the UART
1206 // baud rate over USB without setting it using the serialpassthrough command
1207 serialSetBaudRateCb(cliPort, serialSetBaudRate, passThroughPort);
1210 cliPrintLine("Forwarding, power cycle to exit.");
1212 #ifdef USE_PINIO
1213 // Register control line state callback
1214 if (pinioDtr) {
1215 serialSetCtrlLineStateCb(cliPort, cbCtrlLine, (void *)(intptr_t)(pinioDtr - 1));
1217 #endif /* USE_PINIO */
1219 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
1221 #endif
1223 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
1225 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1226 // print out adjustment ranges channel settings
1227 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1228 const adjustmentRange_t *ar = &adjustmentRanges[i];
1229 bool equalsDefault = false;
1230 if (defaultAdjustmentRanges) {
1231 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1232 equalsDefault = !memcmp(ar, arDefault, sizeof(*ar));
1233 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1235 arDefault->adjustmentIndex,
1236 arDefault->auxChannelIndex,
1237 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1238 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1239 arDefault->adjustmentFunction,
1240 arDefault->auxSwitchChannelIndex,
1241 arDefault->adjustmentCenter,
1242 arDefault->adjustmentScale
1245 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1247 ar->adjustmentIndex,
1248 ar->auxChannelIndex,
1249 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1250 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1251 ar->adjustmentFunction,
1252 ar->auxSwitchChannelIndex,
1253 ar->adjustmentCenter,
1254 ar->adjustmentScale
1259 static void cliAdjustmentRange(char *cmdline)
1261 const char *format = "adjrange %u %u %u %u %u %u %u %u %u";
1262 int i, val = 0;
1263 const char *ptr;
1265 if (isEmpty(cmdline)) {
1266 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
1267 } else {
1268 ptr = cmdline;
1269 i = atoi(ptr++);
1270 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1271 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1272 uint8_t validArgumentCount = 0;
1274 ptr = nextArg(ptr);
1275 if (ptr) {
1276 val = atoi(ptr);
1277 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1278 ar->adjustmentIndex = val;
1279 validArgumentCount++;
1282 ptr = nextArg(ptr);
1283 if (ptr) {
1284 val = atoi(ptr);
1285 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1286 ar->auxChannelIndex = val;
1287 validArgumentCount++;
1291 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1293 ptr = nextArg(ptr);
1294 if (ptr) {
1295 val = atoi(ptr);
1296 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1297 ar->adjustmentFunction = val;
1298 validArgumentCount++;
1301 ptr = nextArg(ptr);
1302 if (ptr) {
1303 val = atoi(ptr);
1304 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1305 ar->auxSwitchChannelIndex = val;
1306 validArgumentCount++;
1310 if (validArgumentCount != 6) {
1311 memset(ar, 0, sizeof(adjustmentRange_t));
1312 cliShowParseError();
1313 return;
1316 // Optional arguments
1317 ar->adjustmentCenter = 0;
1318 ar->adjustmentScale = 0;
1320 ptr = nextArg(ptr);
1321 if (ptr) {
1322 val = atoi(ptr);
1323 ar->adjustmentCenter = val;
1324 validArgumentCount++;
1326 ptr = nextArg(ptr);
1327 if (ptr) {
1328 val = atoi(ptr);
1329 ar->adjustmentScale = val;
1330 validArgumentCount++;
1332 cliDumpPrintLinef(0, false, format,
1334 ar->adjustmentIndex,
1335 ar->auxChannelIndex,
1336 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1337 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1338 ar->adjustmentFunction,
1339 ar->auxSwitchChannelIndex,
1340 ar->adjustmentCenter,
1341 ar->adjustmentScale
1344 } else {
1345 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1350 #ifndef USE_QUAD_MIXER_ONLY
1351 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer)
1353 const char *format = "mmix %d %s %s %s %s";
1354 char buf0[FTOA_BUFFER_LENGTH];
1355 char buf1[FTOA_BUFFER_LENGTH];
1356 char buf2[FTOA_BUFFER_LENGTH];
1357 char buf3[FTOA_BUFFER_LENGTH];
1358 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1359 if (customMotorMixer[i].throttle == 0.0f)
1360 break;
1361 const float thr = customMotorMixer[i].throttle;
1362 const float roll = customMotorMixer[i].roll;
1363 const float pitch = customMotorMixer[i].pitch;
1364 const float yaw = customMotorMixer[i].yaw;
1365 bool equalsDefault = false;
1366 if (defaultCustomMotorMixer) {
1367 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1368 const float rollDefault = defaultCustomMotorMixer[i].roll;
1369 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1370 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1371 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1373 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1375 ftoa(thrDefault, buf0),
1376 ftoa(rollDefault, buf1),
1377 ftoa(pitchDefault, buf2),
1378 ftoa(yawDefault, buf3));
1380 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1382 ftoa(thr, buf0),
1383 ftoa(roll, buf1),
1384 ftoa(pitch, buf2),
1385 ftoa(yaw, buf3));
1388 #endif // USE_QUAD_MIXER_ONLY
1390 static void cliMotorMix(char *cmdline)
1392 #ifdef USE_QUAD_MIXER_ONLY
1393 UNUSED(cmdline);
1394 #else
1395 int check = 0;
1396 uint8_t len;
1397 const char *ptr;
1399 if (isEmpty(cmdline)) {
1400 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1401 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1402 // erase custom mixer
1403 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1404 customMotorMixerMutable(i)->throttle = 0.0f;
1406 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1407 ptr = nextArg(cmdline);
1408 if (ptr) {
1409 len = strlen(ptr);
1410 for (uint32_t i = 0; ; i++) {
1411 if (mixerNames[i] == NULL) {
1412 cliPrintErrorLinef("Invalid name");
1413 break;
1415 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1416 mixerLoadMix(i, customMotorMixerMutable(0));
1417 cliPrintLinef("Loaded %s", mixerNames[i]);
1418 cliMotorMix("");
1419 break;
1423 } else {
1424 ptr = cmdline;
1425 uint32_t i = atoi(ptr); // get motor number
1426 if (i < MAX_SUPPORTED_MOTORS) {
1427 ptr = nextArg(ptr);
1428 if (ptr) {
1429 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1430 check++;
1432 ptr = nextArg(ptr);
1433 if (ptr) {
1434 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1435 check++;
1437 ptr = nextArg(ptr);
1438 if (ptr) {
1439 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1440 check++;
1442 ptr = nextArg(ptr);
1443 if (ptr) {
1444 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1445 check++;
1447 if (check != 4) {
1448 cliShowParseError();
1449 } else {
1450 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1452 } else {
1453 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1456 #endif
1459 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1461 const char *format = "rxrange %u %u %u";
1462 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1463 bool equalsDefault = false;
1464 if (defaultChannelRangeConfigs) {
1465 equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
1466 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1468 defaultChannelRangeConfigs[i].min,
1469 defaultChannelRangeConfigs[i].max
1472 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1474 channelRangeConfigs[i].min,
1475 channelRangeConfigs[i].max
1480 static void cliRxRange(char *cmdline)
1482 const char *format = "rxrange %u %u %u";
1483 int i, validArgumentCount = 0;
1484 const char *ptr;
1486 if (isEmpty(cmdline)) {
1487 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1488 } else if (strcasecmp(cmdline, "reset") == 0) {
1489 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1490 } else {
1491 ptr = cmdline;
1492 i = atoi(ptr);
1493 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1494 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1496 ptr = nextArg(ptr);
1497 if (ptr) {
1498 rangeMin = atoi(ptr);
1499 validArgumentCount++;
1502 ptr = nextArg(ptr);
1503 if (ptr) {
1504 rangeMax = atoi(ptr);
1505 validArgumentCount++;
1508 if (validArgumentCount != 2) {
1509 cliShowParseError();
1510 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1511 cliShowParseError();
1512 } else {
1513 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1514 channelRangeConfig->min = rangeMin;
1515 channelRangeConfig->max = rangeMax;
1516 cliDumpPrintLinef(0, false, format,
1518 channelRangeConfig->min,
1519 channelRangeConfig->max
1523 } else {
1524 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1529 #ifdef USE_LED_STRIP
1530 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1532 const char *format = "led %u %s";
1533 char ledConfigBuffer[20];
1534 char ledConfigDefaultBuffer[20];
1535 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1536 ledConfig_t ledConfig = ledConfigs[i];
1537 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1538 bool equalsDefault = false;
1539 if (defaultLedConfigs) {
1540 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1541 equalsDefault = ledConfig == ledConfigDefault;
1542 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1543 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1545 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1549 static void cliLed(char *cmdline)
1551 const char *format = "led %u %s";
1552 char ledConfigBuffer[20];
1553 int i;
1554 const char *ptr;
1556 if (isEmpty(cmdline)) {
1557 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1558 } else {
1559 ptr = cmdline;
1560 i = atoi(ptr);
1561 if (i < LED_MAX_STRIP_LENGTH) {
1562 ptr = nextArg(cmdline);
1563 if (parseLedStripConfig(i, ptr)) {
1564 generateLedConfig((ledConfig_t *)&ledStripConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
1565 cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
1566 } else {
1567 cliShowParseError();
1569 } else {
1570 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1575 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1577 const char *format = "color %u %d,%u,%u";
1578 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1579 const hsvColor_t *color = &colors[i];
1580 bool equalsDefault = false;
1581 if (defaultColors) {
1582 const hsvColor_t *colorDefault = &defaultColors[i];
1583 equalsDefault = !memcmp(color, colorDefault, sizeof(*color));
1584 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1586 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1590 static void cliColor(char *cmdline)
1592 const char *format = "color %u %d,%u,%u";
1593 if (isEmpty(cmdline)) {
1594 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1595 } else {
1596 const char *ptr = cmdline;
1597 const int i = atoi(ptr);
1598 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1599 ptr = nextArg(cmdline);
1600 if (parseColor(i, ptr)) {
1601 const hsvColor_t *color = &ledStripConfig()->colors[i];
1602 cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
1603 } else {
1604 cliShowParseError();
1606 } else {
1607 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1612 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1614 const char *format = "mode_color %u %u %u";
1615 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1616 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1617 int colorIndex = ledStripConfig->modeColors[i].color[j];
1618 bool equalsDefault = false;
1619 if (defaultLedStripConfig) {
1620 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1621 equalsDefault = colorIndex == colorIndexDefault;
1622 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1624 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1628 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1629 const int colorIndex = ledStripConfig->specialColors.color[j];
1630 bool equalsDefault = false;
1631 if (defaultLedStripConfig) {
1632 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1633 equalsDefault = colorIndex == colorIndexDefault;
1634 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1636 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1639 const int ledStripAuxChannel = ledStripConfig->ledstrip_aux_channel;
1640 bool equalsDefault = false;
1641 if (defaultLedStripConfig) {
1642 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1643 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1644 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1646 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1649 static void cliModeColor(char *cmdline)
1651 if (isEmpty(cmdline)) {
1652 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1653 } else {
1654 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1655 int args[ARGS_COUNT];
1656 int argNo = 0;
1657 char *saveptr;
1658 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1659 while (ptr && argNo < ARGS_COUNT) {
1660 args[argNo++] = atoi(ptr);
1661 ptr = strtok_r(NULL, " ", &saveptr);
1664 if (ptr != NULL || argNo != ARGS_COUNT) {
1665 cliShowParseError();
1666 return;
1669 int modeIdx = args[MODE];
1670 int funIdx = args[FUNCTION];
1671 int color = args[COLOR];
1672 if (!setModeColor(modeIdx, funIdx, color)) {
1673 cliShowParseError();
1674 return;
1676 // values are validated
1677 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1680 #endif
1682 #ifdef USE_SERVOS
1683 static void printServo(uint8_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams)
1685 // print out servo settings
1686 const char *format = "servo %u %d %d %d %d %d";
1687 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1688 const servoParam_t *servoConf = &servoParams[i];
1689 bool equalsDefault = false;
1690 if (defaultServoParams) {
1691 const servoParam_t *defaultServoConf = &defaultServoParams[i];
1692 equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
1693 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1695 defaultServoConf->min,
1696 defaultServoConf->max,
1697 defaultServoConf->middle,
1698 defaultServoConf->rate,
1699 defaultServoConf->forwardFromChannel
1702 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1704 servoConf->min,
1705 servoConf->max,
1706 servoConf->middle,
1707 servoConf->rate,
1708 servoConf->forwardFromChannel
1711 // print servo directions
1712 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1713 const char *format = "smix reverse %d %d r";
1714 const servoParam_t *servoConf = &servoParams[i];
1715 const servoParam_t *servoConfDefault = &defaultServoParams[i];
1716 if (defaultServoParams) {
1717 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
1718 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1719 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
1720 if (servoConfDefault->reversedSources & (1 << channel)) {
1721 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
1723 if (servoConf->reversedSources & (1 << channel)) {
1724 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
1727 } else {
1728 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1729 if (servoConf->reversedSources & (1 << channel)) {
1730 cliDumpPrintLinef(dumpMask, true, format, i , channel);
1737 static void cliServo(char *cmdline)
1739 const char *format = "servo %u %d %d %d %d %d";
1740 enum { SERVO_ARGUMENT_COUNT = 6 };
1741 int16_t arguments[SERVO_ARGUMENT_COUNT];
1743 servoParam_t *servo;
1745 int i;
1746 char *ptr;
1748 if (isEmpty(cmdline)) {
1749 printServo(DUMP_MASTER, servoParams(0), NULL);
1750 } else {
1751 int validArgumentCount = 0;
1753 ptr = cmdline;
1755 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1757 // If command line doesn't fit the format, don't modify the config
1758 while (*ptr) {
1759 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1760 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1761 cliShowParseError();
1762 return;
1765 arguments[validArgumentCount++] = atoi(ptr);
1767 do {
1768 ptr++;
1769 } while (*ptr >= '0' && *ptr <= '9');
1770 } else if (*ptr == ' ') {
1771 ptr++;
1772 } else {
1773 cliShowParseError();
1774 return;
1778 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
1780 i = arguments[INDEX];
1782 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1783 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1784 cliShowParseError();
1785 return;
1788 servo = servoParamsMutable(i);
1790 if (
1791 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
1792 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
1793 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1794 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1795 arguments[RATE] < -100 || arguments[RATE] > 100 ||
1796 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1798 cliShowParseError();
1799 return;
1802 servo->min = arguments[MIN];
1803 servo->max = arguments[MAX];
1804 servo->middle = arguments[MIDDLE];
1805 servo->rate = arguments[RATE];
1806 servo->forwardFromChannel = arguments[FORWARD];
1808 cliDumpPrintLinef(0, false, format,
1810 servo->min,
1811 servo->max,
1812 servo->middle,
1813 servo->rate,
1814 servo->forwardFromChannel
1819 #endif
1821 #ifdef USE_SERVOS
1822 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1824 const char *format = "smix %d %d %d %d %d %d %d %d";
1825 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1826 const servoMixer_t customServoMixer = customServoMixers[i];
1827 if (customServoMixer.rate == 0) {
1828 break;
1831 bool equalsDefault = false;
1832 if (defaultCustomServoMixers) {
1833 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1834 equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
1836 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1838 customServoMixerDefault.targetChannel,
1839 customServoMixerDefault.inputSource,
1840 customServoMixerDefault.rate,
1841 customServoMixerDefault.speed,
1842 customServoMixerDefault.min,
1843 customServoMixerDefault.max,
1844 customServoMixerDefault.box
1847 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1849 customServoMixer.targetChannel,
1850 customServoMixer.inputSource,
1851 customServoMixer.rate,
1852 customServoMixer.speed,
1853 customServoMixer.min,
1854 customServoMixer.max,
1855 customServoMixer.box
1859 cliPrintLinefeed();
1862 static void cliServoMix(char *cmdline)
1864 int args[8], check = 0;
1865 int len = strlen(cmdline);
1867 if (len == 0) {
1868 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
1869 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1870 // erase custom mixer
1871 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1872 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1873 servoParamsMutable(i)->reversedSources = 0;
1875 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1876 const char *ptr = nextArg(cmdline);
1877 if (ptr) {
1878 len = strlen(ptr);
1879 for (uint32_t i = 0; ; i++) {
1880 if (mixerNames[i] == NULL) {
1881 cliPrintErrorLinef("Invalid name");
1882 break;
1884 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1885 servoMixerLoadMix(i);
1886 cliPrintLinef("Loaded %s", mixerNames[i]);
1887 cliServoMix("");
1888 break;
1892 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
1893 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
1894 char *ptr = strchr(cmdline, ' ');
1896 if (ptr == NULL) {
1897 cliPrintf("s");
1898 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1899 cliPrintf("\ti%d", inputSource);
1900 cliPrintLinefeed();
1902 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
1903 cliPrintf("%d", servoIndex);
1904 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++) {
1905 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
1907 cliPrintLinefeed();
1909 return;
1912 char *saveptr;
1913 ptr = strtok_r(ptr, " ", &saveptr);
1914 while (ptr != NULL && check < ARGS_COUNT - 1) {
1915 args[check++] = atoi(ptr);
1916 ptr = strtok_r(NULL, " ", &saveptr);
1919 if (ptr == NULL || check != ARGS_COUNT - 1) {
1920 cliShowParseError();
1921 return;
1924 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
1925 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
1926 && (*ptr == 'r' || *ptr == 'n')) {
1927 if (*ptr == 'r') {
1928 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
1929 } else {
1930 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
1932 } else {
1933 cliShowParseError();
1934 return;
1937 cliServoMix("reverse");
1938 } else {
1939 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
1940 char *saveptr;
1941 char *ptr = strtok_r(cmdline, " ", &saveptr);
1942 while (ptr != NULL && check < ARGS_COUNT) {
1943 args[check++] = atoi(ptr);
1944 ptr = strtok_r(NULL, " ", &saveptr);
1947 if (ptr != NULL || check != ARGS_COUNT) {
1948 cliShowParseError();
1949 return;
1952 int32_t i = args[RULE];
1953 if (i >= 0 && i < MAX_SERVO_RULES &&
1954 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1955 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1956 args[RATE] >= -100 && args[RATE] <= 100 &&
1957 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1958 args[MIN] >= 0 && args[MIN] <= 100 &&
1959 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
1960 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
1961 customServoMixersMutable(i)->targetChannel = args[TARGET];
1962 customServoMixersMutable(i)->inputSource = args[INPUT];
1963 customServoMixersMutable(i)->rate = args[RATE];
1964 customServoMixersMutable(i)->speed = args[SPEED];
1965 customServoMixersMutable(i)->min = args[MIN];
1966 customServoMixersMutable(i)->max = args[MAX];
1967 customServoMixersMutable(i)->box = args[BOX];
1968 cliServoMix("");
1969 } else {
1970 cliShowParseError();
1974 #endif
1976 #ifdef USE_SDCARD
1978 static void cliWriteBytes(const uint8_t *buffer, int count)
1980 while (count > 0) {
1981 cliWrite(*buffer);
1982 buffer++;
1983 count--;
1987 static void cliSdInfo(char *cmdline)
1989 UNUSED(cmdline);
1991 cliPrint("SD card: ");
1993 if (!sdcard_isInserted()) {
1994 cliPrintLine("None inserted");
1995 return;
1998 if (!sdcard_isInitialized()) {
1999 cliPrintLine("Startup failed");
2000 return;
2003 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2005 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2006 metadata->manufacturerID,
2007 metadata->numBlocks / 2, /* One block is half a kB */
2008 metadata->productionMonth,
2009 metadata->productionYear,
2010 metadata->productRevisionMajor,
2011 metadata->productRevisionMinor
2014 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2016 cliPrint("'\r\n" "Filesystem: ");
2018 switch (afatfs_getFilesystemState()) {
2019 case AFATFS_FILESYSTEM_STATE_READY:
2020 cliPrint("Ready");
2021 break;
2022 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2023 cliPrint("Initializing");
2024 break;
2025 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2026 case AFATFS_FILESYSTEM_STATE_FATAL:
2027 cliPrint("Fatal");
2029 switch (afatfs_getLastError()) {
2030 case AFATFS_ERROR_BAD_MBR:
2031 cliPrint(" - no FAT MBR partitions");
2032 break;
2033 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2034 cliPrint(" - bad FAT header");
2035 break;
2036 case AFATFS_ERROR_GENERIC:
2037 case AFATFS_ERROR_NONE:
2038 ; // Nothing more detailed to print
2039 break;
2041 break;
2043 cliPrintLinefeed();
2046 #endif
2048 #ifdef USE_FLASHFS
2050 static void cliFlashInfo(char *cmdline)
2052 const flashGeometry_t *layout = flashfsGetGeometry();
2054 UNUSED(cmdline);
2056 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
2057 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
2061 static void cliFlashErase(char *cmdline)
2063 UNUSED(cmdline);
2065 if (!flashfsIsSupported()) {
2066 return;
2069 #ifndef MINIMAL_CLI
2070 uint32_t i = 0;
2071 cliPrintLine("Erasing, please wait ... ");
2072 #else
2073 cliPrintLine("Erasing,");
2074 #endif
2076 bufWriterFlush(cliWriter);
2077 flashfsEraseCompletely();
2079 while (!flashfsIsReady()) {
2080 #ifndef MINIMAL_CLI
2081 cliPrintf(".");
2082 if (i++ > 120) {
2083 i=0;
2084 cliPrintLinefeed();
2087 bufWriterFlush(cliWriter);
2088 #endif
2089 delay(100);
2091 beeper(BEEPER_BLACKBOX_ERASE);
2092 cliPrintLinefeed();
2093 cliPrintLine("Done.");
2096 #ifdef USE_FLASH_TOOLS
2098 static void cliFlashWrite(char *cmdline)
2100 const uint32_t address = atoi(cmdline);
2101 const char *text = strchr(cmdline, ' ');
2103 if (!text) {
2104 cliShowParseError();
2105 } else {
2106 flashfsSeekAbs(address);
2107 flashfsWrite((uint8_t*)text, strlen(text), true);
2108 flashfsFlushSync();
2110 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2114 static void cliFlashRead(char *cmdline)
2116 uint32_t address = atoi(cmdline);
2118 const char *nextArg = strchr(cmdline, ' ');
2120 if (!nextArg) {
2121 cliShowParseError();
2122 } else {
2123 uint32_t length = atoi(nextArg);
2125 cliPrintLinef("Reading %u bytes at %u:", length, address);
2127 uint8_t buffer[32];
2128 while (length > 0) {
2129 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2131 for (int i = 0; i < bytesRead; i++) {
2132 cliWrite(buffer[i]);
2135 length -= bytesRead;
2136 address += bytesRead;
2138 if (bytesRead == 0) {
2139 //Assume we reached the end of the volume or something fatal happened
2140 break;
2143 cliPrintLinefeed();
2147 #endif
2148 #endif
2150 #ifdef USE_VTX_CONTROL
2151 static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault)
2153 // print out vtx channel settings
2154 const char *format = "vtx %u %u %u %u %u %u";
2155 bool equalsDefault = false;
2156 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
2157 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
2158 if (vtxConfigDefault) {
2159 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
2160 equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
2161 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2163 cacDefault->auxChannelIndex,
2164 cacDefault->band,
2165 cacDefault->channel,
2166 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
2167 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
2170 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2172 cac->auxChannelIndex,
2173 cac->band,
2174 cac->channel,
2175 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2176 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2181 static void cliVtx(char *cmdline)
2183 const char *format = "vtx %u %u %u %u %u %u";
2184 int i, val = 0;
2185 const char *ptr;
2187 if (isEmpty(cmdline)) {
2188 printVtx(DUMP_MASTER, vtxConfig(), NULL);
2189 } else {
2190 ptr = cmdline;
2191 i = atoi(ptr++);
2192 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
2193 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
2194 uint8_t validArgumentCount = 0;
2195 ptr = nextArg(ptr);
2196 if (ptr) {
2197 val = atoi(ptr);
2198 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
2199 cac->auxChannelIndex = val;
2200 validArgumentCount++;
2203 ptr = nextArg(ptr);
2204 if (ptr) {
2205 val = atoi(ptr);
2206 // FIXME Use VTX API to get min/max
2207 if (val >= VTX_SETTINGS_MIN_BAND && val <= VTX_SETTINGS_MAX_BAND) {
2208 cac->band = val;
2209 validArgumentCount++;
2212 ptr = nextArg(ptr);
2213 if (ptr) {
2214 val = atoi(ptr);
2215 // FIXME Use VTX API to get min/max
2216 if (val >= VTX_SETTINGS_MIN_CHANNEL && val <= VTX_SETTINGS_MAX_CHANNEL) {
2217 cac->channel = val;
2218 validArgumentCount++;
2221 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
2223 if (validArgumentCount != 5) {
2224 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
2225 cliShowParseError();
2226 } else {
2227 cliDumpPrintLinef(0, false, format,
2229 cac->auxChannelIndex,
2230 cac->band,
2231 cac->channel,
2232 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
2233 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
2236 } else {
2237 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
2242 #endif // VTX_CONTROL
2244 static void printName(uint8_t dumpMask, const pilotConfig_t *pilotConfig)
2246 const bool equalsDefault = strlen(pilotConfig->name) == 0;
2247 cliDumpPrintLinef(dumpMask, equalsDefault, "name %s", equalsDefault ? emptyName : pilotConfig->name);
2250 static void cliName(char *cmdline)
2252 const unsigned int len = strlen(cmdline);
2253 if (len > 0) {
2254 memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
2255 if (strncmp(cmdline, emptyName, len)) {
2256 strncpy(pilotConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
2259 printName(DUMP_MASTER, pilotConfig());
2262 #if defined(USE_BOARD_INFO)
2264 #define ERROR_MESSAGE "%s cannot be changed. Current value: '%s'"
2266 static void cliBoardName(char *cmdline)
2268 const unsigned int len = strlen(cmdline);
2269 if (len > 0 && boardInformationIsSet() && (len != strlen(getBoardName()) || strncmp(getBoardName(), cmdline, len))) {
2270 cliPrintErrorLinef(ERROR_MESSAGE, "board_name", getBoardName());
2271 } else {
2272 if (len > 0) {
2273 setBoardName(cmdline);
2274 boardInformationUpdated = true;
2276 cliPrintLinef("board_name %s", getBoardName());
2280 static void cliManufacturerId(char *cmdline)
2282 const unsigned int len = strlen(cmdline);
2283 if (len > 0 && boardInformationIsSet() && (len != strlen(getManufacturerId()) || strncmp(getManufacturerId(), cmdline, len))) {
2284 cliPrintErrorLinef(ERROR_MESSAGE, "manufacturer_id", getManufacturerId());
2285 } else {
2286 if (len > 0) {
2287 setManufacturerId(cmdline);
2288 boardInformationUpdated = true;
2290 cliPrintLinef("manufacturer_id %s", getManufacturerId());
2294 #if defined(USE_SIGNATURE)
2295 static void writeSignature(char *signatureStr, uint8_t *signature)
2297 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
2298 tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
2302 static void cliSignature(char *cmdline)
2304 const int len = strlen(cmdline);
2306 uint8_t signature[SIGNATURE_LENGTH] = {0};
2307 if (len > 0) {
2308 if (len != 2 * SIGNATURE_LENGTH) {
2309 cliPrintErrorLinef("Invalid length: %d (expected: %d)", len, 2 * SIGNATURE_LENGTH);
2311 return;
2314 #define BLOCK_SIZE 2
2315 for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
2316 char temp[BLOCK_SIZE + 1];
2317 strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
2318 temp[BLOCK_SIZE] = '\0';
2319 char *end;
2320 unsigned result = strtoul(temp, &end, 16);
2321 if (end == &temp[BLOCK_SIZE]) {
2322 signature[i] = result;
2323 } else {
2324 cliPrintErrorLinef("Invalid character found: %c", end[0]);
2326 return;
2329 #undef BLOCK_SIZE
2332 char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
2333 if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
2334 writeSignature(signatureStr, getSignature());
2335 cliPrintErrorLinef(ERROR_MESSAGE, "signature", signatureStr);
2336 } else {
2337 if (len > 0) {
2338 setSignature(signature);
2340 signatureUpdated = true;
2342 writeSignature(signatureStr, getSignature());
2343 } else if (signatureUpdated || signatureIsSet()) {
2344 writeSignature(signatureStr, getSignature());
2347 cliPrintLinef("signature %s", signatureStr);
2350 #endif
2352 #undef ERROR_MESSAGE
2354 #endif // USE_BOARD_INFO
2356 static void cliMcuId(char *cmdline)
2358 UNUSED(cmdline);
2360 cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
2363 static uint32_t getFeatureMask(const uint32_t featureMask)
2365 if (featureMaskIsCopied) {
2366 return featureMaskCopy;
2367 } else {
2368 return featureMask;
2372 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2374 const uint32_t mask = getFeatureMask(featureConfig->enabledFeatures);
2375 const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2376 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
2377 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
2378 const char *format = "feature -%s";
2379 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2380 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2383 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
2384 if (strcmp(featureNames[i], emptyString) != 0) { //Skip unused
2385 const char *format = "feature %s";
2386 if (defaultMask & (1 << i)) {
2387 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2389 if (mask & (1 << i)) {
2390 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2396 static void cliFeature(char *cmdline)
2398 uint32_t len = strlen(cmdline);
2399 const uint32_t mask = getFeatureMask(featureMask());
2400 if (len == 0) {
2401 cliPrint("Enabled: ");
2402 for (uint32_t i = 0; ; i++) {
2403 if (featureNames[i] == NULL) {
2404 break;
2406 if (mask & (1 << i)) {
2407 cliPrintf("%s ", featureNames[i]);
2410 cliPrintLinefeed();
2411 } else if (strncasecmp(cmdline, "list", len) == 0) {
2412 cliPrint("Available:");
2413 for (uint32_t i = 0; ; i++) {
2414 if (featureNames[i] == NULL)
2415 break;
2416 if (strcmp(featureNames[i], emptyString) != 0) //Skip unused
2417 cliPrintf(" %s", featureNames[i]);
2419 cliPrintLinefeed();
2420 return;
2421 } else {
2422 if (!featureMaskIsCopied) {
2423 featureMaskCopy = featureMask();
2424 featureMaskIsCopied = true;
2426 uint32_t feature;
2428 bool remove = false;
2429 if (cmdline[0] == '-') {
2430 // remove feature
2431 remove = true;
2432 cmdline++; // skip over -
2433 len--;
2436 for (uint32_t i = 0; ; i++) {
2437 if (featureNames[i] == NULL) {
2438 cliPrintErrorLinef("Invalid name");
2439 break;
2442 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
2443 feature = 1 << i;
2444 #ifndef USE_GPS
2445 if (feature & FEATURE_GPS) {
2446 cliPrintLine("unavailable");
2447 break;
2449 #endif
2450 #ifndef USE_RANGEFINDER
2451 if (feature & FEATURE_RANGEFINDER) {
2452 cliPrintLine("unavailable");
2453 break;
2455 #endif
2456 if (remove) {
2457 featureClear(feature, &featureMaskCopy);
2458 cliPrint("Disabled");
2459 } else {
2460 featureSet(feature, &featureMaskCopy);
2461 cliPrint("Enabled");
2463 cliPrintLinef(" %s", featureNames[i]);
2464 break;
2470 #if defined(USE_BEEPER)
2471 static void printBeeper(uint8_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name)
2473 const uint8_t beeperCount = beeperTableEntryCount();
2474 for (int32_t i = 0; i < beeperCount - 1; i++) {
2475 const char *formatOff = "%s -%s";
2476 const char *formatOn = "%s %s";
2477 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
2478 cliDefaultPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
2479 cliDumpPrintLinef(dumpMask, ~(offFlags ^ offFlagsDefault) & beeperModeMask, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
2483 static void processBeeperCommand(char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
2485 uint32_t len = strlen(cmdline);
2486 uint8_t beeperCount = beeperTableEntryCount();
2488 if (len == 0) {
2489 cliPrintf("Disabled:");
2490 for (int32_t i = 0; ; i++) {
2491 if (i == beeperCount - 1) {
2492 if (*offFlags == 0)
2493 cliPrint(" none");
2494 break;
2497 if (beeperModeMaskForTableIndex(i) & *offFlags)
2498 cliPrintf(" %s", beeperNameForTableIndex(i));
2500 cliPrintLinefeed();
2501 } else if (strncasecmp(cmdline, "list", len) == 0) {
2502 cliPrint("Available:");
2503 for (uint32_t i = 0; i < beeperCount; i++) {
2504 if (beeperModeMaskForTableIndex(i) & allowedFlags) {
2505 cliPrintf(" %s", beeperNameForTableIndex(i));
2508 cliPrintLinefeed();
2509 } else {
2510 bool remove = false;
2511 if (cmdline[0] == '-') {
2512 remove = true; // this is for beeper OFF condition
2513 cmdline++;
2514 len--;
2517 for (uint32_t i = 0; ; i++) {
2518 if (i == beeperCount) {
2519 cliPrintErrorLinef("Invalid name");
2520 break;
2522 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
2523 if (remove) { // beeper off
2524 if (i == BEEPER_ALL - 1) {
2525 *offFlags = allowedFlags;
2526 } else {
2527 *offFlags |= beeperModeMaskForTableIndex(i);
2529 cliPrint("Disabled");
2531 else { // beeper on
2532 if (i == BEEPER_ALL - 1) {
2533 *offFlags = 0;
2534 } else {
2535 *offFlags &= ~beeperModeMaskForTableIndex(i);
2537 cliPrint("Enabled");
2539 cliPrintLinef(" %s", beeperNameForTableIndex(i));
2540 break;
2546 #if defined(USE_DSHOT)
2547 static void cliBeacon(char *cmdline)
2549 processBeeperCommand(cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
2551 #endif
2553 static void cliBeeper(char *cmdline)
2555 processBeeperCommand(cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
2557 #endif
2559 #ifdef USE_RX_FRSKY_SPI
2560 void cliFrSkyBind(char *cmdline){
2561 UNUSED(cmdline);
2562 switch (rxSpiConfig()->rx_spi_protocol) {
2563 case RX_SPI_FRSKY_D:
2564 case RX_SPI_FRSKY_X:
2565 frSkySpiBind();
2567 cliPrint("Binding...");
2569 break;
2570 default:
2571 cliPrint("Not supported.");
2573 break;
2576 #endif
2578 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
2580 bool equalsDefault = true;
2581 char buf[16];
2582 char bufDefault[16];
2583 uint32_t i;
2584 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2585 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
2586 if (defaultRxConfig) {
2587 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
2588 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
2591 buf[i] = '\0';
2593 const char *formatMap = "map %s";
2594 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
2595 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
2599 static void cliMap(char *cmdline)
2601 uint32_t i;
2602 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
2604 uint32_t len = strlen(cmdline);
2605 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
2607 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2608 buf[i] = toupper((unsigned char)cmdline[i]);
2610 buf[i] = '\0';
2612 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2613 buf[i] = toupper((unsigned char)cmdline[i]);
2615 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
2616 continue;
2618 cliShowParseError();
2619 return;
2621 parseRcChannels(buf, rxConfigMutable());
2622 } else if (len > 0) {
2623 cliShowParseError();
2624 return;
2627 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2628 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
2631 buf[i] = '\0';
2632 cliPrintLinef("map %s", buf);
2635 static char *skipSpace(char *buffer)
2637 while (*(buffer) == ' ') {
2638 buffer++;
2641 return buffer;
2644 static char *checkCommand(char *cmdLine, const char *command)
2646 if (!strncasecmp(cmdLine, command, strlen(command)) // command names match
2647 && (isspace((unsigned)cmdLine[strlen(command)]) || cmdLine[strlen(command)] == 0)) {
2648 return skipSpace(cmdLine + strlen(command) + 1);
2649 } else {
2650 return 0;
2654 static void cliRebootEx(bool bootLoader)
2656 cliPrint("\r\nRebooting");
2657 bufWriterFlush(cliWriter);
2658 waitForSerialPortToFinishTransmitting(cliPort);
2659 stopPwmAllMotors();
2660 if (bootLoader) {
2661 systemResetToBootloader();
2662 return;
2664 systemReset();
2667 static void cliReboot(void)
2669 cliRebootEx(false);
2672 static void cliBootloader(char *cmdLine)
2674 UNUSED(cmdLine);
2676 cliPrintHashLine("restarting in bootloader mode");
2677 cliRebootEx(true);
2680 static void cliExit(char *cmdline)
2682 UNUSED(cmdline);
2684 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2685 bufWriterFlush(cliWriter);
2687 *cliBuffer = '\0';
2688 bufferIndex = 0;
2689 cliMode = 0;
2690 // incase a motor was left running during motortest, clear it here
2691 mixerResetDisarmedMotors();
2692 cliReboot();
2694 cliWriter = NULL;
2697 #ifdef USE_GPS
2698 static void cliGpsPassthrough(char *cmdline)
2700 UNUSED(cmdline);
2702 gpsEnablePassthrough(cliPort);
2704 #endif
2706 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
2707 static void cliPrintGyroRegisters(uint8_t whichSensor)
2709 cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
2710 cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
2711 cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
2714 static void cliDumpGyroRegisters(char *cmdline)
2716 #ifdef USE_DUAL_GYRO
2717 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_1) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
2718 cliPrintLinef("\r\n# Gyro 1");
2719 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
2721 if ((gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_2) || (gyroConfig()->gyro_to_use == GYRO_CONFIG_USE_GYRO_BOTH)) {
2722 cliPrintLinef("\r\n# Gyro 2");
2723 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_2);
2725 #else
2726 cliPrintGyroRegisters(GYRO_CONFIG_USE_GYRO_1);
2727 #endif // USE_DUAL_GYRO
2728 UNUSED(cmdline);
2730 #endif
2733 static int parseOutputIndex(char *pch, bool allowAllEscs) {
2734 int outputIndex = atoi(pch);
2735 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
2736 cliPrintLinef("Using output %d.", outputIndex);
2737 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
2738 cliPrintLinef("Using all outputs.");
2739 } else {
2740 cliPrintErrorLinef("Invalid output number. Range: 0 %d.", getMotorCount() - 1);
2742 return -1;
2745 return outputIndex;
2748 #if defined(USE_DSHOT)
2749 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2751 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
2752 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
2753 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
2755 enum {
2756 ESC_INFO_KISS_V1,
2757 ESC_INFO_KISS_V2,
2758 ESC_INFO_BLHELI32
2761 #define ESC_INFO_VERSION_POSITION 12
2763 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
2765 bool escInfoReceived = false;
2766 if (bytesRead > ESC_INFO_VERSION_POSITION) {
2767 uint8_t escInfoVersion;
2768 uint8_t frameLength;
2769 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
2770 escInfoVersion = ESC_INFO_BLHELI32;
2771 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
2772 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
2773 escInfoVersion = ESC_INFO_KISS_V2;
2774 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
2775 } else {
2776 escInfoVersion = ESC_INFO_KISS_V1;
2777 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
2780 if (bytesRead == frameLength) {
2781 escInfoReceived = true;
2783 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
2784 uint8_t firmwareVersion = 0;
2785 uint8_t firmwareSubVersion = 0;
2786 uint8_t escType = 0;
2787 switch (escInfoVersion) {
2788 case ESC_INFO_KISS_V1:
2789 firmwareVersion = escInfoBuffer[12];
2790 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
2791 escType = (escInfoBuffer[13] & 0xe0) >> 5;
2793 break;
2794 case ESC_INFO_KISS_V2:
2795 firmwareVersion = escInfoBuffer[13];
2796 firmwareSubVersion = escInfoBuffer[14];
2797 escType = escInfoBuffer[15];
2799 break;
2800 case ESC_INFO_BLHELI32:
2801 firmwareVersion = escInfoBuffer[13];
2802 firmwareSubVersion = escInfoBuffer[14];
2803 escType = escInfoBuffer[15];
2805 break;
2808 cliPrint("ESC Type: ");
2809 switch (escInfoVersion) {
2810 case ESC_INFO_KISS_V1:
2811 case ESC_INFO_KISS_V2:
2812 switch (escType) {
2813 case 1:
2814 cliPrintLine("KISS8A");
2816 break;
2817 case 2:
2818 cliPrintLine("KISS16A");
2820 break;
2821 case 3:
2822 cliPrintLine("KISS24A");
2824 break;
2825 case 5:
2826 cliPrintLine("KISS Ultralite");
2828 break;
2829 default:
2830 cliPrintLine("unknown");
2832 break;
2835 break;
2836 case ESC_INFO_BLHELI32:
2838 char *escType = (char *)(escInfoBuffer + 31);
2839 escType[32] = 0;
2840 cliPrintLine(escType);
2843 break;
2846 cliPrint("MCU Serial No: 0x");
2847 for (int i = 0; i < 12; i++) {
2848 if (i && (i % 3 == 0)) {
2849 cliPrint("-");
2851 cliPrintf("%02x", escInfoBuffer[i]);
2853 cliPrintLinefeed();
2855 switch (escInfoVersion) {
2856 case ESC_INFO_KISS_V1:
2857 case ESC_INFO_KISS_V2:
2858 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
2860 break;
2861 case ESC_INFO_BLHELI32:
2862 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
2864 break;
2866 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
2867 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
2868 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
2869 if (escInfoVersion == ESC_INFO_BLHELI32) {
2870 uint8_t setting = escInfoBuffer[18];
2871 cliPrint("Low voltage Limit: ");
2872 switch (setting) {
2873 case 0:
2874 cliPrintLine("off");
2876 break;
2877 case 255:
2878 cliPrintLine("unsupported");
2880 break;
2881 default:
2882 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
2884 break;
2887 setting = escInfoBuffer[19];
2888 cliPrint("Current Limit: ");
2889 switch (setting) {
2890 case 0:
2891 cliPrintLine("off");
2893 break;
2894 case 255:
2895 cliPrintLine("unsupported");
2897 break;
2898 default:
2899 cliPrintLinef("%d", setting);
2901 break;
2904 for (int i = 0; i < 4; i++) {
2905 setting = escInfoBuffer[i + 20];
2906 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
2910 } else {
2911 cliPrintErrorLinef("Checksum Error.");
2916 if (!escInfoReceived) {
2917 cliPrintLine("No Info.");
2921 static void executeEscInfoCommand(uint8_t escIndex)
2923 cliPrintLinef("Info for ESC %d:", escIndex);
2925 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
2927 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
2929 pwmWriteDshotCommand(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, true);
2931 delay(10);
2933 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
2935 #endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
2937 static void cliDshotProg(char *cmdline)
2939 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
2940 cliShowParseError();
2942 return;
2945 char *saveptr;
2946 char *pch = strtok_r(cmdline, " ", &saveptr);
2947 int pos = 0;
2948 int escIndex = 0;
2949 bool firstCommand = true;
2950 while (pch != NULL) {
2951 switch (pos) {
2952 case 0:
2953 escIndex = parseOutputIndex(pch, true);
2954 if (escIndex == -1) {
2955 return;
2958 break;
2959 default:
2961 int command = atoi(pch);
2962 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
2963 if (firstCommand) {
2964 pwmDisableMotors();
2966 if (command == DSHOT_CMD_ESC_INFO) {
2967 delay(5); // Wait for potential ESC telemetry transmission to finish
2968 } else {
2969 delay(1);
2972 firstCommand = false;
2975 if (command != DSHOT_CMD_ESC_INFO) {
2976 pwmWriteDshotCommand(escIndex, getMotorCount(), command, true);
2977 } else {
2978 #if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
2979 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
2980 if (escIndex != ALL_MOTORS) {
2981 executeEscInfoCommand(escIndex);
2982 } else {
2983 for (uint8_t i = 0; i < getMotorCount(); i++) {
2984 executeEscInfoCommand(i);
2987 } else
2988 #endif
2990 cliPrintLine("Not supported.");
2994 cliPrintLinef("Command Sent: %d", command);
2996 } else {
2997 cliPrintErrorLinef("Invalid command. Range: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
3001 break;
3004 pos++;
3005 pch = strtok_r(NULL, " ", &saveptr);
3008 pwmEnableMotors();
3010 #endif // USE_DSHOT
3012 #ifdef USE_ESCSERIAL
3013 static void cliEscPassthrough(char *cmdline)
3015 if (isEmpty(cmdline)) {
3016 cliShowParseError();
3018 return;
3021 char *saveptr;
3022 char *pch = strtok_r(cmdline, " ", &saveptr);
3023 int pos = 0;
3024 uint8_t mode = 0;
3025 int escIndex = 0;
3026 while (pch != NULL) {
3027 switch (pos) {
3028 case 0:
3029 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
3030 mode = PROTOCOL_SIMONK;
3031 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
3032 mode = PROTOCOL_BLHELI;
3033 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
3034 mode = PROTOCOL_KISS;
3035 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
3036 mode = PROTOCOL_KISSALL;
3037 } else {
3038 cliShowParseError();
3040 return;
3042 break;
3043 case 1:
3044 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
3045 if (escIndex == -1) {
3046 return;
3049 break;
3050 default:
3051 cliShowParseError();
3053 return;
3055 break;
3058 pos++;
3059 pch = strtok_r(NULL, " ", &saveptr);
3062 escEnablePassthrough(cliPort, escIndex, mode);
3064 #endif
3066 #ifndef USE_QUAD_MIXER_ONLY
3067 static void cliMixer(char *cmdline)
3069 int len;
3071 len = strlen(cmdline);
3073 if (len == 0) {
3074 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
3075 return;
3076 } else if (strncasecmp(cmdline, "list", len) == 0) {
3077 cliPrint("Available:");
3078 for (uint32_t i = 0; ; i++) {
3079 if (mixerNames[i] == NULL)
3080 break;
3081 cliPrintf(" %s", mixerNames[i]);
3083 cliPrintLinefeed();
3084 return;
3087 for (uint32_t i = 0; ; i++) {
3088 if (mixerNames[i] == NULL) {
3089 cliPrintErrorLinef("Invalid name");
3090 return;
3092 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
3093 mixerConfigMutable()->mixerMode = i + 1;
3094 break;
3098 cliMixer("");
3100 #endif
3102 static void cliMotor(char *cmdline)
3104 if (isEmpty(cmdline)) {
3105 cliShowParseError();
3107 return;
3110 int motorIndex = 0;
3111 int motorValue = 0;
3113 char *saveptr;
3114 char *pch = strtok_r(cmdline, " ", &saveptr);
3115 int index = 0;
3116 while (pch != NULL) {
3117 switch (index) {
3118 case 0:
3119 motorIndex = parseOutputIndex(pch, true);
3120 if (motorIndex == -1) {
3121 return;
3124 break;
3125 case 1:
3126 motorValue = atoi(pch);
3128 break;
3130 index++;
3131 pch = strtok_r(NULL, " ", &saveptr);
3134 if (index == 2) {
3135 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
3136 cliShowArgumentRangeError("value", 1000, 2000);
3137 } else {
3138 uint32_t motorOutputValue = convertExternalToMotor(motorValue);
3140 if (motorIndex != ALL_MOTORS) {
3141 motor_disarmed[motorIndex] = motorOutputValue;
3143 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
3144 } else {
3145 for (int i = 0; i < getMotorCount(); i++) {
3146 motor_disarmed[i] = motorOutputValue;
3149 cliPrintLinef("all motors: %d", motorOutputValue);
3152 } else {
3153 cliShowParseError();
3157 #ifndef MINIMAL_CLI
3158 static void cliPlaySound(char *cmdline)
3160 int i;
3161 const char *name;
3162 static int lastSoundIdx = -1;
3164 if (isEmpty(cmdline)) {
3165 i = lastSoundIdx + 1; //next sound index
3166 if ((name=beeperNameForTableIndex(i)) == NULL) {
3167 while (true) { //no name for index; try next one
3168 if (++i >= beeperTableEntryCount())
3169 i = 0; //if end then wrap around to first entry
3170 if ((name=beeperNameForTableIndex(i)) != NULL)
3171 break; //if name OK then play sound below
3172 if (i == lastSoundIdx + 1) { //prevent infinite loop
3173 cliPrintErrorLinef("Error playing sound");
3174 return;
3178 } else { //index value was given
3179 i = atoi(cmdline);
3180 if ((name=beeperNameForTableIndex(i)) == NULL) {
3181 cliPrintLinef("No sound for index %d", i);
3182 return;
3185 lastSoundIdx = i;
3186 beeperSilence();
3187 cliPrintLinef("Playing sound %d: %s", i, name);
3188 beeper(beeperModeForTableIndex(i));
3190 #endif
3192 static void cliProfile(char *cmdline)
3194 if (isEmpty(cmdline)) {
3195 cliPrintLinef("profile %d", getPidProfileIndexToUse());
3196 return;
3197 } else {
3198 const int i = atoi(cmdline);
3199 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3200 changePidProfile(i);
3201 cliProfile("");
3206 static void cliRateProfile(char *cmdline)
3208 if (isEmpty(cmdline)) {
3209 cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
3210 return;
3211 } else {
3212 const int i = atoi(cmdline);
3213 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
3214 changeControlRateProfile(i);
3215 cliRateProfile("");
3220 static void cliDumpPidProfile(uint8_t pidProfileIndex, uint8_t dumpMask)
3222 if (pidProfileIndex >= MAX_PROFILE_COUNT) {
3223 // Faulty values
3224 return;
3227 pidProfileIndexToUse = pidProfileIndex;
3229 cliPrintHashLine("profile");
3230 cliProfile("");
3231 cliPrintLinefeed();
3232 dumpAllValues(PROFILE_VALUE, dumpMask);
3234 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
3237 static void cliDumpRateProfile(uint8_t rateProfileIndex, uint8_t dumpMask)
3239 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
3240 // Faulty values
3241 return;
3244 rateProfileIndexToUse = rateProfileIndex;
3246 cliPrintHashLine("rateprofile");
3247 cliRateProfile("");
3248 cliPrintLinefeed();
3249 dumpAllValues(PROFILE_RATE_VALUE, dumpMask);
3251 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
3254 static void cliSave(char *cmdline)
3256 UNUSED(cmdline);
3258 cliPrintHashLine("saving");
3260 #if defined(USE_BOARD_INFO)
3261 if (boardInformationUpdated) {
3262 persistBoardInformation();
3264 #if defined(USE_SIGNATURE)
3265 if (signatureUpdated) {
3266 persistSignature();
3268 #endif
3269 #endif // USE_BOARD_INFO
3271 if (featureMaskIsCopied) {
3272 writeEEPROMWithFeatures(featureMaskCopy);
3273 } else {
3274 writeEEPROM();
3277 cliReboot();
3280 static void cliDefaults(char *cmdline)
3282 bool saveConfigs;
3284 if (isEmpty(cmdline)) {
3285 saveConfigs = true;
3286 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
3287 saveConfigs = false;
3288 } else {
3289 return;
3292 cliPrintHashLine("resetting to defaults");
3294 resetConfigs();
3296 if (saveConfigs) {
3297 cliSave(NULL);
3301 void cliPrintVarDefault(const clivalue_t *value)
3303 const pgRegistry_t *pg = pgFind(value->pgn);
3304 if (pg) {
3305 const char *defaultFormat = "Default value: ";
3306 const int valueOffset = getValueOffset(value);
3307 const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
3308 if (!equalsDefault) {
3309 cliPrintf(defaultFormat, value->name);
3310 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
3311 cliPrintLinefeed();
3316 STATIC_UNIT_TESTED void cliGet(char *cmdline)
3318 const clivalue_t *val;
3319 int matchedCommands = 0;
3321 pidProfileIndexToUse = getCurrentPidProfileIndex();
3322 rateProfileIndexToUse = getCurrentControlRateProfileIndex();
3324 backupAndResetConfigs();
3326 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3327 if (strcasestr(valueTable[i].name, cmdline)) {
3328 val = &valueTable[i];
3329 if (matchedCommands > 0) {
3330 cliPrintLinefeed();
3332 cliPrintf("%s = ", valueTable[i].name);
3333 cliPrintVar(val, 0);
3334 cliPrintLinefeed();
3335 switch (val->type & VALUE_SECTION_MASK) {
3336 case PROFILE_VALUE:
3337 cliProfile("");
3339 break;
3340 case PROFILE_RATE_VALUE:
3341 cliRateProfile("");
3343 break;
3344 default:
3346 break;
3348 cliPrintVarRange(val);
3349 cliPrintVarDefault(val);
3350 matchedCommands++;
3354 restoreConfigs();
3356 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
3357 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
3359 if (matchedCommands) {
3360 return;
3363 cliPrintErrorLinef("Invalid name");
3366 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
3368 while (*(bufEnd - 1) == ' ') {
3369 bufEnd--;
3372 return bufEnd - bufBegin;
3375 STATIC_UNIT_TESTED void cliSet(char *cmdline)
3377 const uint32_t len = strlen(cmdline);
3378 char *eqptr;
3380 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
3381 cliPrintLine("Current settings: ");
3383 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3384 const clivalue_t *val = &valueTable[i];
3385 cliPrintf("%s = ", valueTable[i].name);
3386 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3387 cliPrintLinefeed();
3389 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
3390 // has equals
3392 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
3394 // skip the '=' and any ' ' characters
3395 eqptr++;
3396 eqptr = skipSpace(eqptr);
3398 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
3399 const clivalue_t *val = &valueTable[i];
3401 // ensure exact match when setting to prevent setting variables with shorter names
3402 if (strncasecmp(cmdline, val->name, strlen(val->name)) == 0 && variableNameLength == strlen(val->name)) {
3404 bool valueChanged = false;
3405 int16_t value = 0;
3406 switch (val->type & VALUE_MODE_MASK) {
3407 case MODE_DIRECT: {
3408 int16_t value = atoi(eqptr);
3410 if (value >= val->config.minmax.min && value <= val->config.minmax.max) {
3411 cliSetVar(val, value);
3412 valueChanged = true;
3416 break;
3417 case MODE_LOOKUP:
3418 case MODE_BITSET: {
3419 int tableIndex;
3420 if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
3421 tableIndex = TABLE_OFF_ON;
3422 } else {
3423 tableIndex = val->config.lookup.tableIndex;
3425 const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
3426 bool matched = false;
3427 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
3428 matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
3430 if (matched) {
3431 value = tableValueIndex;
3433 cliSetVar(val, value);
3434 valueChanged = true;
3439 break;
3441 case MODE_ARRAY: {
3442 const uint8_t arrayLength = val->config.array.length;
3443 char *valPtr = eqptr;
3445 int i = 0;
3446 while (i < arrayLength && valPtr != NULL) {
3447 // skip spaces
3448 valPtr = skipSpace(valPtr);
3450 // process substring starting at valPtr
3451 // note: no need to copy substrings for atoi()
3452 // it stops at the first character that cannot be converted...
3453 switch (val->type & VALUE_TYPE_MASK) {
3454 default:
3455 case VAR_UINT8:
3457 // fetch data pointer
3458 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
3459 // store value
3460 *data = (uint8_t)atoi((const char*) valPtr);
3463 break;
3464 case VAR_INT8:
3466 // fetch data pointer
3467 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
3468 // store value
3469 *data = (int8_t)atoi((const char*) valPtr);
3472 break;
3473 case VAR_UINT16:
3475 // fetch data pointer
3476 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
3477 // store value
3478 *data = (uint16_t)atoi((const char*) valPtr);
3481 break;
3482 case VAR_INT16:
3484 // fetch data pointer
3485 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
3486 // store value
3487 *data = (int16_t)atoi((const char*) valPtr);
3490 break;
3493 // find next comma (or end of string)
3494 valPtr = strchr(valPtr, ',') + 1;
3496 i++;
3500 // mark as changed
3501 valueChanged = true;
3503 break;
3507 if (valueChanged) {
3508 cliPrintf("%s set to ", val->name);
3509 cliPrintVar(val, 0);
3510 } else {
3511 cliPrintErrorLinef("Invalid value");
3512 cliPrintVarRange(val);
3515 return;
3518 cliPrintErrorLinef("Invalid name");
3519 } else {
3520 // no equals, check for matching variables.
3521 cliGet(cmdline);
3525 static void cliStatus(char *cmdline)
3527 UNUSED(cmdline);
3529 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3531 #ifdef USE_RTC_TIME
3532 char buf[FORMATTED_DATE_TIME_BUFSIZE];
3533 dateTime_t dt;
3534 if (rtcGetDateTime(&dt)) {
3535 dateTimeFormatLocal(buf, &dt);
3536 cliPrintLinef("Current Time: %s", buf);
3538 #endif
3540 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
3542 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3544 #ifdef USE_ADC_INTERNAL
3545 uint16_t vrefintMv = getVrefMv();
3546 int16_t coretemp = getCoreTemperatureCelsius();
3547 cliPrintf(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
3548 #endif
3550 #if defined(USE_SENSOR_NAMES)
3551 const uint32_t detectedSensorsMask = sensorsMask();
3552 for (uint32_t i = 0; ; i++) {
3553 if (sensorTypeNames[i] == NULL) {
3554 break;
3556 const uint32_t mask = (1 << i);
3557 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3558 const uint8_t sensorHardwareIndex = detectedSensors[i];
3559 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3560 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3561 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
3562 cliPrintf(".%c", acc.dev.revisionCode);
3566 #endif /* USE_SENSOR_NAMES */
3567 cliPrintLinefeed();
3569 #ifdef USE_SDCARD
3570 cliSdInfo(NULL);
3571 #endif
3573 #ifdef USE_I2C
3574 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3575 #else
3576 const uint16_t i2cErrorCounter = 0;
3577 #endif
3579 #ifdef STACK_CHECK
3580 cliPrintf("Stack used: %d, ", stackUsedSize());
3581 #endif
3582 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
3583 #ifdef EEPROM_IN_RAM
3584 #define CONFIG_SIZE EEPROM_SIZE
3585 #else
3586 #define CONFIG_SIZE (&__config_end - &__config_start)
3587 #endif
3588 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), CONFIG_SIZE);
3590 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
3591 const int rxRate = currentRxRefreshRate == 0 ? 0 : (int)(1000000.0f / ((float)currentRxRefreshRate));
3592 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3593 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
3594 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
3595 cliPrint("Arming disable flags:");
3596 armingDisableFlags_e flags = getArmingDisableFlags();
3597 while (flags) {
3598 const int bitpos = ffs(flags) - 1;
3599 flags &= ~(1 << bitpos);
3600 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
3602 cliPrintLinefeed();
3605 #ifndef SKIP_TASK_STATISTICS
3606 static void cliTasks(char *cmdline)
3608 UNUSED(cmdline);
3609 int maxLoadSum = 0;
3610 int averageLoadSum = 0;
3612 #ifndef MINIMAL_CLI
3613 if (systemConfig()->task_statistics) {
3614 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
3615 } else {
3616 cliPrintLine("Task list");
3618 #endif
3619 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3620 cfTaskInfo_t taskInfo;
3621 getTaskInfo(taskId, &taskInfo);
3622 if (taskInfo.isEnabled) {
3623 int taskFrequency;
3624 int subTaskFrequency = 0;
3625 if (taskId == TASK_GYROPID) {
3626 subTaskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3627 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
3628 if (pidConfig()->pid_process_denom > 1) {
3629 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3630 } else {
3631 taskFrequency = subTaskFrequency;
3632 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
3634 } else {
3635 taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3636 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3638 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3639 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3640 if (taskId != TASK_SERIAL) {
3641 maxLoadSum += maxLoad;
3642 averageLoadSum += averageLoad;
3644 if (systemConfig()->task_statistics) {
3645 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
3646 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
3647 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
3648 } else {
3649 cliPrintLinef("%6d", taskFrequency);
3651 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
3652 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
3655 schedulerResetTaskMaxExecutionTime(taskId);
3658 if (systemConfig()->task_statistics) {
3659 cfCheckFuncInfo_t checkFuncInfo;
3660 getCheckFuncInfo(&checkFuncInfo);
3661 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
3662 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3665 #endif
3667 static void cliVersion(char *cmdline)
3669 UNUSED(cmdline);
3671 cliPrintLinef("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
3672 FC_FIRMWARE_NAME,
3673 targetName,
3674 systemConfig()->boardIdentifier,
3675 FC_VERSION_STRING,
3676 buildDate,
3677 buildTime,
3678 shortGitRevision,
3679 MSP_API_VERSION_STRING
3683 #ifdef USE_RC_SMOOTHING_FILTER
3684 static void cliRcSmoothing(char *cmdline)
3686 UNUSED(cmdline);
3687 cliPrint("# RC Smoothing Type: ");
3688 if (rxConfig()->rc_smoothing_type == RC_SMOOTHING_TYPE_FILTER) {
3689 cliPrintLine("FILTER");
3690 uint16_t avgRxFrameMs = rcSmoothingGetValue(RC_SMOOTHING_VALUE_AVERAGE_FRAME);
3691 if (rcSmoothingAutoCalculate()) {
3692 cliPrint("# Detected RX frame rate: ");
3693 if (avgRxFrameMs == 0) {
3694 cliPrintLine("NO SIGNAL");
3695 } else {
3696 cliPrintLinef("%d.%dms", avgRxFrameMs / 1000, avgRxFrameMs % 1000);
3699 cliPrint("# Input filter type: ");
3700 cliPrintLinef(lookupTables[TABLE_RC_SMOOTHING_INPUT_TYPE].values[rxConfig()->rc_smoothing_input_type]);
3701 cliPrintf("# Active input cutoff: %dhz ", rcSmoothingGetValue(RC_SMOOTHING_VALUE_INPUT_ACTIVE));
3702 if (rxConfig()->rc_smoothing_input_cutoff == 0) {
3703 cliPrintLine("(auto)");
3704 } else {
3705 cliPrintLine("(manual)");
3707 cliPrint("# Derivative filter type: ");
3708 cliPrintLinef(lookupTables[TABLE_RC_SMOOTHING_DERIVATIVE_TYPE].values[rxConfig()->rc_smoothing_derivative_type]);
3709 cliPrintf("# Active derivative cutoff: %dhz (", rcSmoothingGetValue(RC_SMOOTHING_VALUE_DERIVATIVE_ACTIVE));
3710 if (rxConfig()->rc_smoothing_derivative_type == RC_SMOOTHING_DERIVATIVE_OFF) {
3711 cliPrintLine("off)");
3712 } else {
3713 if (rxConfig()->rc_smoothing_derivative_cutoff == 0) {
3714 cliPrintLine("auto)");
3715 } else {
3716 cliPrintLine("manual)");
3719 } else {
3720 cliPrintLine("INTERPOLATION");
3723 #endif // USE_RC_SMOOTHING_FILTER
3725 #if defined(USE_RESOURCE_MGMT)
3727 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
3729 typedef struct {
3730 const uint8_t owner;
3731 pgn_t pgn;
3732 uint8_t stride;
3733 uint8_t offset;
3734 const uint8_t maxIndex;
3735 } cliResourceValue_t;
3737 // Handy macros for keeping the table tidy.
3738 // DEFS : Single entry
3739 // DEFA : Array of uint8_t (stride = 1)
3740 // DEFW : Wider stride case; array of structs.
3742 #define DEFS(owner, pgn, type, member) \
3743 { owner, pgn, 0, offsetof(type, member), 0 }
3745 #define DEFA(owner, pgn, type, member, max) \
3746 { owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
3748 #define DEFW(owner, pgn, type, member, max) \
3749 { owner, pgn, sizeof(type), offsetof(type, member), max }
3751 const cliResourceValue_t resourceTable[] = {
3752 #ifdef USE_BEEPER
3753 DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag) ,
3754 #endif
3755 DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
3756 #ifdef USE_SERVOS
3757 DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
3758 #endif
3759 #if defined(USE_PPM)
3760 DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
3761 #endif
3762 #if defined(USE_PWM)
3763 DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
3764 #endif
3765 #ifdef USE_RANGEFINDER_HCSR04
3766 DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
3767 DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
3768 #endif
3769 #ifdef USE_LED_STRIP
3770 DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
3771 #endif
3772 DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[0], SERIAL_PORT_MAX_INDEX ),
3773 DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[0], SERIAL_PORT_MAX_INDEX ),
3774 #ifdef USE_INVERTER
3775 DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[0], SERIAL_PORT_MAX_INDEX ),
3776 #endif
3777 #ifdef USE_I2C
3778 DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
3779 DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
3780 #endif
3781 DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_NUMBER ),
3782 #ifdef USE_SPEKTRUM_BIND
3783 DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
3784 DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
3785 #endif
3786 #ifdef USE_TRANSPONDER
3787 DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
3788 #endif
3789 #ifdef USE_SPI
3790 DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
3791 DEFW( OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
3792 DEFW( OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
3793 #endif
3794 #ifdef USE_ESCSERIAL
3795 DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
3796 #endif
3797 #ifdef USE_CAMERA_CONTROL
3798 DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
3799 #endif
3800 #ifdef USE_ADC
3801 DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
3802 DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
3803 DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
3804 DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
3805 #endif
3806 #ifdef USE_BARO
3807 DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
3808 #endif
3809 #ifdef USE_MAG
3810 DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
3811 #ifdef USE_MAG_DATA_READY_SIGNAL
3812 DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
3813 #endif
3814 #endif
3815 #ifdef USE_SDCARD
3816 DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
3817 DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
3818 #endif
3819 #ifdef USE_PINIO
3820 DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
3821 #endif
3822 #if defined(USE_USB_MSC)
3823 DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
3824 #endif
3825 #ifdef USE_FLASH
3826 DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
3827 #endif
3828 #ifdef USE_MAX7456
3829 DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
3830 #endif
3831 #ifdef USE_SPI
3832 DEFA( OWNER_SPI_PREINIT_IPU, PG_SPI_PREINIT_IPU_CONFIG, spiCs_t, csnTag, SPI_PREINIT_IPU_COUNT ),
3833 DEFA( OWNER_SPI_PREINIT_OPU, PG_SPI_PREINIT_OPU_CONFIG, spiCs_t, csnTag, SPI_PREINIT_OPU_COUNT ),
3834 #endif
3835 #ifdef USE_RX_SPI
3836 DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
3837 #endif
3840 #undef DEFS
3841 #undef DEFA
3842 #undef DEFW
3844 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
3846 const pgRegistry_t* rec = pgFind(value.pgn);
3847 return CONST_CAST(ioTag_t *, rec->address + value.stride * index + value.offset);
3850 static void printResource(uint8_t dumpMask)
3852 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
3853 const char* owner = ownerNames[resourceTable[i].owner];
3854 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
3855 const void *currentConfig;
3856 const void *defaultConfig;
3857 if (configIsInCopy) {
3858 currentConfig = pg->copy;
3859 defaultConfig = pg->address;
3860 } else {
3861 currentConfig = pg->address;
3862 defaultConfig = NULL;
3865 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
3866 const ioTag_t ioTag = *((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
3867 const ioTag_t ioTagDefault = *((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
3869 bool equalsDefault = ioTag == ioTagDefault;
3870 const char *format = "resource %s %d %c%02d";
3871 const char *formatUnassigned = "resource %s %d NONE";
3872 if (!ioTagDefault) {
3873 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3874 } else {
3875 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
3877 if (!ioTag) {
3878 if (!(dumpMask & HIDE_UNUSED)) {
3879 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3881 } else {
3882 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
3888 static void printResourceOwner(uint8_t owner, uint8_t index)
3890 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
3892 if (resourceTable[owner].maxIndex > 0) {
3893 cliPrintf(" %d", RESOURCE_INDEX(index));
3897 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
3899 if (!newTag) {
3900 return;
3903 const char * format = "\r\nNOTE: %c%02d already assigned to ";
3904 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
3905 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
3906 ioTag_t *tag = getIoTag(resourceTable[r], i);
3907 if (*tag == newTag) {
3908 bool cleared = false;
3909 if (r == resourceIndex) {
3910 if (i == index) {
3911 continue;
3913 *tag = IO_TAG_NONE;
3914 cleared = true;
3917 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
3919 printResourceOwner(r, i);
3921 if (cleared) {
3922 cliPrintf(". ");
3923 printResourceOwner(r, i);
3924 cliPrintf(" disabled");
3927 cliPrintLine(".");
3933 static bool strToPin(char *pch, ioTag_t *tag)
3935 if (strcasecmp(pch, "NONE") == 0) {
3936 *tag = IO_TAG_NONE;
3937 return true;
3938 } else {
3939 unsigned pin = 0;
3940 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
3942 if (port < 8) {
3943 pch++;
3944 pin = atoi(pch);
3945 if (pin < 16) {
3946 *tag = DEFIO_TAG_MAKE(port, pin);
3947 return true;
3951 return false;
3954 static void cliResource(char *cmdline)
3956 int len = strlen(cmdline);
3958 if (len == 0) {
3959 printResource(DUMP_MASTER | HIDE_UNUSED);
3961 return;
3962 } else if (strncasecmp(cmdline, "list", len) == 0) {
3963 #ifdef MINIMAL_CLI
3964 cliPrintLine("IO");
3965 #else
3966 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
3967 cliRepeat('-', 20);
3968 #endif
3969 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3970 const char* owner;
3971 owner = ownerNames[ioRecs[i].owner];
3973 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
3974 if (ioRecs[i].index > 0) {
3975 cliPrintf(" %d", ioRecs[i].index);
3977 cliPrintLinefeed();
3980 #ifndef MINIMAL_CLI
3981 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
3982 #endif
3984 return;
3987 uint8_t resourceIndex = 0;
3988 int index = 0;
3989 char *pch = NULL;
3990 char *saveptr;
3992 pch = strtok_r(cmdline, " ", &saveptr);
3993 for (resourceIndex = 0; ; resourceIndex++) {
3994 if (resourceIndex >= ARRAYLEN(resourceTable)) {
3995 cliPrintErrorLinef("Invalid");
3996 return;
3999 if (strncasecmp(pch, ownerNames[resourceTable[resourceIndex].owner], len) == 0) {
4000 break;
4004 pch = strtok_r(NULL, " ", &saveptr);
4005 index = atoi(pch);
4007 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
4008 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
4009 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
4010 return;
4012 index -= 1;
4014 pch = strtok_r(NULL, " ", &saveptr);
4017 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
4019 if (strlen(pch) > 0) {
4020 if (strToPin(pch, tag)) {
4021 if (*tag == IO_TAG_NONE) {
4022 #ifdef MINIMAL_CLI
4023 cliPrintLine("Freed");
4024 #else
4025 cliPrintLine("Resource is freed");
4026 #endif
4027 return;
4028 } else {
4029 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
4030 if (rec) {
4031 resourceCheck(resourceIndex, index, *tag);
4032 #ifdef MINIMAL_CLI
4033 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
4034 #else
4035 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
4036 #endif
4037 } else {
4038 cliShowParseError();
4040 return;
4045 cliShowParseError();
4048 static void printDma(void)
4050 cliPrintLinefeed();
4052 #ifdef MINIMAL_CLI
4053 cliPrintLine("DMA:");
4054 #else
4055 cliPrintLine("Currently active DMA:");
4056 cliRepeat('-', 20);
4057 #endif
4058 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
4059 const char* owner;
4060 owner = ownerNames[dmaGetOwner(i)];
4062 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
4063 uint8_t resourceIndex = dmaGetResourceIndex(i);
4064 if (resourceIndex > 0) {
4065 cliPrintLinef(" %s %d", owner, resourceIndex);
4066 } else {
4067 cliPrintLinef(" %s", owner);
4072 static void cliDma(char* cmdLine)
4074 UNUSED(cmdLine);
4075 printDma();
4077 #endif /* USE_RESOURCE_MGMT */
4079 #ifdef USE_TIMER_MGMT
4081 static void printTimer(uint8_t dumpMask)
4083 cliPrintLine("# examples: ");
4084 const char *format = "timer %c%02d %d";
4085 cliPrint("#");
4086 cliPrintLinef(format, 'A', 1, 1);
4088 cliPrint("#");
4089 cliPrintLinef(format, 'A', 1, 0);
4091 for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
4093 const ioTag_t ioTag = timerIOConfig(i)->ioTag;
4094 const uint8_t timerIndex = timerIOConfig(i)->index;
4096 if (!ioTag) {
4097 continue;
4100 if (timerIndex != 0 && !(dumpMask & HIDE_UNUSED)) {
4101 cliDumpPrintLinef(dumpMask, false, format,
4102 IO_GPIOPortIdxByTag(ioTag) + 'A',
4103 IO_GPIOPinIdxByTag(ioTag),
4104 timerIndex
4110 static void cliTimer(char *cmdline)
4112 int len = strlen(cmdline);
4114 if (len == 0) {
4115 printTimer(DUMP_MASTER | HIDE_UNUSED);
4116 return;
4117 } else if (strncasecmp(cmdline, "list", len) == 0) {
4118 printTimer(DUMP_MASTER);
4119 return;
4122 char *pch = NULL;
4123 char *saveptr;
4124 int timerIOIndex = -1;
4126 ioTag_t ioTag = 0;
4127 pch = strtok_r(cmdline, " ", &saveptr);
4128 if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
4129 goto error;
4132 /* find existing entry, or go for next available */
4133 for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
4134 if (timerIOConfig(i)->ioTag == ioTag) {
4135 timerIOIndex = i;
4136 break;
4139 /* first available empty slot */
4140 if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
4141 timerIOIndex = i;
4145 if (timerIOIndex < 0) {
4146 cliPrintErrorLinef("Index out of range.");
4147 return;
4150 uint8_t timerIndex = 0;
4151 pch = strtok_r(NULL, " ", &saveptr);
4152 if (pch) {
4153 if (strcasecmp(pch, "list") == 0) {
4154 /* output the list of available options */
4155 uint8_t index = 1;
4156 for (unsigned i = 0; i < USABLE_TIMER_CHANNEL_COUNT; i++) {
4157 if (timerHardware[i].tag == ioTag) {
4158 cliPrintLinef("# %d. TIM%d CH%d",
4159 index,
4160 timerGetTIMNumber(timerHardware[i].tim),
4161 CC_INDEX_FROM_CHANNEL(timerHardware[i].channel)
4163 index++;
4166 return;
4167 } else if (strcasecmp(pch, "none") == 0) {
4168 goto success;
4169 } else {
4170 timerIndex = atoi(pch);
4172 } else {
4173 goto error;
4176 success:
4177 timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == 0 ? IO_TAG_NONE : ioTag;
4178 timerIOConfigMutable(timerIOIndex)->index = timerIndex;
4180 cliPrintLine("Success");
4181 return;
4183 error:
4184 cliShowParseError();
4186 #endif
4188 static void printConfig(char *cmdline, bool doDiff)
4190 uint8_t dumpMask = DUMP_MASTER;
4191 char *options;
4192 if ((options = checkCommand(cmdline, "master"))) {
4193 dumpMask = DUMP_MASTER; // only
4194 } else if ((options = checkCommand(cmdline, "profile"))) {
4195 dumpMask = DUMP_PROFILE; // only
4196 } else if ((options = checkCommand(cmdline, "rates"))) {
4197 dumpMask = DUMP_RATES; // only
4198 } else if ((options = checkCommand(cmdline, "all"))) {
4199 dumpMask = DUMP_ALL; // all profiles and rates
4200 } else {
4201 options = cmdline;
4204 if (doDiff) {
4205 dumpMask = dumpMask | DO_DIFF;
4208 backupAndResetConfigs();
4209 if (checkCommand(options, "defaults")) {
4210 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
4213 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
4214 cliPrintHashLine("version");
4215 cliVersion(NULL);
4216 cliPrintLinefeed();
4218 #if defined(USE_BOARD_INFO)
4219 cliBoardName("");
4220 cliManufacturerId("");
4221 #endif
4223 if (dumpMask & DUMP_ALL) {
4224 cliMcuId(NULL);
4225 #if defined(USE_BOARD_INFO) && defined(USE_SIGNATURE)
4226 cliSignature("");
4227 #endif
4230 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
4231 cliPrintHashLine("reset configuration to default settings");
4232 cliPrint("defaults nosave");
4233 cliPrintLinefeed();
4236 cliPrintHashLine("name");
4237 printName(dumpMask, &pilotConfig_Copy);
4239 #ifdef USE_RESOURCE_MGMT
4240 cliPrintHashLine("resources");
4241 printResource(dumpMask);
4242 #endif
4244 #ifndef USE_QUAD_MIXER_ONLY
4245 cliPrintHashLine("mixer");
4246 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
4247 const char *formatMixer = "mixer %s";
4248 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
4249 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
4251 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
4253 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0));
4255 #ifdef USE_SERVOS
4256 cliPrintHashLine("servo");
4257 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
4259 cliPrintHashLine("servo mix");
4260 // print custom servo mixer if exists
4261 cliDumpPrintLinef(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n");
4262 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
4263 #endif
4264 #endif
4266 cliPrintHashLine("feature");
4267 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
4269 #if defined(USE_BEEPER)
4270 cliPrintHashLine("beeper");
4271 printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper");
4273 #if defined(USE_DSHOT)
4274 cliPrintHashLine("beacon");
4275 printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon");
4276 #endif
4277 #endif // USE_BEEPER
4279 cliPrintHashLine("map");
4280 printMap(dumpMask, &rxConfig_Copy, rxConfig());
4282 cliPrintHashLine("serial");
4283 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
4285 #ifdef USE_LED_STRIP
4286 cliPrintHashLine("led");
4287 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
4289 cliPrintHashLine("color");
4290 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
4292 cliPrintHashLine("mode_color");
4293 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
4294 #endif
4296 cliPrintHashLine("aux");
4297 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
4299 cliPrintHashLine("adjrange");
4300 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
4302 cliPrintHashLine("rxrange");
4303 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
4305 #ifdef USE_VTX_CONTROL
4306 cliPrintHashLine("vtx");
4307 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig());
4308 #endif
4310 cliPrintHashLine("rxfail");
4311 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0));
4313 cliPrintHashLine("master");
4314 dumpAllValues(MASTER_VALUE, dumpMask);
4316 if (dumpMask & DUMP_ALL) {
4317 for (uint32_t pidProfileIndex = 0; pidProfileIndex < MAX_PROFILE_COUNT; pidProfileIndex++) {
4318 cliDumpPidProfile(pidProfileIndex, dumpMask);
4320 cliPrintHashLine("restore original profile selection");
4322 pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
4324 cliProfile("");
4326 pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
4328 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
4329 cliDumpRateProfile(rateIndex, dumpMask);
4331 cliPrintHashLine("restore original rateprofile selection");
4333 rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
4335 cliRateProfile("");
4337 rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
4339 cliPrintHashLine("save configuration");
4340 cliPrint("save");
4341 } else {
4342 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
4344 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
4348 if (dumpMask & DUMP_PROFILE) {
4349 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
4352 if (dumpMask & DUMP_RATES) {
4353 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
4355 // restore configs from copies
4356 restoreConfigs();
4359 static void cliDump(char *cmdline)
4361 printConfig(cmdline, false);
4364 static void cliDiff(char *cmdline)
4366 printConfig(cmdline, true);
4369 #if defined(USE_USB_MSC)
4370 static void cliMsc(char *cmdline)
4372 UNUSED(cmdline);
4374 if (mscCheckFilesystemReady()) {
4375 cliPrintHashLine("Restarting in mass storage mode");
4376 cliPrint("\r\nRebooting");
4377 bufWriterFlush(cliWriter);
4378 waitForSerialPortToFinishTransmitting(cliPort);
4379 stopPwmAllMotors();
4381 systemResetToMsc();
4382 } else {
4383 cliPrintHashLine("Storage not present or failed to initialize!");
4386 #endif
4388 typedef struct {
4389 const char *name;
4390 #ifndef MINIMAL_CLI
4391 const char *description;
4392 const char *args;
4393 #endif
4394 void (*func)(char *cmdline);
4395 } clicmd_t;
4397 #ifndef MINIMAL_CLI
4398 #define CLI_COMMAND_DEF(name, description, args, method) \
4400 name , \
4401 description , \
4402 args , \
4403 method \
4405 #else
4406 #define CLI_COMMAND_DEF(name, description, args, method) \
4408 name, \
4409 method \
4411 #endif
4413 static void cliHelp(char *cmdline);
4415 // should be sorted a..z for bsearch()
4416 const clicmd_t cmdTable[] = {
4417 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
4418 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
4419 #if defined(USE_BEEPER)
4420 #if defined(USE_DSHOT)
4421 CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
4422 "\t<->[name]", cliBeacon),
4423 #endif
4424 CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
4425 "\t<->[name]", cliBeeper),
4426 #endif // USE_BEEPER
4427 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL, cliBootloader),
4428 #if defined(USE_BOARD_INFO)
4429 CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
4430 #endif
4431 #ifdef USE_LED_STRIP
4432 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
4433 #endif
4434 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults),
4435 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|all] {defaults}", cliDiff),
4436 #ifdef USE_RESOURCE_MGMT
4437 CLI_COMMAND_DEF("dma", "list dma utilisation", NULL, cliDma),
4438 #endif
4439 #ifdef USE_DSHOT
4440 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
4441 #endif
4442 CLI_COMMAND_DEF("dump", "dump configuration",
4443 "[master|profile|rates|all] {defaults}", cliDump),
4444 #ifdef USE_ESCSERIAL
4445 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
4446 #endif
4447 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
4448 CLI_COMMAND_DEF("feature", "configure features",
4449 "list\r\n"
4450 "\t<+|->[name]", cliFeature),
4451 #ifdef USE_FLASHFS
4452 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
4453 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
4454 #ifdef USE_FLASH_TOOLS
4455 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
4456 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
4457 #endif
4458 #endif
4459 #ifdef USE_RX_FRSKY_SPI
4460 CLI_COMMAND_DEF("frsky_bind", "initiate binding for FrSky SPI RX", NULL, cliFrSkyBind),
4461 #endif
4462 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
4463 #ifdef USE_GPS
4464 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
4465 #endif
4466 #if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
4467 CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
4468 #endif
4469 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
4470 #ifdef USE_LED_STRIP
4471 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
4472 #endif
4473 #if defined(USE_BOARD_INFO)
4474 CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
4475 #endif
4476 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
4477 CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
4478 #ifndef USE_QUAD_MIXER_ONLY
4479 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
4480 #endif
4481 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
4482 #ifdef USE_LED_STRIP
4483 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
4484 #endif
4485 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
4486 #ifdef USE_USB_MSC
4487 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
4488 #endif
4489 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
4490 #ifndef MINIMAL_CLI
4491 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
4492 #endif
4493 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
4494 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
4495 #ifdef USE_RC_SMOOTHING_FILTER
4496 CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
4497 #endif // USE_RC_SMOOTHING_FILTER
4498 #ifdef USE_RESOURCE_MGMT
4499 CLI_COMMAND_DEF("resource", "show/set resources", NULL, cliResource),
4500 #endif
4501 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
4502 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
4503 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
4504 #ifdef USE_SDCARD
4505 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
4506 #endif
4507 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
4508 #ifndef SKIP_SERIAL_PASSTHROUGH
4509 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [DTR PINIO]: passthrough to serial", cliSerialPassthrough),
4510 #endif
4511 #ifdef USE_SERVOS
4512 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
4513 #endif
4514 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
4515 #if defined(USE_BOARD_INFO) && defined(USE_SIGNATURE)
4516 CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
4517 #endif
4518 #ifdef USE_SERVOS
4519 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
4520 "\treset\r\n"
4521 "\tload <mixer>\r\n"
4522 "\treverse <servo> <source> r|n", cliServoMix),
4523 #endif
4524 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
4525 #ifndef SKIP_TASK_STATISTICS
4526 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
4527 #endif
4528 #ifdef USE_TIMER_MGMT
4529 CLI_COMMAND_DEF("timer", "show timer configuration", NULL, cliTimer),
4530 #endif
4531 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
4532 #ifdef USE_VTX_CONTROL
4533 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
4534 #endif
4537 static void cliHelp(char *cmdline)
4539 UNUSED(cmdline);
4541 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
4542 cliPrint(cmdTable[i].name);
4543 #ifndef MINIMAL_CLI
4544 if (cmdTable[i].description) {
4545 cliPrintf(" - %s", cmdTable[i].description);
4547 if (cmdTable[i].args) {
4548 cliPrintf("\r\n\t%s", cmdTable[i].args);
4550 #endif
4551 cliPrintLinefeed();
4555 void cliProcess(void)
4557 if (!cliWriter) {
4558 return;
4561 // Be a little bit tricky. Flush the last inputs buffer, if any.
4562 bufWriterFlush(cliWriter);
4564 while (serialRxBytesWaiting(cliPort)) {
4565 uint8_t c = serialRead(cliPort);
4566 if (c == '\t' || c == '?') {
4567 // do tab completion
4568 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
4569 uint32_t i = bufferIndex;
4570 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4571 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
4572 continue;
4573 if (!pstart)
4574 pstart = cmd;
4575 pend = cmd;
4577 if (pstart) { /* Buffer matches one or more commands */
4578 for (; ; bufferIndex++) {
4579 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
4580 break;
4581 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
4582 /* Unambiguous -- append a space */
4583 cliBuffer[bufferIndex++] = ' ';
4584 cliBuffer[bufferIndex] = '\0';
4585 break;
4587 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
4590 if (!bufferIndex || pstart != pend) {
4591 /* Print list of ambiguous matches */
4592 cliPrint("\r\033[K");
4593 for (cmd = pstart; cmd <= pend; cmd++) {
4594 cliPrint(cmd->name);
4595 cliWrite('\t');
4597 cliPrompt();
4598 i = 0; /* Redraw prompt */
4600 for (; i < bufferIndex; i++)
4601 cliWrite(cliBuffer[i]);
4602 } else if (!bufferIndex && c == 4) { // CTRL-D
4603 cliExit(cliBuffer);
4604 return;
4605 } else if (c == 12) { // NewPage / CTRL-L
4606 // clear screen
4607 cliPrint("\033[2J\033[1;1H");
4608 cliPrompt();
4609 } else if (bufferIndex && (c == '\n' || c == '\r')) {
4610 // enter pressed
4611 cliPrintLinefeed();
4613 // Strip comment starting with # from line
4614 char *p = cliBuffer;
4615 p = strchr(p, '#');
4616 if (NULL != p) {
4617 bufferIndex = (uint32_t)(p - cliBuffer);
4620 // Strip trailing whitespace
4621 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
4622 bufferIndex--;
4625 // Process non-empty lines
4626 if (bufferIndex > 0) {
4627 cliBuffer[bufferIndex] = 0; // null terminate
4629 const clicmd_t *cmd;
4630 char *options;
4631 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4632 if ((options = checkCommand(cliBuffer, cmd->name))) {
4633 break;
4636 if (cmd < cmdTable + ARRAYLEN(cmdTable))
4637 cmd->func(options);
4638 else
4639 cliPrint("Unknown command, try 'help'");
4640 bufferIndex = 0;
4643 memset(cliBuffer, 0, sizeof(cliBuffer));
4645 // 'exit' will reset this flag, so we don't need to print prompt again
4646 if (!cliMode)
4647 return;
4649 cliPrompt();
4650 } else if (c == 127) {
4651 // backspace
4652 if (bufferIndex) {
4653 cliBuffer[--bufferIndex] = 0;
4654 cliPrint("\010 \010");
4656 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
4657 if (!bufferIndex && c == ' ')
4658 continue; // Ignore leading spaces
4659 cliBuffer[bufferIndex++] = c;
4660 cliWrite(c);
4665 void cliEnter(serialPort_t *serialPort)
4667 cliMode = 1;
4668 cliPort = serialPort;
4669 setPrintfSerialPort(cliPort);
4670 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
4672 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
4674 #ifndef MINIMAL_CLI
4675 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4676 #else
4677 cliPrintLine("\r\nCLI");
4678 #endif
4679 setArmingDisabled(ARMING_DISABLED_CLI);
4681 cliPrompt();
4684 void cliInit(const serialConfig_t *serialConfig)
4686 UNUSED(serialConfig);
4688 #endif // USE_CLI