Configurable SDCARD, and clean up of DMA.
[betaflight.git] / src / main / interface / cli.c
blobe7c02a12139d99b2dd9f0b48f71f4bcc2a1e6cdd
1 /*
2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <stdarg.h>
22 #include <string.h>
23 #include <math.h>
24 #include <ctype.h>
26 #include "platform.h"
27 #include "common/time.h"
29 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
30 // signal that we're in cli mode
31 uint8_t cliMode = 0;
32 #ifndef EEPROM_IN_RAM
33 extern uint8_t __config_start; // configured via linker script when building binaries.
34 extern uint8_t __config_end;
35 #endif
37 #ifdef USE_CLI
39 #include "blackbox/blackbox.h"
41 #include "build/build_config.h"
42 #include "build/debug.h"
43 #include "build/version.h"
45 #include "cms/cms.h"
47 #include "common/axis.h"
48 #include "common/color.h"
49 #include "common/maths.h"
50 #include "common/printf.h"
51 #include "common/typeconversion.h"
52 #include "common/utils.h"
54 #include "config/config_eeprom.h"
55 #include "config/feature.h"
57 #include "drivers/accgyro/accgyro.h"
58 #include "drivers/adc.h"
59 #include "drivers/buf_writer.h"
60 #include "drivers/bus_spi.h"
61 #include "drivers/compass/compass.h"
62 #include "drivers/display.h"
63 #include "drivers/dma.h"
64 #include "drivers/flash.h"
65 #include "drivers/io.h"
66 #include "drivers/io_impl.h"
67 #include "drivers/inverter.h"
68 #include "drivers/sdcard.h"
69 #include "drivers/sensor.h"
70 #include "drivers/serial.h"
71 #include "drivers/serial_escserial.h"
72 #include "drivers/rangefinder/rangefinder_hcsr04.h"
73 #include "drivers/sound_beeper.h"
74 #include "drivers/stack_check.h"
75 #include "drivers/system.h"
76 #include "drivers/transponder_ir.h"
77 #include "drivers/time.h"
78 #include "drivers/timer.h"
79 #include "drivers/light_led.h"
80 #include "drivers/camera_control.h"
81 #include "drivers/vtx_common.h"
83 #include "fc/config.h"
84 #include "fc/controlrate_profile.h"
85 #include "fc/fc_core.h"
86 #include "fc/rc_adjustments.h"
87 #include "fc/rc_controls.h"
88 #include "fc/runtime_config.h"
90 #include "flight/altitude.h"
91 #include "flight/failsafe.h"
92 #include "flight/imu.h"
93 #include "flight/mixer.h"
94 #include "flight/navigation.h"
95 #include "flight/pid.h"
96 #include "flight/servos.h"
98 #include "interface/cli.h"
99 #include "interface/msp.h"
100 #include "interface/msp_box.h"
101 #include "interface/msp_protocol.h"
102 #include "interface/settings.h"
104 #include "io/asyncfatfs/asyncfatfs.h"
105 #include "io/beeper.h"
106 #include "io/flashfs.h"
107 #include "io/gimbal.h"
108 #include "io/gps.h"
109 #include "io/ledstrip.h"
110 #include "io/osd.h"
111 #include "io/serial.h"
112 #include "io/transponder_ir.h"
113 #include "io/vtx_control.h"
114 #include "io/vtx.h"
116 #include "pg/adc.h"
117 #include "pg/beeper.h"
118 #include "pg/beeper_dev.h"
119 #include "pg/bus_i2c.h"
120 #include "pg/bus_spi.h"
121 #include "pg/pg.h"
122 #include "pg/pg_ids.h"
123 #include "pg/rx_pwm.h"
125 #include "rx/rx.h"
126 #include "rx/spektrum.h"
127 #include "rx/cc2500_frsky_common.h"
128 #include "rx/cc2500_frsky_x.h"
130 #include "scheduler/scheduler.h"
132 #include "sensors/acceleration.h"
133 #include "sensors/adcinternal.h"
134 #include "sensors/barometer.h"
135 #include "sensors/battery.h"
136 #include "sensors/boardalignment.h"
137 #include "sensors/compass.h"
138 #include "sensors/esc_sensor.h"
139 #include "sensors/gyro.h"
140 #include "sensors/sensors.h"
142 #include "telemetry/frsky_hub.h"
143 #include "telemetry/telemetry.h"
146 static serialPort_t *cliPort;
148 #ifdef STM32F1
149 #define CLI_IN_BUFFER_SIZE 128
150 #else
151 // Space required to set array parameters
152 #define CLI_IN_BUFFER_SIZE 256
153 #endif
154 #define CLI_OUT_BUFFER_SIZE 64
156 static bufWriter_t *cliWriter;
157 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
159 static char cliBuffer[CLI_IN_BUFFER_SIZE];
160 static uint32_t bufferIndex = 0;
162 static bool configIsInCopy = false;
164 static const char* const emptyName = "-";
165 static const char* const emptryString = "";
167 #ifndef USE_QUAD_MIXER_ONLY
168 // sync this with mixerMode_e
169 static const char * const mixerNames[] = {
170 "TRI", "QUADP", "QUADX", "BI",
171 "GIMBAL", "Y6", "HEX6",
172 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
173 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
174 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
175 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
177 #endif
179 // sync this with features_e
180 static const char * const featureNames[] = {
181 "RX_PPM", "", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
182 "SERVO_TILT", "SOFTSERIAL", "GPS", "",
183 "RANGEFINDER", "TELEMETRY", "", "3D", "RX_PARALLEL_PWM",
184 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
185 "", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
186 "", "", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
189 // sync this with rxFailsafeChannelMode_e
190 static const char rxFailsafeModeCharacters[] = "ahs";
192 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
193 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_INVALID },
194 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
197 #if defined(USE_SENSOR_NAMES)
198 // sync this with sensors_e
199 static const char * const sensorTypeNames[] = {
200 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
203 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
205 static const char * const *sensorHardwareNames[] = {
206 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware
208 #endif // USE_SENSOR_NAMES
210 static void cliPrint(const char *str)
212 while (*str) {
213 bufWriterAppend(cliWriter, *str++);
215 bufWriterFlush(cliWriter);
218 static void cliPrintLinefeed(void)
220 cliPrint("\r\n");
223 static void cliPrintLine(const char *str)
225 cliPrint(str);
226 cliPrintLinefeed();
229 #ifdef MINIMAL_CLI
230 #define cliPrintHashLine(str)
231 #else
232 static void cliPrintHashLine(const char *str)
234 cliPrint("\r\n# ");
235 cliPrintLine(str);
237 #endif
239 static void cliPutp(void *p, char ch)
241 bufWriterAppend(p, ch);
244 typedef enum {
245 DUMP_MASTER = (1 << 0),
246 DUMP_PROFILE = (1 << 1),
247 DUMP_RATES = (1 << 2),
248 DUMP_ALL = (1 << 3),
249 DO_DIFF = (1 << 4),
250 SHOW_DEFAULTS = (1 << 5),
251 HIDE_UNUSED = (1 << 6)
252 } dumpFlags_e;
254 static void cliPrintfva(const char *format, va_list va)
256 tfp_format(cliWriter, cliPutp, format, va);
257 bufWriterFlush(cliWriter);
260 static void cliPrintLinefva(const char *format, va_list va)
262 tfp_format(cliWriter, cliPutp, format, va);
263 bufWriterFlush(cliWriter);
264 cliPrintLinefeed();
267 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
269 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
270 va_list va;
271 va_start(va, format);
272 cliPrintLinefva(format, va);
273 va_end(va);
274 return true;
275 } else {
276 return false;
280 static void cliWrite(uint8_t ch)
282 bufWriterAppend(cliWriter, ch);
285 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
287 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
288 cliWrite('#');
290 va_list va;
291 va_start(va, format);
292 cliPrintLinefva(format, va);
293 va_end(va);
294 return true;
295 } else {
296 return false;
300 static void cliPrintf(const char *format, ...)
302 va_list va;
303 va_start(va, format);
304 cliPrintfva(format, va);
305 va_end(va);
309 static void cliPrintLinef(const char *format, ...)
311 va_list va;
312 va_start(va, format);
313 cliPrintLinefva(format, va);
314 va_end(va);
318 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
320 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
321 for (int i = 0; i < var->config.array.length; i++) {
322 switch (var->type & VALUE_TYPE_MASK) {
323 default:
324 case VAR_UINT8:
325 // uint8_t array
326 cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
327 break;
329 case VAR_INT8:
330 // int8_t array
331 cliPrintf("%d", ((int8_t *)valuePointer)[i]);
332 break;
334 case VAR_UINT16:
335 // uin16_t array
336 cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
337 break;
339 case VAR_INT16:
340 // int16_t array
341 cliPrintf("%d", ((int16_t *)valuePointer)[i]);
342 break;
345 if (i < var->config.array.length - 1) {
346 cliPrint(",");
349 } else {
350 int value = 0;
352 switch (var->type & VALUE_TYPE_MASK) {
353 case VAR_UINT8:
354 value = *(uint8_t *)valuePointer;
355 break;
357 case VAR_INT8:
358 value = *(int8_t *)valuePointer;
359 break;
361 case VAR_UINT16:
362 case VAR_INT16:
363 value = *(int16_t *)valuePointer;
364 break;
367 switch (var->type & VALUE_MODE_MASK) {
368 case MODE_DIRECT:
369 cliPrintf("%d", value);
370 if (full) {
371 cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
373 break;
374 case MODE_LOOKUP:
375 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
376 break;
381 static bool valuePtrEqualsDefault(uint8_t type, const void *ptr, const void *ptrDefault)
383 bool result = false;
384 switch (type & VALUE_TYPE_MASK) {
385 case VAR_UINT8:
386 result = *(uint8_t *)ptr == *(uint8_t *)ptrDefault;
387 break;
389 case VAR_INT8:
390 result = *(int8_t *)ptr == *(int8_t *)ptrDefault;
391 break;
393 case VAR_UINT16:
394 case VAR_INT16:
395 result = *(int16_t *)ptr == *(int16_t *)ptrDefault;
396 break;
399 return result;
402 static uint16_t getValueOffset(const clivalue_t *value)
404 switch (value->type & VALUE_SECTION_MASK) {
405 case MASTER_VALUE:
406 return value->offset;
407 case PROFILE_VALUE:
408 return value->offset + sizeof(pidProfile_t) * getCurrentPidProfileIndex();
409 case PROFILE_RATE_VALUE:
410 return value->offset + sizeof(controlRateConfig_t) * getCurrentControlRateProfileIndex();
412 return 0;
415 void *cliGetValuePointer(const clivalue_t *value)
417 const pgRegistry_t* rec = pgFind(value->pgn);
418 return CONST_CAST(void *, rec->address + getValueOffset(value));
421 const void *cliGetDefaultPointer(const clivalue_t *value)
423 const pgRegistry_t* rec = pgFind(value->pgn);
424 return rec->address + getValueOffset(value);
427 static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
429 const pgRegistry_t *pg = pgFind(value->pgn);
430 #ifdef DEBUG
431 if (!pg) {
432 cliPrintLinef("VALUE %s ERROR", value->name);
433 return; // if it's not found, the pgn shouldn't be in the value table!
435 #endif
437 const char *format = "set %s = ";
438 const char *defaultFormat = "#set %s = ";
439 const int valueOffset = getValueOffset(value);
440 const bool equalsDefault = valuePtrEqualsDefault(value->type, pg->copy + valueOffset, pg->address + valueOffset);
442 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
443 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
444 cliPrintf(defaultFormat, value->name);
445 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
446 cliPrintLinefeed();
448 cliPrintf(format, value->name);
449 printValuePointer(value, pg->copy + valueOffset, false);
450 cliPrintLinefeed();
454 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
456 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
457 const clivalue_t *value = &valueTable[i];
458 bufWriterFlush(cliWriter);
459 if ((value->type & VALUE_SECTION_MASK) == valueSection) {
460 dumpPgValue(value, dumpMask);
465 static void cliPrintVar(const clivalue_t *var, bool full)
467 const void *ptr = cliGetValuePointer(var);
469 printValuePointer(var, ptr, full);
472 static void cliPrintVarRange(const clivalue_t *var)
474 switch (var->type & VALUE_MODE_MASK) {
475 case (MODE_DIRECT): {
476 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
478 break;
479 case (MODE_LOOKUP): {
480 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
481 cliPrint("Allowed values:");
482 for (uint32_t i = 0; i < tableEntry->valueCount ; i++) {
483 if (i > 0)
484 cliPrint(",");
485 cliPrintf(" %s", tableEntry->values[i]);
487 cliPrintLinefeed();
489 break;
490 case (MODE_ARRAY): {
491 cliPrintLinef("Array length: %d", var->config.array.length);
494 break;
498 static void cliSetVar(const clivalue_t *var, const int16_t value)
500 void *ptr = cliGetValuePointer(var);
502 switch (var->type & VALUE_TYPE_MASK) {
503 case VAR_UINT8:
504 *(uint8_t *)ptr = value;
505 break;
507 case VAR_INT8:
508 *(int8_t *)ptr = value;
509 break;
511 case VAR_UINT16:
512 case VAR_INT16:
513 *(int16_t *)ptr = value;
514 break;
518 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
519 static void cliRepeat(char ch, uint8_t len)
521 for (int i = 0; i < len; i++) {
522 bufWriterAppend(cliWriter, ch);
524 cliPrintLinefeed();
526 #endif
528 static void cliPrompt(void)
530 cliPrint("\r\n# ");
533 static void cliShowParseError(void)
535 cliPrintLine("Parse error");
538 static void cliShowArgumentRangeError(char *name, int min, int max)
540 cliPrintLinef("%s not between %d and %d", name, min, max);
543 static const char *nextArg(const char *currentArg)
545 const char *ptr = strchr(currentArg, ' ');
546 while (ptr && *ptr == ' ') {
547 ptr++;
550 return ptr;
553 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
555 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
556 ptr = nextArg(ptr);
557 if (ptr) {
558 int val = atoi(ptr);
559 val = CHANNEL_VALUE_TO_STEP(val);
560 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
561 if (argIndex == 0) {
562 range->startStep = val;
563 } else {
564 range->endStep = val;
566 (*validArgumentCount)++;
571 return ptr;
574 // Check if a string's length is zero
575 static bool isEmpty(const char *string)
577 return (string == NULL || *string == '\0') ? true : false;
580 static void printRxFailsafe(uint8_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs)
582 // print out rxConfig failsafe settings
583 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
584 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
585 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
586 const bool equalsDefault = channelFailsafeConfig->mode == defaultChannelFailsafeConfig->mode
587 && channelFailsafeConfig->step == defaultChannelFailsafeConfig->step;
588 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
589 if (requireValue) {
590 const char *format = "rxfail %u %c %d";
591 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
592 channel,
593 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
594 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
596 cliDumpPrintLinef(dumpMask, equalsDefault, format,
597 channel,
598 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
599 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
601 } else {
602 const char *format = "rxfail %u %c";
603 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
604 channel,
605 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
607 cliDumpPrintLinef(dumpMask, equalsDefault, format,
608 channel,
609 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
615 static void cliRxFailsafe(char *cmdline)
617 uint8_t channel;
618 char buf[3];
620 if (isEmpty(cmdline)) {
621 // print out rxConfig failsafe settings
622 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
623 cliRxFailsafe(itoa(channel, buf, 10));
625 } else {
626 const char *ptr = cmdline;
627 channel = atoi(ptr++);
628 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
630 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
632 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
633 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
634 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
636 ptr = nextArg(ptr);
637 if (ptr) {
638 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
639 if (p) {
640 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
641 mode = rxFailsafeModesTable[type][requestedMode];
642 } else {
643 mode = RX_FAILSAFE_MODE_INVALID;
645 if (mode == RX_FAILSAFE_MODE_INVALID) {
646 cliShowParseError();
647 return;
650 requireValue = mode == RX_FAILSAFE_MODE_SET;
652 ptr = nextArg(ptr);
653 if (ptr) {
654 if (!requireValue) {
655 cliShowParseError();
656 return;
658 uint16_t value = atoi(ptr);
659 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
660 if (value > MAX_RXFAIL_RANGE_STEP) {
661 cliPrintLine("Value out of range");
662 return;
665 channelFailsafeConfig->step = value;
666 } else if (requireValue) {
667 cliShowParseError();
668 return;
670 channelFailsafeConfig->mode = mode;
673 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
675 // double use of cliPrintf below
676 // 1. acknowledge interpretation on command,
677 // 2. query current setting on single item,
679 if (requireValue) {
680 cliPrintLinef("rxfail %u %c %d",
681 channel,
682 modeCharacter,
683 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
685 } else {
686 cliPrintLinef("rxfail %u %c",
687 channel,
688 modeCharacter
691 } else {
692 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
697 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
699 const char *format = "aux %u %u %u %u %u %u";
700 // print out aux channel settings
701 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
702 const modeActivationCondition_t *mac = &modeActivationConditions[i];
703 bool equalsDefault = false;
704 if (defaultModeActivationConditions) {
705 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
706 equalsDefault = mac->modeId == macDefault->modeId
707 && mac->auxChannelIndex == macDefault->auxChannelIndex
708 && mac->range.startStep == macDefault->range.startStep
709 && mac->range.endStep == macDefault->range.endStep
710 && mac->modeLogic == macDefault->modeLogic;
711 const box_t *box = findBoxByBoxId(macDefault->modeId);
712 if (box) {
713 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
715 box->permanentId,
716 macDefault->auxChannelIndex,
717 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
718 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
719 macDefault->modeLogic
723 const box_t *box = findBoxByBoxId(mac->modeId);
724 if (box) {
725 cliDumpPrintLinef(dumpMask, equalsDefault, format,
727 box->permanentId,
728 mac->auxChannelIndex,
729 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
730 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
731 mac->modeLogic
737 static void cliAux(char *cmdline)
739 int i, val = 0;
740 const char *ptr;
742 if (isEmpty(cmdline)) {
743 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
744 } else {
745 ptr = cmdline;
746 i = atoi(ptr++);
747 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
748 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
749 uint8_t validArgumentCount = 0;
750 ptr = nextArg(ptr);
751 if (ptr) {
752 val = atoi(ptr);
753 const box_t *box = findBoxByPermanentId(val);
754 if (box) {
755 mac->modeId = box->boxId;
756 validArgumentCount++;
759 ptr = nextArg(ptr);
760 if (ptr) {
761 val = atoi(ptr);
762 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
763 mac->auxChannelIndex = val;
764 validArgumentCount++;
767 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
768 ptr = nextArg(ptr);
769 if (ptr) {
770 val = atoi(ptr);
771 if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
772 mac->modeLogic = val;
773 validArgumentCount++;
776 if (validArgumentCount == 4) { // for backwards compatibility
777 mac->modeLogic = MODELOGIC_OR;
778 } else if (validArgumentCount != 5) {
779 memset(mac, 0, sizeof(modeActivationCondition_t));
781 cliPrintLinef( "aux %u %u %u %u %u %u",
783 mac->modeId,
784 mac->auxChannelIndex,
785 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
786 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
787 mac->modeLogic
789 } else {
790 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
795 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
797 const char *format = "serial %d %d %ld %ld %ld %ld";
798 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
799 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
800 continue;
802 bool equalsDefault = false;
803 if (serialConfigDefault) {
804 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
805 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
806 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
807 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
808 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
809 && serialConfig->portConfigs[i].blackbox_baudrateIndex == serialConfigDefault->portConfigs[i].blackbox_baudrateIndex;
810 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
811 serialConfigDefault->portConfigs[i].identifier,
812 serialConfigDefault->portConfigs[i].functionMask,
813 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
814 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
815 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
816 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
819 cliDumpPrintLinef(dumpMask, equalsDefault, format,
820 serialConfig->portConfigs[i].identifier,
821 serialConfig->portConfigs[i].functionMask,
822 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
823 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
824 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
825 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
830 static void cliSerial(char *cmdline)
832 if (isEmpty(cmdline)) {
833 printSerial(DUMP_MASTER, serialConfig(), NULL);
834 return;
836 serialPortConfig_t portConfig;
837 memset(&portConfig, 0 , sizeof(portConfig));
839 serialPortConfig_t *currentConfig;
841 uint8_t validArgumentCount = 0;
843 const char *ptr = cmdline;
845 int val = atoi(ptr++);
846 currentConfig = serialFindPortConfiguration(val);
847 if (currentConfig) {
848 portConfig.identifier = val;
849 validArgumentCount++;
852 ptr = nextArg(ptr);
853 if (ptr) {
854 val = atoi(ptr);
855 portConfig.functionMask = val & 0xFFFF;
856 validArgumentCount++;
859 for (int i = 0; i < 4; i ++) {
860 ptr = nextArg(ptr);
861 if (!ptr) {
862 break;
865 val = atoi(ptr);
867 uint8_t baudRateIndex = lookupBaudRateIndex(val);
868 if (baudRates[baudRateIndex] != (uint32_t) val) {
869 break;
872 switch (i) {
873 case 0:
874 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
875 continue;
877 portConfig.msp_baudrateIndex = baudRateIndex;
878 break;
879 case 1:
880 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
881 continue;
883 portConfig.gps_baudrateIndex = baudRateIndex;
884 break;
885 case 2:
886 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
887 continue;
889 portConfig.telemetry_baudrateIndex = baudRateIndex;
890 break;
891 case 3:
892 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
893 continue;
895 portConfig.blackbox_baudrateIndex = baudRateIndex;
896 break;
899 validArgumentCount++;
902 if (validArgumentCount < 6) {
903 cliShowParseError();
904 return;
907 memcpy(currentConfig, &portConfig, sizeof(portConfig));
910 #ifndef SKIP_SERIAL_PASSTHROUGH
911 static void cliSerialPassthrough(char *cmdline)
913 if (isEmpty(cmdline)) {
914 cliShowParseError();
915 return;
918 int id = -1;
919 uint32_t baud = 0;
920 unsigned mode = 0;
921 char *saveptr;
922 char* tok = strtok_r(cmdline, " ", &saveptr);
923 int index = 0;
925 while (tok != NULL) {
926 switch (index) {
927 case 0:
928 id = atoi(tok);
929 break;
930 case 1:
931 baud = atoi(tok);
932 break;
933 case 2:
934 if (strstr(tok, "rx") || strstr(tok, "RX"))
935 mode |= MODE_RX;
936 if (strstr(tok, "tx") || strstr(tok, "TX"))
937 mode |= MODE_TX;
938 break;
940 index++;
941 tok = strtok_r(NULL, " ", &saveptr);
944 cliPrintf("Port %d ", id);
945 serialPort_t *passThroughPort;
946 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
947 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
948 if (!baud) {
949 cliPrintLine("closed, specify baud.");
950 return;
952 if (!mode)
953 mode = MODE_RXTX;
955 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
956 baud, mode,
957 SERIAL_NOT_INVERTED);
958 if (!passThroughPort) {
959 cliPrintLine("could not be opened.");
960 return;
962 cliPrintf("opened, baud = %d.\r\n", baud);
963 } else {
964 passThroughPort = passThroughPortUsage->serialPort;
965 // If the user supplied a mode, override the port's mode, otherwise
966 // leave the mode unchanged. serialPassthrough() handles one-way ports.
967 cliPrintLine("already open.");
968 if (mode && passThroughPort->mode != mode) {
969 cliPrintf("mode changed from %d to %d.\r\n",
970 passThroughPort->mode, mode);
971 serialSetMode(passThroughPort, mode);
973 // If this port has a rx callback associated we need to remove it now.
974 // Otherwise no data will be pushed in the serial port buffer!
975 if (passThroughPort->rxCallback) {
976 passThroughPort->rxCallback = 0;
980 cliPrintLine("Forwarding, power cycle to exit.");
982 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
984 #endif
986 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
988 const char *format = "adjrange %u %u %u %u %u %u %u";
989 // print out adjustment ranges channel settings
990 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
991 const adjustmentRange_t *ar = &adjustmentRanges[i];
992 bool equalsDefault = false;
993 if (defaultAdjustmentRanges) {
994 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
995 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
996 && ar->range.startStep == arDefault->range.startStep
997 && ar->range.endStep == arDefault->range.endStep
998 && ar->adjustmentFunction == arDefault->adjustmentFunction
999 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
1000 && ar->adjustmentIndex == arDefault->adjustmentIndex;
1001 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1003 arDefault->adjustmentIndex,
1004 arDefault->auxChannelIndex,
1005 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1006 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1007 arDefault->adjustmentFunction,
1008 arDefault->auxSwitchChannelIndex
1011 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1013 ar->adjustmentIndex,
1014 ar->auxChannelIndex,
1015 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1016 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1017 ar->adjustmentFunction,
1018 ar->auxSwitchChannelIndex
1023 static void cliAdjustmentRange(char *cmdline)
1025 int i, val = 0;
1026 const char *ptr;
1028 if (isEmpty(cmdline)) {
1029 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
1030 } else {
1031 ptr = cmdline;
1032 i = atoi(ptr++);
1033 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1034 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1035 uint8_t validArgumentCount = 0;
1037 ptr = nextArg(ptr);
1038 if (ptr) {
1039 val = atoi(ptr);
1040 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1041 ar->adjustmentIndex = val;
1042 validArgumentCount++;
1045 ptr = nextArg(ptr);
1046 if (ptr) {
1047 val = atoi(ptr);
1048 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1049 ar->auxChannelIndex = val;
1050 validArgumentCount++;
1054 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1056 ptr = nextArg(ptr);
1057 if (ptr) {
1058 val = atoi(ptr);
1059 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1060 ar->adjustmentFunction = val;
1061 validArgumentCount++;
1064 ptr = nextArg(ptr);
1065 if (ptr) {
1066 val = atoi(ptr);
1067 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1068 ar->auxSwitchChannelIndex = val;
1069 validArgumentCount++;
1073 if (validArgumentCount != 6) {
1074 memset(ar, 0, sizeof(adjustmentRange_t));
1075 cliShowParseError();
1077 } else {
1078 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1083 #ifndef USE_QUAD_MIXER_ONLY
1084 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer)
1086 const char *format = "mmix %d %s %s %s %s";
1087 char buf0[FTOA_BUFFER_LENGTH];
1088 char buf1[FTOA_BUFFER_LENGTH];
1089 char buf2[FTOA_BUFFER_LENGTH];
1090 char buf3[FTOA_BUFFER_LENGTH];
1091 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1092 if (customMotorMixer[i].throttle == 0.0f)
1093 break;
1094 const float thr = customMotorMixer[i].throttle;
1095 const float roll = customMotorMixer[i].roll;
1096 const float pitch = customMotorMixer[i].pitch;
1097 const float yaw = customMotorMixer[i].yaw;
1098 bool equalsDefault = false;
1099 if (defaultCustomMotorMixer) {
1100 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1101 const float rollDefault = defaultCustomMotorMixer[i].roll;
1102 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1103 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1104 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1106 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1108 ftoa(thrDefault, buf0),
1109 ftoa(rollDefault, buf1),
1110 ftoa(pitchDefault, buf2),
1111 ftoa(yawDefault, buf3));
1113 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1115 ftoa(thr, buf0),
1116 ftoa(roll, buf1),
1117 ftoa(pitch, buf2),
1118 ftoa(yaw, buf3));
1121 #endif // USE_QUAD_MIXER_ONLY
1123 static void cliMotorMix(char *cmdline)
1125 #ifdef USE_QUAD_MIXER_ONLY
1126 UNUSED(cmdline);
1127 #else
1128 int check = 0;
1129 uint8_t len;
1130 const char *ptr;
1132 if (isEmpty(cmdline)) {
1133 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1134 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1135 // erase custom mixer
1136 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1137 customMotorMixerMutable(i)->throttle = 0.0f;
1139 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1140 ptr = nextArg(cmdline);
1141 if (ptr) {
1142 len = strlen(ptr);
1143 for (uint32_t i = 0; ; i++) {
1144 if (mixerNames[i] == NULL) {
1145 cliPrintLine("Invalid name");
1146 break;
1148 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1149 mixerLoadMix(i, customMotorMixerMutable(0));
1150 cliPrintLinef("Loaded %s", mixerNames[i]);
1151 cliMotorMix("");
1152 break;
1156 } else {
1157 ptr = cmdline;
1158 uint32_t i = atoi(ptr); // get motor number
1159 if (i < MAX_SUPPORTED_MOTORS) {
1160 ptr = nextArg(ptr);
1161 if (ptr) {
1162 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1163 check++;
1165 ptr = nextArg(ptr);
1166 if (ptr) {
1167 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1168 check++;
1170 ptr = nextArg(ptr);
1171 if (ptr) {
1172 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1173 check++;
1175 ptr = nextArg(ptr);
1176 if (ptr) {
1177 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1178 check++;
1180 if (check != 4) {
1181 cliShowParseError();
1182 } else {
1183 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1185 } else {
1186 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1189 #endif
1192 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1194 const char *format = "rxrange %u %u %u";
1195 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1196 bool equalsDefault = false;
1197 if (defaultChannelRangeConfigs) {
1198 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1199 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1200 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1202 defaultChannelRangeConfigs[i].min,
1203 defaultChannelRangeConfigs[i].max
1206 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1208 channelRangeConfigs[i].min,
1209 channelRangeConfigs[i].max
1214 static void cliRxRange(char *cmdline)
1216 int i, validArgumentCount = 0;
1217 const char *ptr;
1219 if (isEmpty(cmdline)) {
1220 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1221 } else if (strcasecmp(cmdline, "reset") == 0) {
1222 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1223 } else {
1224 ptr = cmdline;
1225 i = atoi(ptr);
1226 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1227 int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
1229 ptr = nextArg(ptr);
1230 if (ptr) {
1231 rangeMin = atoi(ptr);
1232 validArgumentCount++;
1235 ptr = nextArg(ptr);
1236 if (ptr) {
1237 rangeMax = atoi(ptr);
1238 validArgumentCount++;
1241 if (validArgumentCount != 2) {
1242 cliShowParseError();
1243 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1244 cliShowParseError();
1245 } else {
1246 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1247 channelRangeConfig->min = rangeMin;
1248 channelRangeConfig->max = rangeMax;
1250 } else {
1251 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1256 #ifdef USE_LED_STRIP
1257 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1259 const char *format = "led %u %s";
1260 char ledConfigBuffer[20];
1261 char ledConfigDefaultBuffer[20];
1262 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1263 ledConfig_t ledConfig = ledConfigs[i];
1264 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1265 bool equalsDefault = false;
1266 if (defaultLedConfigs) {
1267 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1268 equalsDefault = ledConfig == ledConfigDefault;
1269 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1270 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1272 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1276 static void cliLed(char *cmdline)
1278 int i;
1279 const char *ptr;
1281 if (isEmpty(cmdline)) {
1282 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1283 } else {
1284 ptr = cmdline;
1285 i = atoi(ptr);
1286 if (i < LED_MAX_STRIP_LENGTH) {
1287 ptr = nextArg(cmdline);
1288 if (!parseLedStripConfig(i, ptr)) {
1289 cliShowParseError();
1291 } else {
1292 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1297 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1299 const char *format = "color %u %d,%u,%u";
1300 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1301 const hsvColor_t *color = &colors[i];
1302 bool equalsDefault = false;
1303 if (defaultColors) {
1304 const hsvColor_t *colorDefault = &defaultColors[i];
1305 equalsDefault = color->h == colorDefault->h
1306 && color->s == colorDefault->s
1307 && color->v == colorDefault->v;
1308 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1310 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1314 static void cliColor(char *cmdline)
1316 if (isEmpty(cmdline)) {
1317 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1318 } else {
1319 const char *ptr = cmdline;
1320 const int i = atoi(ptr);
1321 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1322 ptr = nextArg(cmdline);
1323 if (!parseColor(i, ptr)) {
1324 cliShowParseError();
1326 } else {
1327 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1332 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1334 const char *format = "mode_color %u %u %u";
1335 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1336 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1337 int colorIndex = ledStripConfig->modeColors[i].color[j];
1338 bool equalsDefault = false;
1339 if (defaultLedStripConfig) {
1340 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1341 equalsDefault = colorIndex == colorIndexDefault;
1342 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1344 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1348 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1349 const int colorIndex = ledStripConfig->specialColors.color[j];
1350 bool equalsDefault = false;
1351 if (defaultLedStripConfig) {
1352 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1353 equalsDefault = colorIndex == colorIndexDefault;
1354 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1356 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1359 const int ledStripAuxChannel = ledStripConfig->ledstrip_aux_channel;
1360 bool equalsDefault = false;
1361 if (defaultLedStripConfig) {
1362 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1363 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1364 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1366 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1369 static void cliModeColor(char *cmdline)
1371 if (isEmpty(cmdline)) {
1372 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1373 } else {
1374 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1375 int args[ARGS_COUNT];
1376 int argNo = 0;
1377 char *saveptr;
1378 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1379 while (ptr && argNo < ARGS_COUNT) {
1380 args[argNo++] = atoi(ptr);
1381 ptr = strtok_r(NULL, " ", &saveptr);
1384 if (ptr != NULL || argNo != ARGS_COUNT) {
1385 cliShowParseError();
1386 return;
1389 int modeIdx = args[MODE];
1390 int funIdx = args[FUNCTION];
1391 int color = args[COLOR];
1392 if (!setModeColor(modeIdx, funIdx, color)) {
1393 cliShowParseError();
1394 return;
1396 // values are validated
1397 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1400 #endif
1402 #ifdef USE_SERVOS
1403 static void printServo(uint8_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams)
1405 // print out servo settings
1406 const char *format = "servo %u %d %d %d %d %d";
1407 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1408 const servoParam_t *servoConf = &servoParams[i];
1409 bool equalsDefault = false;
1410 if (defaultServoParams) {
1411 const servoParam_t *defaultServoConf = &defaultServoParams[i];
1412 equalsDefault = servoConf->min == defaultServoConf->min
1413 && servoConf->max == defaultServoConf->max
1414 && servoConf->middle == defaultServoConf->middle
1415 && servoConf->rate == defaultServoConf->rate
1416 && servoConf->forwardFromChannel == defaultServoConf->forwardFromChannel;
1417 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1419 defaultServoConf->min,
1420 defaultServoConf->max,
1421 defaultServoConf->middle,
1422 defaultServoConf->rate,
1423 defaultServoConf->forwardFromChannel
1426 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1428 servoConf->min,
1429 servoConf->max,
1430 servoConf->middle,
1431 servoConf->rate,
1432 servoConf->forwardFromChannel
1435 // print servo directions
1436 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1437 const char *format = "smix reverse %d %d r";
1438 const servoParam_t *servoConf = &servoParams[i];
1439 const servoParam_t *servoConfDefault = &defaultServoParams[i];
1440 if (defaultServoParams) {
1441 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
1442 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1443 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
1444 if (servoConfDefault->reversedSources & (1 << channel)) {
1445 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
1447 if (servoConf->reversedSources & (1 << channel)) {
1448 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
1451 } else {
1452 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1453 if (servoConf->reversedSources & (1 << channel)) {
1454 cliDumpPrintLinef(dumpMask, true, format, i , channel);
1461 static void cliServo(char *cmdline)
1463 enum { SERVO_ARGUMENT_COUNT = 6 };
1464 int16_t arguments[SERVO_ARGUMENT_COUNT];
1466 servoParam_t *servo;
1468 int i;
1469 char *ptr;
1471 if (isEmpty(cmdline)) {
1472 printServo(DUMP_MASTER, servoParams(0), NULL);
1473 } else {
1474 int validArgumentCount = 0;
1476 ptr = cmdline;
1478 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1480 // If command line doesn't fit the format, don't modify the config
1481 while (*ptr) {
1482 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1483 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1484 cliShowParseError();
1485 return;
1488 arguments[validArgumentCount++] = atoi(ptr);
1490 do {
1491 ptr++;
1492 } while (*ptr >= '0' && *ptr <= '9');
1493 } else if (*ptr == ' ') {
1494 ptr++;
1495 } else {
1496 cliShowParseError();
1497 return;
1501 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
1503 i = arguments[INDEX];
1505 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1506 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1507 cliShowParseError();
1508 return;
1511 servo = servoParamsMutable(i);
1513 if (
1514 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
1515 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
1516 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1517 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1518 arguments[RATE] < -100 || arguments[RATE] > 100 ||
1519 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1521 cliShowParseError();
1522 return;
1525 servo->min = arguments[MIN];
1526 servo->max = arguments[MAX];
1527 servo->middle = arguments[MIDDLE];
1528 servo->rate = arguments[RATE];
1529 servo->forwardFromChannel = arguments[FORWARD];
1532 #endif
1534 #ifdef USE_SERVOS
1535 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1537 const char *format = "smix %d %d %d %d %d %d %d %d";
1538 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1539 const servoMixer_t customServoMixer = customServoMixers[i];
1540 if (customServoMixer.rate == 0) {
1541 break;
1544 bool equalsDefault = false;
1545 if (defaultCustomServoMixers) {
1546 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1547 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1548 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1549 && customServoMixer.rate == customServoMixerDefault.rate
1550 && customServoMixer.speed == customServoMixerDefault.speed
1551 && customServoMixer.min == customServoMixerDefault.min
1552 && customServoMixer.max == customServoMixerDefault.max
1553 && customServoMixer.box == customServoMixerDefault.box;
1555 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1557 customServoMixerDefault.targetChannel,
1558 customServoMixerDefault.inputSource,
1559 customServoMixerDefault.rate,
1560 customServoMixerDefault.speed,
1561 customServoMixerDefault.min,
1562 customServoMixerDefault.max,
1563 customServoMixerDefault.box
1566 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1568 customServoMixer.targetChannel,
1569 customServoMixer.inputSource,
1570 customServoMixer.rate,
1571 customServoMixer.speed,
1572 customServoMixer.min,
1573 customServoMixer.max,
1574 customServoMixer.box
1578 cliPrintLinefeed();
1581 static void cliServoMix(char *cmdline)
1583 int args[8], check = 0;
1584 int len = strlen(cmdline);
1586 if (len == 0) {
1587 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
1588 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1589 // erase custom mixer
1590 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1591 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1592 servoParamsMutable(i)->reversedSources = 0;
1594 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1595 const char *ptr = nextArg(cmdline);
1596 if (ptr) {
1597 len = strlen(ptr);
1598 for (uint32_t i = 0; ; i++) {
1599 if (mixerNames[i] == NULL) {
1600 cliPrintLine("Invalid name");
1601 break;
1603 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1604 servoMixerLoadMix(i);
1605 cliPrintLinef("Loaded %s", mixerNames[i]);
1606 cliServoMix("");
1607 break;
1611 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
1612 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
1613 char *ptr = strchr(cmdline, ' ');
1615 len = strlen(ptr);
1616 if (len == 0) {
1617 cliPrintf("s");
1618 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1619 cliPrintf("\ti%d", inputSource);
1620 cliPrintLinefeed();
1622 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
1623 cliPrintf("%d", servoIndex);
1624 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1625 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
1626 cliPrintLinefeed();
1628 return;
1631 char *saveptr;
1632 ptr = strtok_r(ptr, " ", &saveptr);
1633 while (ptr != NULL && check < ARGS_COUNT - 1) {
1634 args[check++] = atoi(ptr);
1635 ptr = strtok_r(NULL, " ", &saveptr);
1638 if (ptr == NULL || check != ARGS_COUNT - 1) {
1639 cliShowParseError();
1640 return;
1643 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
1644 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
1645 && (*ptr == 'r' || *ptr == 'n')) {
1646 if (*ptr == 'r')
1647 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
1648 else
1649 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
1650 } else
1651 cliShowParseError();
1653 cliServoMix("reverse");
1654 } else {
1655 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
1656 char *saveptr;
1657 char *ptr = strtok_r(cmdline, " ", &saveptr);
1658 while (ptr != NULL && check < ARGS_COUNT) {
1659 args[check++] = atoi(ptr);
1660 ptr = strtok_r(NULL, " ", &saveptr);
1663 if (ptr != NULL || check != ARGS_COUNT) {
1664 cliShowParseError();
1665 return;
1668 int32_t i = args[RULE];
1669 if (i >= 0 && i < MAX_SERVO_RULES &&
1670 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1671 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1672 args[RATE] >= -100 && args[RATE] <= 100 &&
1673 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1674 args[MIN] >= 0 && args[MIN] <= 100 &&
1675 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
1676 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
1677 customServoMixersMutable(i)->targetChannel = args[TARGET];
1678 customServoMixersMutable(i)->inputSource = args[INPUT];
1679 customServoMixersMutable(i)->rate = args[RATE];
1680 customServoMixersMutable(i)->speed = args[SPEED];
1681 customServoMixersMutable(i)->min = args[MIN];
1682 customServoMixersMutable(i)->max = args[MAX];
1683 customServoMixersMutable(i)->box = args[BOX];
1684 cliServoMix("");
1685 } else {
1686 cliShowParseError();
1690 #endif
1692 #ifdef USE_SDCARD
1694 static void cliWriteBytes(const uint8_t *buffer, int count)
1696 while (count > 0) {
1697 cliWrite(*buffer);
1698 buffer++;
1699 count--;
1703 static void cliSdInfo(char *cmdline)
1705 UNUSED(cmdline);
1707 cliPrint("SD card: ");
1709 if (!sdcard_isInserted()) {
1710 cliPrintLine("None inserted");
1711 return;
1714 if (!sdcard_isInitialized()) {
1715 cliPrintLine("Startup failed");
1716 return;
1719 const sdcardMetadata_t *metadata = sdcard_getMetadata();
1721 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1722 metadata->manufacturerID,
1723 metadata->numBlocks / 2, /* One block is half a kB */
1724 metadata->productionMonth,
1725 metadata->productionYear,
1726 metadata->productRevisionMajor,
1727 metadata->productRevisionMinor
1730 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
1732 cliPrint("'\r\n" "Filesystem: ");
1734 switch (afatfs_getFilesystemState()) {
1735 case AFATFS_FILESYSTEM_STATE_READY:
1736 cliPrint("Ready");
1737 break;
1738 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
1739 cliPrint("Initializing");
1740 break;
1741 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
1742 case AFATFS_FILESYSTEM_STATE_FATAL:
1743 cliPrint("Fatal");
1745 switch (afatfs_getLastError()) {
1746 case AFATFS_ERROR_BAD_MBR:
1747 cliPrint(" - no FAT MBR partitions");
1748 break;
1749 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
1750 cliPrint(" - bad FAT header");
1751 break;
1752 case AFATFS_ERROR_GENERIC:
1753 case AFATFS_ERROR_NONE:
1754 ; // Nothing more detailed to print
1755 break;
1757 break;
1759 cliPrintLinefeed();
1762 #endif
1764 #ifdef USE_FLASHFS
1766 static void cliFlashInfo(char *cmdline)
1768 const flashGeometry_t *layout = flashfsGetGeometry();
1770 UNUSED(cmdline);
1772 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
1773 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
1777 static void cliFlashErase(char *cmdline)
1779 UNUSED(cmdline);
1781 #ifndef MINIMAL_CLI
1782 uint32_t i = 0;
1783 cliPrintLine("Erasing, please wait ... ");
1784 #else
1785 cliPrintLine("Erasing,");
1786 #endif
1788 bufWriterFlush(cliWriter);
1789 flashfsEraseCompletely();
1791 while (!flashfsIsReady()) {
1792 #ifndef MINIMAL_CLI
1793 cliPrintf(".");
1794 if (i++ > 120) {
1795 i=0;
1796 cliPrintLinefeed();
1799 bufWriterFlush(cliWriter);
1800 #endif
1801 delay(100);
1803 beeper(BEEPER_BLACKBOX_ERASE);
1804 cliPrintLinefeed();
1805 cliPrintLine("Done.");
1808 #ifdef USE_FLASH_TOOLS
1810 static void cliFlashWrite(char *cmdline)
1812 const uint32_t address = atoi(cmdline);
1813 const char *text = strchr(cmdline, ' ');
1815 if (!text) {
1816 cliShowParseError();
1817 } else {
1818 flashfsSeekAbs(address);
1819 flashfsWrite((uint8_t*)text, strlen(text), true);
1820 flashfsFlushSync();
1822 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
1826 static void cliFlashRead(char *cmdline)
1828 uint32_t address = atoi(cmdline);
1830 const char *nextArg = strchr(cmdline, ' ');
1832 if (!nextArg) {
1833 cliShowParseError();
1834 } else {
1835 uint32_t length = atoi(nextArg);
1837 cliPrintLinef("Reading %u bytes at %u:", length, address);
1839 uint8_t buffer[32];
1840 while (length > 0) {
1841 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
1843 for (int i = 0; i < bytesRead; i++) {
1844 cliWrite(buffer[i]);
1847 length -= bytesRead;
1848 address += bytesRead;
1850 if (bytesRead == 0) {
1851 //Assume we reached the end of the volume or something fatal happened
1852 break;
1855 cliPrintLinefeed();
1859 #endif
1860 #endif
1862 #ifdef USE_VTX_CONTROL
1863 static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault)
1865 // print out vtx channel settings
1866 const char *format = "vtx %u %u %u %u %u %u";
1867 bool equalsDefault = false;
1868 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
1869 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
1870 if (vtxConfigDefault) {
1871 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
1872 equalsDefault = cac->auxChannelIndex == cacDefault->auxChannelIndex
1873 && cac->band == cacDefault->band
1874 && cac->channel == cacDefault->channel
1875 && cac->range.startStep == cacDefault->range.startStep
1876 && cac->range.endStep == cacDefault->range.endStep;
1877 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1879 cacDefault->auxChannelIndex,
1880 cacDefault->band,
1881 cacDefault->channel,
1882 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
1883 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
1886 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1888 cac->auxChannelIndex,
1889 cac->band,
1890 cac->channel,
1891 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
1892 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
1897 static void cliVtx(char *cmdline)
1899 int i, val = 0;
1900 const char *ptr;
1902 if (isEmpty(cmdline)) {
1903 printVtx(DUMP_MASTER, vtxConfig(), NULL);
1904 } else {
1905 ptr = cmdline;
1906 i = atoi(ptr++);
1907 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
1908 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
1909 uint8_t validArgumentCount = 0;
1910 ptr = nextArg(ptr);
1911 if (ptr) {
1912 val = atoi(ptr);
1913 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1914 cac->auxChannelIndex = val;
1915 validArgumentCount++;
1918 ptr = nextArg(ptr);
1919 if (ptr) {
1920 val = atoi(ptr);
1921 // FIXME Use VTX API to get min/max
1922 if (val >= VTX_SETTINGS_MIN_BAND && val <= VTX_SETTINGS_MAX_BAND) {
1923 cac->band = val;
1924 validArgumentCount++;
1927 ptr = nextArg(ptr);
1928 if (ptr) {
1929 val = atoi(ptr);
1930 // FIXME Use VTX API to get min/max
1931 if (val >= VTX_SETTINGS_MIN_CHANNEL && val <= VTX_SETTINGS_MAX_CHANNEL) {
1932 cac->channel = val;
1933 validArgumentCount++;
1936 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
1938 if (validArgumentCount != 5) {
1939 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
1941 } else {
1942 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
1947 #endif // VTX_CONTROL
1949 static void printName(uint8_t dumpMask, const pilotConfig_t *pilotConfig)
1951 const bool equalsDefault = strlen(pilotConfig->name) == 0;
1952 cliDumpPrintLinef(dumpMask, equalsDefault, "name %s", equalsDefault ? emptyName : pilotConfig->name);
1955 static void cliName(char *cmdline)
1957 const unsigned int len = strlen(cmdline);
1958 if (len > 0) {
1959 memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
1960 if (strncmp(cmdline, emptyName, len)) {
1961 strncpy(pilotConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
1964 printName(DUMP_MASTER, pilotConfig());
1967 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
1969 const uint32_t mask = featureConfig->enabledFeatures;
1970 const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
1971 for (uint32_t i = 0; featureNames[i]; i++) { // disabled features first
1972 if (strcmp(featureNames[i], emptryString) != 0) { //Skip unused
1973 const char *format = "feature -%s";
1974 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
1975 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
1978 for (uint32_t i = 0; featureNames[i]; i++) { // enabled features
1979 if (strcmp(featureNames[i], emptryString) != 0) { //Skip unused
1980 const char *format = "feature %s";
1981 if (defaultMask & (1 << i)) {
1982 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
1984 if (mask & (1 << i)) {
1985 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
1991 static void cliFeature(char *cmdline)
1993 uint32_t len = strlen(cmdline);
1994 uint32_t mask = featureMask();
1996 if (len == 0) {
1997 cliPrint("Enabled: ");
1998 for (uint32_t i = 0; ; i++) {
1999 if (featureNames[i] == NULL)
2000 break;
2001 if (mask & (1 << i))
2002 cliPrintf("%s ", featureNames[i]);
2004 cliPrintLinefeed();
2005 } else if (strncasecmp(cmdline, "list", len) == 0) {
2006 cliPrint("Available:");
2007 for (uint32_t i = 0; ; i++) {
2008 if (featureNames[i] == NULL)
2009 break;
2010 if (strcmp(featureNames[i], emptryString) != 0) //Skip unused
2011 cliPrintf(" %s", featureNames[i]);
2013 cliPrintLinefeed();
2014 return;
2015 } else {
2016 bool remove = false;
2017 if (cmdline[0] == '-') {
2018 // remove feature
2019 remove = true;
2020 cmdline++; // skip over -
2021 len--;
2024 for (uint32_t i = 0; ; i++) {
2025 if (featureNames[i] == NULL) {
2026 cliPrintLine("Invalid name");
2027 break;
2030 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
2032 mask = 1 << i;
2033 #ifndef USE_GPS
2034 if (mask & FEATURE_GPS) {
2035 cliPrintLine("unavailable");
2036 break;
2038 #endif
2039 #ifndef USE_RANGEFINDER
2040 if (mask & FEATURE_RANGEFINDER) {
2041 cliPrintLine("unavailable");
2042 break;
2044 #endif
2045 if (remove) {
2046 featureClear(mask);
2047 cliPrint("Disabled");
2048 } else {
2049 featureSet(mask);
2050 cliPrint("Enabled");
2052 cliPrintLinef(" %s", featureNames[i]);
2053 break;
2059 #ifdef BEEPER
2060 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
2062 const uint8_t beeperCount = beeperTableEntryCount();
2063 const uint32_t mask = beeperConfig->beeper_off_flags;
2064 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
2065 for (int32_t i = 0; i < beeperCount - 2; i++) {
2066 const char *formatOff = "beeper -%s";
2067 const char *formatOn = "beeper %s";
2068 const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
2069 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & beeperModeMask, mask & beeperModeMask ? formatOn : formatOff, beeperNameForTableIndex(i));
2070 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & beeperModeMask, mask & beeperModeMask ? formatOff : formatOn, beeperNameForTableIndex(i));
2074 static void cliBeeper(char *cmdline)
2076 uint32_t len = strlen(cmdline);
2077 uint8_t beeperCount = beeperTableEntryCount();
2078 uint32_t mask = getBeeperOffMask();
2080 if (len == 0) {
2081 cliPrintf("Disabled:");
2082 for (int32_t i = 0; ; i++) {
2083 if (i == beeperCount - 2) {
2084 if (mask == 0)
2085 cliPrint(" none");
2086 break;
2089 if (mask & beeperModeMaskForTableIndex(i))
2090 cliPrintf(" %s", beeperNameForTableIndex(i));
2092 cliPrintLinefeed();
2093 } else if (strncasecmp(cmdline, "list", len) == 0) {
2094 cliPrint("Available:");
2095 for (uint32_t i = 0; i < beeperCount; i++)
2096 cliPrintf(" %s", beeperNameForTableIndex(i));
2097 cliPrintLinefeed();
2098 return;
2099 } else {
2100 bool remove = false;
2101 if (cmdline[0] == '-') {
2102 remove = true; // this is for beeper OFF condition
2103 cmdline++;
2104 len--;
2107 for (uint32_t i = 0; ; i++) {
2108 if (i == beeperCount) {
2109 cliPrintLine("Invalid name");
2110 break;
2112 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
2113 if (remove) { // beeper off
2114 if (i == BEEPER_ALL-1)
2115 beeperOffSetAll(beeperCount-2);
2116 else
2117 if (i == BEEPER_PREFERENCE-1)
2118 setBeeperOffMask(getPreferredBeeperOffMask());
2119 else {
2120 beeperOffSet(beeperModeMaskForTableIndex(i));
2122 cliPrint("Disabled");
2124 else { // beeper on
2125 if (i == BEEPER_ALL-1)
2126 beeperOffClearAll();
2127 else
2128 if (i == BEEPER_PREFERENCE-1)
2129 setPreferredBeeperOffMask(getBeeperOffMask());
2130 else {
2131 beeperOffClear(beeperModeMaskForTableIndex(i));
2133 cliPrint("Enabled");
2135 cliPrintLinef(" %s", beeperNameForTableIndex(i));
2136 break;
2141 #endif
2143 void cliFrSkyBind(char *cmdline){
2144 UNUSED(cmdline);
2145 switch (rxConfig()->rx_spi_protocol) {
2146 #ifdef USE_RX_FRSKY_SPI
2147 case RX_SPI_FRSKY_D:
2148 case RX_SPI_FRSKY_X:
2149 frSkySpiBind();
2151 cliPrint("Binding...");
2153 break;
2154 #endif
2155 default:
2156 cliPrint("Not supported.");
2158 break;
2162 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
2164 bool equalsDefault = true;
2165 char buf[16];
2166 char bufDefault[16];
2167 uint32_t i;
2168 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2169 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
2170 if (defaultRxConfig) {
2171 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
2172 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
2175 buf[i] = '\0';
2177 const char *formatMap = "map %s";
2178 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
2179 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
2183 static void cliMap(char *cmdline)
2185 uint32_t i;
2186 char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
2188 uint32_t len = strlen(cmdline);
2189 if (len == RX_MAPPABLE_CHANNEL_COUNT) {
2191 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2192 buf[i] = toupper((unsigned char)cmdline[i]);
2194 buf[i] = '\0';
2196 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2197 buf[i] = toupper((unsigned char)cmdline[i]);
2199 if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
2200 continue;
2202 cliShowParseError();
2203 return;
2205 parseRcChannels(buf, rxConfigMutable());
2206 } else if (len > 0) {
2207 cliShowParseError();
2208 return;
2211 for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
2212 buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
2215 buf[i] = '\0';
2216 cliPrintLinef("map %s", buf);
2219 static char *checkCommand(char *cmdLine, const char *command)
2221 if (!strncasecmp(cmdLine, command, strlen(command)) // command names match
2222 && (isspace((unsigned)cmdLine[strlen(command)]) || cmdLine[strlen(command)] == 0)) {
2223 return cmdLine + strlen(command) + 1;
2224 } else {
2225 return 0;
2229 static void cliRebootEx(bool bootLoader)
2231 cliPrint("\r\nRebooting");
2232 bufWriterFlush(cliWriter);
2233 waitForSerialPortToFinishTransmitting(cliPort);
2234 stopPwmAllMotors();
2235 if (bootLoader) {
2236 systemResetToBootloader();
2237 return;
2239 systemReset();
2242 static void cliReboot(void)
2244 cliRebootEx(false);
2247 static void cliBootloader(char *cmdLine)
2249 UNUSED(cmdLine);
2251 cliPrintHashLine("restarting in bootloader mode");
2252 cliRebootEx(true);
2255 static void cliExit(char *cmdline)
2257 UNUSED(cmdline);
2259 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2260 bufWriterFlush(cliWriter);
2262 *cliBuffer = '\0';
2263 bufferIndex = 0;
2264 cliMode = 0;
2265 // incase a motor was left running during motortest, clear it here
2266 mixerResetDisarmedMotors();
2267 cliReboot();
2269 cliWriter = NULL;
2272 #ifdef USE_GPS
2273 static void cliGpsPassthrough(char *cmdline)
2275 UNUSED(cmdline);
2277 gpsEnablePassthrough(cliPort);
2279 #endif
2281 static int parseOutputIndex(char *pch, bool allowAllEscs) {
2282 int outputIndex = atoi(pch);
2283 if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
2284 tfp_printf("Using output %d.\r\n", outputIndex);
2285 } else if (allowAllEscs && outputIndex == ALL_MOTORS) {
2286 tfp_printf("Using all outputs.\r\n");
2287 } else {
2288 tfp_printf("Invalid output number. Range: 0 %d.\r\n", getMotorCount() - 1);
2290 return -1;
2293 return outputIndex;
2296 #ifdef USE_DSHOT
2298 #define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
2299 #define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
2300 #define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
2302 enum {
2303 ESC_INFO_KISS_V1,
2304 ESC_INFO_KISS_V2,
2305 ESC_INFO_BLHELI32
2308 #define ESC_INFO_VERSION_POSITION 12
2310 void printEscInfo(const uint8_t *escInfoBuffer, uint8_t bytesRead)
2312 bool escInfoReceived = false;
2313 if (bytesRead > ESC_INFO_VERSION_POSITION) {
2314 uint8_t escInfoVersion;
2315 uint8_t frameLength;
2316 if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
2317 escInfoVersion = ESC_INFO_BLHELI32;
2318 frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
2319 } else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
2320 escInfoVersion = ESC_INFO_KISS_V2;
2321 frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
2322 } else {
2323 escInfoVersion = ESC_INFO_KISS_V1;
2324 frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
2327 if (bytesRead == frameLength) {
2328 escInfoReceived = true;
2330 if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
2331 uint8_t firmwareVersion = 0;
2332 uint8_t firmwareSubVersion = 0;
2333 uint8_t escType = 0;
2334 switch (escInfoVersion) {
2335 case ESC_INFO_KISS_V1:
2336 firmwareVersion = escInfoBuffer[12];
2337 firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
2338 escType = (escInfoBuffer[13] & 0xe0) >> 5;
2340 break;
2341 case ESC_INFO_KISS_V2:
2342 firmwareVersion = escInfoBuffer[13];
2343 firmwareSubVersion = escInfoBuffer[14];
2344 escType = escInfoBuffer[15];
2346 break;
2347 case ESC_INFO_BLHELI32:
2348 firmwareVersion = escInfoBuffer[13];
2349 firmwareSubVersion = escInfoBuffer[14];
2350 escType = escInfoBuffer[15];
2352 break;
2355 cliPrint("ESC Type: ");
2356 switch (escInfoVersion) {
2357 case ESC_INFO_KISS_V1:
2358 case ESC_INFO_KISS_V2:
2359 switch (escType) {
2360 case 1:
2361 cliPrintLine("KISS8A");
2363 break;
2364 case 2:
2365 cliPrintLine("KISS16A");
2367 break;
2368 case 3:
2369 cliPrintLine("KISS24A");
2371 break;
2372 case 5:
2373 cliPrintLine("KISS Ultralite");
2375 break;
2376 default:
2377 cliPrintLine("unknown");
2379 break;
2382 break;
2383 case ESC_INFO_BLHELI32:
2385 char *escType = (char *)(escInfoBuffer + 31);
2386 escType[32] = 0;
2387 cliPrintLine(escType);
2390 break;
2393 cliPrint("MCU Serial No: 0x");
2394 for (int i = 0; i < 12; i++) {
2395 if (i && (i % 3 == 0)) {
2396 cliPrint("-");
2398 cliPrintf("%02x", escInfoBuffer[i]);
2400 cliPrintLinefeed();
2402 switch (escInfoVersion) {
2403 case ESC_INFO_KISS_V1:
2404 case ESC_INFO_KISS_V2:
2405 cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
2407 break;
2408 case ESC_INFO_BLHELI32:
2409 cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
2411 break;
2413 if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
2414 cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
2415 cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
2416 if (escInfoVersion == ESC_INFO_BLHELI32) {
2417 uint8_t setting = escInfoBuffer[18];
2418 cliPrint("Low voltage Limit: ");
2419 switch (setting) {
2420 case 0:
2421 cliPrintLine("off");
2423 break;
2424 case 255:
2425 cliPrintLine("unsupported");
2427 break;
2428 default:
2429 cliPrintLinef("%d.%01d", setting / 10, setting % 10);
2431 break;
2434 setting = escInfoBuffer[19];
2435 cliPrint("Current Limit: ");
2436 switch (setting) {
2437 case 0:
2438 cliPrintLine("off");
2440 break;
2441 case 255:
2442 cliPrintLine("unsupported");
2444 break;
2445 default:
2446 cliPrintLinef("%d", setting);
2448 break;
2451 for (int i = 0; i < 4; i++) {
2452 setting = escInfoBuffer[i + 20];
2453 cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
2457 } else {
2458 cliPrintLine("Checksum Error.");
2463 if (!escInfoReceived) {
2464 cliPrintLine("No Info.");
2468 static void executeEscInfoCommand(uint8_t escIndex)
2470 cliPrintLinef("Info for ESC %d:", escIndex);
2472 uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
2474 startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
2476 pwmWriteDshotCommand(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO);
2478 delay(10);
2480 printEscInfo(escInfoBuffer, getNumberEscBytesRead());
2483 static void cliDshotProg(char *cmdline)
2485 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
2486 cliShowParseError();
2488 return;
2491 char *saveptr;
2492 char *pch = strtok_r(cmdline, " ", &saveptr);
2493 int pos = 0;
2494 int escIndex = 0;
2495 bool firstCommand = true;
2496 while (pch != NULL) {
2497 switch (pos) {
2498 case 0:
2499 escIndex = parseOutputIndex(pch, true);
2500 if (escIndex == -1) {
2501 return;
2504 break;
2505 default:
2507 int command = atoi(pch);
2508 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
2509 if (firstCommand) {
2510 pwmDisableMotors();
2512 if (command == DSHOT_CMD_ESC_INFO) {
2513 delay(5); // Wait for potential ESC telemetry transmission to finish
2514 } else {
2515 delay(1);
2518 firstCommand = false;
2521 if (command != DSHOT_CMD_ESC_INFO) {
2522 pwmWriteDshotCommand(escIndex, getMotorCount(), command);
2523 } else {
2524 if (escIndex != ALL_MOTORS) {
2525 executeEscInfoCommand(escIndex);
2526 } else {
2527 for (uint8_t i = 0; i < getMotorCount(); i++) {
2528 executeEscInfoCommand(i);
2533 cliPrintLinef("Command Sent: %d", command);
2535 if (command <= 5) {
2536 delay(20); // wait for sound output to finish
2538 } else {
2539 cliPrintLinef("Invalid command. Range: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
2543 break;
2546 pos++;
2547 pch = strtok_r(NULL, " ", &saveptr);
2550 pwmEnableMotors();
2552 #endif
2554 #ifdef USE_ESCSERIAL
2555 static void cliEscPassthrough(char *cmdline)
2557 if (isEmpty(cmdline)) {
2558 cliShowParseError();
2560 return;
2563 char *saveptr;
2564 char *pch = strtok_r(cmdline, " ", &saveptr);
2565 int pos = 0;
2566 uint8_t mode = 0;
2567 int escIndex = 0;
2568 while (pch != NULL) {
2569 switch (pos) {
2570 case 0:
2571 if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
2572 mode = PROTOCOL_SIMONK;
2573 } else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
2574 mode = PROTOCOL_BLHELI;
2575 } else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
2576 mode = PROTOCOL_KISS;
2577 } else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
2578 mode = PROTOCOL_KISSALL;
2579 } else {
2580 cliShowParseError();
2582 return;
2584 break;
2585 case 1:
2586 escIndex = parseOutputIndex(pch, mode == PROTOCOL_KISS);
2587 if (escIndex == -1) {
2588 return;
2591 break;
2592 default:
2593 cliShowParseError();
2595 return;
2597 break;
2600 pos++;
2601 pch = strtok_r(NULL, " ", &saveptr);
2604 escEnablePassthrough(cliPort, escIndex, mode);
2606 #endif
2608 #ifndef USE_QUAD_MIXER_ONLY
2609 static void cliMixer(char *cmdline)
2611 int len;
2613 len = strlen(cmdline);
2615 if (len == 0) {
2616 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
2617 return;
2618 } else if (strncasecmp(cmdline, "list", len) == 0) {
2619 cliPrint("Available:");
2620 for (uint32_t i = 0; ; i++) {
2621 if (mixerNames[i] == NULL)
2622 break;
2623 cliPrintf(" %s", mixerNames[i]);
2625 cliPrintLinefeed();
2626 return;
2629 for (uint32_t i = 0; ; i++) {
2630 if (mixerNames[i] == NULL) {
2631 cliPrintLine("Invalid name");
2632 return;
2634 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
2635 mixerConfigMutable()->mixerMode = i + 1;
2636 break;
2640 cliMixer("");
2642 #endif
2644 static void cliMotor(char *cmdline)
2646 if (isEmpty(cmdline)) {
2647 cliShowParseError();
2649 return;
2652 int motorIndex = 0;
2653 int motorValue = 0;
2655 char *saveptr;
2656 char *pch = strtok_r(cmdline, " ", &saveptr);
2657 int index = 0;
2658 while (pch != NULL) {
2659 switch (index) {
2660 case 0:
2661 motorIndex = parseOutputIndex(pch, true);
2662 if (motorIndex == -1) {
2663 return;
2666 break;
2667 case 1:
2668 motorValue = atoi(pch);
2670 break;
2672 index++;
2673 pch = strtok_r(NULL, " ", &saveptr);
2676 if (index == 2) {
2677 if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
2678 cliShowArgumentRangeError("value", 1000, 2000);
2679 } else {
2680 uint32_t motorOutputValue = convertExternalToMotor(motorValue);
2682 if (motorIndex != ALL_MOTORS) {
2683 motor_disarmed[motorIndex] = motorOutputValue;
2685 cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
2686 } else {
2687 for (int i = 0; i < getMotorCount(); i++) {
2688 motor_disarmed[i] = motorOutputValue;
2691 cliPrintLinef("all motors: %d", motorOutputValue);
2694 } else {
2695 cliShowParseError();
2699 #ifndef MINIMAL_CLI
2700 static void cliPlaySound(char *cmdline)
2702 int i;
2703 const char *name;
2704 static int lastSoundIdx = -1;
2706 if (isEmpty(cmdline)) {
2707 i = lastSoundIdx + 1; //next sound index
2708 if ((name=beeperNameForTableIndex(i)) == NULL) {
2709 while (true) { //no name for index; try next one
2710 if (++i >= beeperTableEntryCount())
2711 i = 0; //if end then wrap around to first entry
2712 if ((name=beeperNameForTableIndex(i)) != NULL)
2713 break; //if name OK then play sound below
2714 if (i == lastSoundIdx + 1) { //prevent infinite loop
2715 cliPrintLine("Error playing sound");
2716 return;
2720 } else { //index value was given
2721 i = atoi(cmdline);
2722 if ((name=beeperNameForTableIndex(i)) == NULL) {
2723 cliPrintLinef("No sound for index %d", i);
2724 return;
2727 lastSoundIdx = i;
2728 beeperSilence();
2729 cliPrintLinef("Playing sound %d: %s", i, name);
2730 beeper(beeperModeForTableIndex(i));
2732 #endif
2734 static void cliProfile(char *cmdline)
2736 if (isEmpty(cmdline)) {
2737 cliPrintLinef("profile %d", getCurrentPidProfileIndex());
2738 return;
2739 } else {
2740 const int i = atoi(cmdline);
2741 if (i >= 0 && i < MAX_PROFILE_COUNT) {
2742 systemConfigMutable()->pidProfileIndex = i;
2743 cliProfile("");
2748 static void cliRateProfile(char *cmdline)
2750 if (isEmpty(cmdline)) {
2751 cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
2752 return;
2753 } else {
2754 const int i = atoi(cmdline);
2755 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
2756 changeControlRateProfile(i);
2757 cliRateProfile("");
2762 static void cliDumpPidProfile(uint8_t pidProfileIndex, uint8_t dumpMask)
2764 if (pidProfileIndex >= MAX_PROFILE_COUNT) {
2765 // Faulty values
2766 return;
2768 changePidProfile(pidProfileIndex);
2769 cliPrintHashLine("profile");
2770 cliProfile("");
2771 cliPrintLinefeed();
2772 dumpAllValues(PROFILE_VALUE, dumpMask);
2775 static void cliDumpRateProfile(uint8_t rateProfileIndex, uint8_t dumpMask)
2777 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
2778 // Faulty values
2779 return;
2781 changeControlRateProfile(rateProfileIndex);
2782 cliPrintHashLine("rateprofile");
2783 cliRateProfile("");
2784 cliPrintLinefeed();
2785 dumpAllValues(PROFILE_RATE_VALUE, dumpMask);
2788 static void cliSave(char *cmdline)
2790 UNUSED(cmdline);
2792 cliPrintHashLine("saving");
2793 writeEEPROM();
2794 cliReboot();
2797 static void cliDefaults(char *cmdline)
2799 bool saveConfigs;
2801 if (isEmpty(cmdline)) {
2802 saveConfigs = true;
2803 } else if (strncasecmp(cmdline, "nosave", 6) == 0) {
2804 saveConfigs = false;
2805 } else {
2806 return;
2809 cliPrintHashLine("resetting to defaults");
2810 resetConfigs();
2811 if (saveConfigs) {
2812 cliSave(NULL);
2816 STATIC_UNIT_TESTED void cliGet(char *cmdline)
2818 const clivalue_t *val;
2819 int matchedCommands = 0;
2821 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
2822 if (strstr(valueTable[i].name, cmdline)) {
2823 val = &valueTable[i];
2824 cliPrintf("%s = ", valueTable[i].name);
2825 cliPrintVar(val, 0);
2826 cliPrintLinefeed();
2827 cliPrintVarRange(val);
2828 cliPrintLinefeed();
2830 matchedCommands++;
2835 if (matchedCommands) {
2836 return;
2839 cliPrintLine("Invalid name");
2842 static char *skipSpace(char *buffer)
2844 while (*(buffer) == ' ') {
2845 buffer++;
2848 return buffer;
2851 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
2853 while (*(bufEnd - 1) == ' ') {
2854 bufEnd--;
2857 return bufEnd - bufBegin;
2860 STATIC_UNIT_TESTED void cliSet(char *cmdline)
2862 const uint32_t len = strlen(cmdline);
2863 char *eqptr;
2865 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
2866 cliPrintLine("Current settings: ");
2868 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
2869 const clivalue_t *val = &valueTable[i];
2870 cliPrintf("%s = ", valueTable[i].name);
2871 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
2872 cliPrintLinefeed();
2874 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
2875 // has equals
2877 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
2879 // skip the '=' and any ' ' characters
2880 eqptr++;
2881 eqptr = skipSpace(eqptr);
2883 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
2884 const clivalue_t *val = &valueTable[i];
2886 // ensure exact match when setting to prevent setting variables with shorter names
2887 if (strncasecmp(cmdline, val->name, strlen(val->name)) == 0 && variableNameLength == strlen(val->name)) {
2889 bool valueChanged = false;
2890 int16_t value = 0;
2891 switch (val->type & VALUE_MODE_MASK) {
2892 case MODE_DIRECT: {
2893 int16_t value = atoi(eqptr);
2895 if (value >= val->config.minmax.min && value <= val->config.minmax.max) {
2896 cliSetVar(val, value);
2897 valueChanged = true;
2901 break;
2902 case MODE_LOOKUP: {
2903 const lookupTableEntry_t *tableEntry = &lookupTables[val->config.lookup.tableIndex];
2904 bool matched = false;
2905 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
2906 matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
2908 if (matched) {
2909 value = tableValueIndex;
2911 cliSetVar(val, value);
2912 valueChanged = true;
2917 break;
2918 case MODE_ARRAY: {
2919 const uint8_t arrayLength = val->config.array.length;
2920 char *valPtr = eqptr;
2922 for (int i = 0; i < arrayLength; i++) {
2923 // skip spaces
2924 valPtr = skipSpace(valPtr);
2925 // find next comma (or end of string)
2926 char *valEndPtr = strchr(valPtr, ',');
2928 // comma found or last item?
2929 if ((valEndPtr != NULL) || (i == arrayLength - 1)){
2930 // process substring [valPtr, valEndPtr[
2931 // note: no need to copy substrings for atoi()
2932 // it stops at the first character that cannot be converted...
2933 switch (val->type & VALUE_TYPE_MASK) {
2934 default:
2935 case VAR_UINT8: {
2936 // fetch data pointer
2937 uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
2938 // store value
2939 *data = (uint8_t)atoi((const char*) valPtr);
2941 break;
2943 case VAR_INT8: {
2944 // fetch data pointer
2945 int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
2946 // store value
2947 *data = (int8_t)atoi((const char*) valPtr);
2949 break;
2951 case VAR_UINT16: {
2952 // fetch data pointer
2953 uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
2954 // store value
2955 *data = (uint16_t)atoi((const char*) valPtr);
2957 break;
2959 case VAR_INT16: {
2960 // fetch data pointer
2961 int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
2962 // store value
2963 *data = (int16_t)atoi((const char*) valPtr);
2965 break;
2967 // mark as changed
2968 valueChanged = true;
2970 // prepare to parse next item
2971 valPtr = valEndPtr + 1;
2975 break;
2978 if (valueChanged) {
2979 cliPrintf("%s set to ", val->name);
2980 cliPrintVar(val, 0);
2981 } else {
2982 cliPrintLine("Invalid value");
2983 cliPrintVarRange(val);
2986 return;
2989 cliPrintLine("Invalid name");
2990 } else {
2991 // no equals, check for matching variables.
2992 cliGet(cmdline);
2996 static void cliStatus(char *cmdline)
2998 UNUSED(cmdline);
3000 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3002 #ifdef USE_RTC_TIME
3003 char buf[FORMATTED_DATE_TIME_BUFSIZE];
3004 dateTime_t dt;
3005 if (rtcGetDateTime(&dt)) {
3006 dateTimeFormatLocal(buf, &dt);
3007 cliPrintLinef("Current Time: %s", buf);
3009 #endif
3011 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
3013 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3015 #ifdef USE_ADC_INTERNAL
3016 uint16_t vrefintMv = getVrefMv();
3017 uint16_t coretemp = getCoreTemperatureCelsius();
3018 cliPrintf(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
3019 #endif
3021 #if defined(USE_SENSOR_NAMES)
3022 const uint32_t detectedSensorsMask = sensorsMask();
3023 for (uint32_t i = 0; ; i++) {
3024 if (sensorTypeNames[i] == NULL) {
3025 break;
3027 const uint32_t mask = (1 << i);
3028 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3029 const uint8_t sensorHardwareIndex = detectedSensors[i];
3030 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3031 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3032 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
3033 cliPrintf(".%c", acc.dev.revisionCode);
3037 #endif /* USE_SENSOR_NAMES */
3038 cliPrintLinefeed();
3040 #ifdef USE_SDCARD
3041 cliSdInfo(NULL);
3042 #endif
3044 #ifdef USE_I2C
3045 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3046 #else
3047 const uint16_t i2cErrorCounter = 0;
3048 #endif
3050 #ifdef STACK_CHECK
3051 cliPrintf("Stack used: %d, ", stackUsedSize());
3052 #endif
3053 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
3054 #ifdef EEPROM_IN_RAM
3055 #define CONFIG_SIZE EEPROM_SIZE
3056 #else
3057 #define CONFIG_SIZE (&__config_end - &__config_start)
3058 #endif
3059 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), CONFIG_SIZE);
3061 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
3062 const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
3063 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3064 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
3065 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
3066 cliPrint("Arming disable flags:");
3067 armingDisableFlags_e flags = getArmingDisableFlags();
3068 while (flags) {
3069 const int bitpos = ffs(flags) - 1;
3070 flags &= ~(1 << bitpos);
3071 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
3073 cliPrintLinefeed();
3076 #ifndef SKIP_TASK_STATISTICS
3077 static void cliTasks(char *cmdline)
3079 UNUSED(cmdline);
3080 int maxLoadSum = 0;
3081 int averageLoadSum = 0;
3083 #ifndef MINIMAL_CLI
3084 if (systemConfig()->task_statistics) {
3085 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
3086 } else {
3087 cliPrintLine("Task list");
3089 #endif
3090 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3091 cfTaskInfo_t taskInfo;
3092 getTaskInfo(taskId, &taskInfo);
3093 if (taskInfo.isEnabled) {
3094 int taskFrequency;
3095 int subTaskFrequency = 0;
3096 if (taskId == TASK_GYROPID) {
3097 subTaskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3098 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
3099 if (pidConfig()->pid_process_denom > 1) {
3100 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3101 } else {
3102 taskFrequency = subTaskFrequency;
3103 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
3105 } else {
3106 taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3107 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
3109 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3110 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3111 if (taskId != TASK_SERIAL) {
3112 maxLoadSum += maxLoad;
3113 averageLoadSum += averageLoad;
3115 if (systemConfig()->task_statistics) {
3116 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
3117 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
3118 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
3119 } else {
3120 cliPrintLinef("%6d", taskFrequency);
3122 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
3123 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
3127 if (systemConfig()->task_statistics) {
3128 cfCheckFuncInfo_t checkFuncInfo;
3129 getCheckFuncInfo(&checkFuncInfo);
3130 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
3131 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3134 #endif
3136 static void cliVersion(char *cmdline)
3138 UNUSED(cmdline);
3140 cliPrintLinef("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
3141 FC_FIRMWARE_NAME,
3142 targetName,
3143 systemConfig()->boardIdentifier,
3144 FC_VERSION_STRING,
3145 buildDate,
3146 buildTime,
3147 shortGitRevision,
3148 MSP_API_VERSION_STRING
3152 #if defined(USE_RESOURCE_MGMT)
3154 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
3156 typedef struct {
3157 const uint8_t owner;
3158 pgn_t pgn;
3159 uint16_t offset;
3160 const uint8_t maxIndex;
3161 } cliResourceValue_t;
3163 const cliResourceValue_t resourceTable[] = {
3164 #ifdef BEEPER
3165 { OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, offsetof(beeperDevConfig_t, ioTag), 0 },
3166 #endif
3167 { OWNER_MOTOR, PG_MOTOR_CONFIG, offsetof(motorConfig_t, dev.ioTags[0]), MAX_SUPPORTED_MOTORS },
3168 #ifdef USE_SERVOS
3169 { OWNER_SERVO, PG_SERVO_CONFIG, offsetof(servoConfig_t, dev.ioTags[0]), MAX_SUPPORTED_SERVOS },
3170 #endif
3171 #if defined(USE_PWM) || defined(USE_PPM)
3172 { OWNER_PPMINPUT, PG_PPM_CONFIG, offsetof(ppmConfig_t, ioTag), 0 },
3173 { OWNER_PWMINPUT, PG_PWM_CONFIG, offsetof(pwmConfig_t, ioTags[0]), PWM_INPUT_PORT_COUNT },
3174 #endif
3175 #ifdef USE_RANGEFINDER_HCSR04
3176 { OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, offsetof(sonarConfig_t, triggerTag), 0 },
3177 { OWNER_SONAR_ECHO, PG_SONAR_CONFIG, offsetof(sonarConfig_t, echoTag), 0 },
3178 #endif
3179 #ifdef USE_LED_STRIP
3180 { OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, offsetof(ledStripConfig_t, ioTag), 0 },
3181 #endif
3182 { OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagTx[0]), SERIAL_PORT_MAX_INDEX },
3183 { OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagRx[0]), SERIAL_PORT_MAX_INDEX },
3184 #ifdef USE_INVERTER
3185 { OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagInverter[0]), SERIAL_PORT_MAX_INDEX },
3186 #endif
3187 #ifdef USE_I2C
3188 { OWNER_I2C_SCL, PG_I2C_CONFIG, offsetof(i2cConfig_t, ioTagScl[0]), I2CDEV_COUNT },
3189 { OWNER_I2C_SDA, PG_I2C_CONFIG, offsetof(i2cConfig_t, ioTagSda[0]), I2CDEV_COUNT },
3190 #endif
3191 { OWNER_LED, PG_STATUS_LED_CONFIG, offsetof(statusLedConfig_t, ioTags[0]), STATUS_LED_NUMBER },
3192 #ifdef USE_SPEKTRUM_BIND
3193 { OWNER_RX_BIND, PG_RX_CONFIG, offsetof(rxConfig_t, spektrum_bind_pin_override_ioTag), 0 },
3194 { OWNER_RX_BIND_PLUG, PG_RX_CONFIG, offsetof(rxConfig_t, spektrum_bind_plug_ioTag), 0 },
3195 #endif
3196 #ifdef USE_TRANSPONDER
3197 { OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, offsetof(transponderConfig_t, ioTag), 0 },
3198 #endif
3199 #ifdef USE_SPI
3200 { OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, offsetof(spiPinConfig_t, ioTagSck[0]), SPIDEV_COUNT },
3201 { OWNER_SPI_MISO, PG_SPI_PIN_CONFIG, offsetof(spiPinConfig_t, ioTagMiso[0]), SPIDEV_COUNT },
3202 { OWNER_SPI_MOSI, PG_SPI_PIN_CONFIG, offsetof(spiPinConfig_t, ioTagMosi[0]), SPIDEV_COUNT },
3203 #endif
3204 #ifdef USE_ESCSERIAL
3205 { OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, offsetof(escSerialConfig_t, ioTag), 0 },
3206 #endif
3207 #ifdef USE_CAMERA_CONTROL
3208 { OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, offsetof(cameraControlConfig_t, ioTag), 0 },
3209 #endif
3210 #ifdef USE_ADC
3211 { OWNER_ADC_BATT, PG_ADC_CONFIG, offsetof(adcConfig_t, vbat.ioTag), 0 },
3212 { OWNER_ADC_RSSI, PG_ADC_CONFIG, offsetof(adcConfig_t, rssi.ioTag), 0 },
3213 { OWNER_ADC_CURR, PG_ADC_CONFIG, offsetof(adcConfig_t, current.ioTag), 0 },
3214 { OWNER_ADC_EXT, PG_ADC_CONFIG, offsetof(adcConfig_t, external1.ioTag), 0 },
3215 #endif
3216 #ifdef USE_BARO
3217 { OWNER_BARO_CS, PG_BAROMETER_CONFIG, offsetof(barometerConfig_t, baro_spi_csn), 0 },
3218 #endif
3219 #ifdef USE_MAG
3220 { OWNER_COMPASS_CS, PG_COMPASS_CONFIG, offsetof(compassConfig_t, mag_spi_csn), 0 },
3221 #endif
3224 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
3226 const pgRegistry_t* rec = pgFind(value.pgn);
3227 return CONST_CAST(ioTag_t *, rec->address + value.offset + index);
3230 static void printResource(uint8_t dumpMask)
3232 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
3233 const char* owner = ownerNames[resourceTable[i].owner];
3234 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
3235 const void *currentConfig;
3236 const void *defaultConfig;
3237 if (configIsInCopy) {
3238 currentConfig = pg->copy;
3239 defaultConfig = pg->address;
3240 } else {
3241 currentConfig = pg->address;
3242 defaultConfig = NULL;
3245 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
3246 const ioTag_t ioTag = *((const ioTag_t *)currentConfig + resourceTable[i].offset + index);
3247 const ioTag_t ioTagDefault = *((const ioTag_t *)defaultConfig + resourceTable[i].offset + index);
3249 bool equalsDefault = ioTag == ioTagDefault;
3250 const char *format = "resource %s %d %c%02d";
3251 const char *formatUnassigned = "resource %s %d NONE";
3252 if (!ioTagDefault) {
3253 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3254 } else {
3255 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
3257 if (!ioTag) {
3258 if (!(dumpMask & HIDE_UNUSED)) {
3259 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
3261 } else {
3262 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
3268 static void printResourceOwner(uint8_t owner, uint8_t index)
3270 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
3272 if (resourceTable[owner].maxIndex > 0) {
3273 cliPrintf(" %d", RESOURCE_INDEX(index));
3277 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
3279 if (!newTag) {
3280 return;
3283 const char * format = "\r\nNOTE: %c%02d already assigned to ";
3284 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
3285 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
3286 ioTag_t *tag = getIoTag(resourceTable[r], i);
3287 if (*tag == newTag) {
3288 bool cleared = false;
3289 if (r == resourceIndex) {
3290 if (i == index) {
3291 continue;
3293 *tag = IO_TAG_NONE;
3294 cleared = true;
3297 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
3299 printResourceOwner(r, i);
3301 if (cleared) {
3302 cliPrintf(". ");
3303 printResourceOwner(r, i);
3304 cliPrintf(" disabled");
3307 cliPrintLine(".");
3313 static bool strToPin(char *pch, ioTag_t *tag)
3315 if (strcasecmp(pch, "NONE") == 0) {
3316 *tag = IO_TAG_NONE;
3317 return true;
3318 } else {
3319 unsigned pin = 0;
3320 unsigned port = (*pch >= 'a') ? *pch - 'a' : *pch - 'A';
3322 if (port < 8) {
3323 pch++;
3324 pin = atoi(pch);
3325 if (pin < 16) {
3326 *tag = DEFIO_TAG_MAKE(port, pin);
3327 return true;
3331 return false;
3334 static void cliResource(char *cmdline)
3336 int len = strlen(cmdline);
3338 if (len == 0) {
3339 printResource(DUMP_MASTER | HIDE_UNUSED);
3341 return;
3342 } else if (strncasecmp(cmdline, "list", len) == 0) {
3343 #ifdef MINIMAL_CLI
3344 cliPrintLine("IO");
3345 #else
3346 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
3347 cliRepeat('-', 20);
3348 #endif
3349 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3350 const char* owner;
3351 owner = ownerNames[ioRecs[i].owner];
3353 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
3354 if (ioRecs[i].index > 0) {
3355 cliPrintf(" %d", ioRecs[i].index);
3357 cliPrintLinefeed();
3360 #ifndef MINIMAL_CLI
3361 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
3362 #endif
3364 return;
3367 uint8_t resourceIndex = 0;
3368 int index = 0;
3369 char *pch = NULL;
3370 char *saveptr;
3372 pch = strtok_r(cmdline, " ", &saveptr);
3373 for (resourceIndex = 0; ; resourceIndex++) {
3374 if (resourceIndex >= ARRAYLEN(resourceTable)) {
3375 cliPrintLine("Invalid");
3376 return;
3379 if (strncasecmp(pch, ownerNames[resourceTable[resourceIndex].owner], len) == 0) {
3380 break;
3384 pch = strtok_r(NULL, " ", &saveptr);
3385 index = atoi(pch);
3387 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
3388 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
3389 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
3390 return;
3392 index -= 1;
3394 pch = strtok_r(NULL, " ", &saveptr);
3397 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
3399 if (strlen(pch) > 0) {
3400 if (strToPin(pch, tag)) {
3401 if (*tag == IO_TAG_NONE) {
3402 #ifdef MINIMAL_CLI
3403 cliPrintLine("Freed");
3404 #else
3405 cliPrintLine("Resource is freed");
3406 #endif
3407 return;
3408 } else {
3409 ioRec_t *rec = IO_Rec(IOGetByTag(*tag));
3410 if (rec) {
3411 resourceCheck(resourceIndex, index, *tag);
3412 #ifdef MINIMAL_CLI
3413 cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
3414 #else
3415 cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
3416 #endif
3417 } else {
3418 cliShowParseError();
3420 return;
3425 cliShowParseError();
3428 static void printDma(void)
3430 cliPrintLinefeed();
3432 #ifdef MINIMAL_CLI
3433 cliPrintLine("DMA:");
3434 #else
3435 cliPrintLine("Currently active DMA:");
3436 cliRepeat('-', 20);
3437 #endif
3438 for (int i = 1; i <= DMA_LAST_HANDLER; i++) {
3439 const char* owner;
3440 owner = ownerNames[dmaGetOwner(i)];
3442 cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
3443 uint8_t resourceIndex = dmaGetResourceIndex(i);
3444 if (resourceIndex > 0) {
3445 cliPrintLinef(" %s %d", owner, resourceIndex);
3446 } else {
3447 cliPrintLinef(" %s", owner);
3452 static void cliDma(char* cmdLine)
3454 UNUSED(cmdLine);
3455 printDma();
3457 #endif /* USE_RESOURCE_MGMT */
3459 static void backupConfigs(void)
3461 // make copies of configs to do differencing
3462 PG_FOREACH(pg) {
3463 memcpy(pg->copy, pg->address, pg->size);
3466 configIsInCopy = true;
3469 static void restoreConfigs(void)
3471 PG_FOREACH(pg) {
3472 memcpy(pg->address, pg->copy, pg->size);
3475 configIsInCopy = false;
3478 static void printConfig(char *cmdline, bool doDiff)
3480 uint8_t dumpMask = DUMP_MASTER;
3481 char *options;
3482 if ((options = checkCommand(cmdline, "master"))) {
3483 dumpMask = DUMP_MASTER; // only
3484 } else if ((options = checkCommand(cmdline, "profile"))) {
3485 dumpMask = DUMP_PROFILE; // only
3486 } else if ((options = checkCommand(cmdline, "rates"))) {
3487 dumpMask = DUMP_RATES; // only
3488 } else if ((options = checkCommand(cmdline, "all"))) {
3489 dumpMask = DUMP_ALL; // all profiles and rates
3490 } else {
3491 options = cmdline;
3494 if (doDiff) {
3495 dumpMask = dumpMask | DO_DIFF;
3498 backupConfigs();
3499 // reset all configs to defaults to do differencing
3500 resetConfigs();
3502 #if defined(USE_TARGET_CONFIG)
3503 targetConfiguration();
3504 #endif
3505 if (checkCommand(options, "defaults")) {
3506 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
3509 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
3510 cliPrintHashLine("version");
3511 cliVersion(NULL);
3513 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
3514 cliPrintHashLine("reset configuration to default settings");
3515 cliPrint("defaults nosave");
3516 cliPrintLinefeed();
3519 cliPrintHashLine("name");
3520 printName(dumpMask, &pilotConfig_Copy);
3522 #ifdef USE_RESOURCE_MGMT
3523 cliPrintHashLine("resources");
3524 printResource(dumpMask);
3525 #endif
3527 #ifndef USE_QUAD_MIXER_ONLY
3528 cliPrintHashLine("mixer");
3529 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
3530 const char *formatMixer = "mixer %s";
3531 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
3532 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
3534 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
3536 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0));
3538 #ifdef USE_SERVOS
3539 cliPrintHashLine("servo");
3540 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
3542 cliPrintHashLine("servo mix");
3543 // print custom servo mixer if exists
3544 cliDumpPrintLinef(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n");
3545 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
3546 #endif
3547 #endif
3549 cliPrintHashLine("feature");
3550 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
3552 #ifdef BEEPER
3553 cliPrintHashLine("beeper");
3554 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
3555 #endif
3557 cliPrintHashLine("map");
3558 printMap(dumpMask, &rxConfig_Copy, rxConfig());
3560 cliPrintHashLine("serial");
3561 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
3563 #ifdef USE_LED_STRIP
3564 cliPrintHashLine("led");
3565 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
3567 cliPrintHashLine("color");
3568 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
3570 cliPrintHashLine("mode_color");
3571 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
3572 #endif
3574 cliPrintHashLine("aux");
3575 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
3577 cliPrintHashLine("adjrange");
3578 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
3580 cliPrintHashLine("rxrange");
3581 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
3583 #ifdef USE_VTX_CONTROL
3584 cliPrintHashLine("vtx");
3585 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig());
3586 #endif
3588 cliPrintHashLine("rxfail");
3589 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0));
3591 cliPrintHashLine("master");
3592 dumpAllValues(MASTER_VALUE, dumpMask);
3594 if (dumpMask & DUMP_ALL) {
3595 const uint8_t pidProfileIndexSave = systemConfig_Copy.pidProfileIndex;
3596 for (uint32_t pidProfileIndex = 0; pidProfileIndex < MAX_PROFILE_COUNT; pidProfileIndex++) {
3597 cliDumpPidProfile(pidProfileIndex, dumpMask);
3599 changePidProfile(pidProfileIndexSave);
3600 cliPrintHashLine("restore original profile selection");
3601 cliProfile("");
3603 const uint8_t controlRateProfileIndexSave = systemConfig_Copy.activeRateProfile;
3604 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
3605 cliDumpRateProfile(rateIndex, dumpMask);
3607 changeControlRateProfile(controlRateProfileIndexSave);
3608 cliPrintHashLine("restore original rateprofile selection");
3609 cliRateProfile("");
3611 cliPrintHashLine("save configuration");
3612 cliPrint("save");
3613 } else {
3614 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
3616 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
3620 if (dumpMask & DUMP_PROFILE) {
3621 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
3624 if (dumpMask & DUMP_RATES) {
3625 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
3627 // restore configs from copies
3628 restoreConfigs();
3631 static void cliDump(char *cmdline)
3633 printConfig(cmdline, false);
3636 static void cliDiff(char *cmdline)
3638 printConfig(cmdline, true);
3641 typedef struct {
3642 const char *name;
3643 #ifndef MINIMAL_CLI
3644 const char *description;
3645 const char *args;
3646 #endif
3647 void (*func)(char *cmdline);
3648 } clicmd_t;
3650 #ifndef MINIMAL_CLI
3651 #define CLI_COMMAND_DEF(name, description, args, method) \
3653 name , \
3654 description , \
3655 args , \
3656 method \
3658 #else
3659 #define CLI_COMMAND_DEF(name, description, args, method) \
3661 name, \
3662 method \
3664 #endif
3666 static void cliHelp(char *cmdline);
3668 // should be sorted a..z for bsearch()
3669 const clicmd_t cmdTable[] = {
3670 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
3671 CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
3672 #ifdef BEEPER
3673 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3674 "\t<+|->[name]", cliBeeper),
3675 #endif
3676 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL, cliBootloader),
3677 #ifdef USE_LED_STRIP
3678 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
3679 #endif
3680 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "[nosave]", cliDefaults),
3681 CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|all] {showdefaults}", cliDiff),
3682 #ifdef USE_DSHOT
3683 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
3684 #endif
3685 CLI_COMMAND_DEF("dump", "dump configuration",
3686 "[master|profile|rates|all] {defaults}", cliDump),
3687 #ifdef USE_ESCSERIAL
3688 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
3689 #endif
3690 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
3691 CLI_COMMAND_DEF("feature", "configure features",
3692 "list\r\n"
3693 "\t<+|->[name]", cliFeature),
3694 #ifdef USE_FLASHFS
3695 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
3696 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
3697 #ifdef USE_FLASH_TOOLS
3698 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
3699 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
3700 #endif
3701 #endif
3702 #ifdef USE_RX_FRSKY_SPI
3703 CLI_COMMAND_DEF("frsky_bind", "initiate binding for FrSky SPI RX", NULL, cliFrSkyBind),
3704 #endif
3705 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
3706 #ifdef USE_GPS
3707 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
3708 #endif
3709 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
3710 #ifdef USE_LED_STRIP
3711 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
3712 #endif
3713 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
3714 #ifndef USE_QUAD_MIXER_ONLY
3715 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
3716 #endif
3717 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
3718 #ifdef USE_LED_STRIP
3719 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
3720 #endif
3721 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
3722 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
3723 #ifndef MINIMAL_CLI
3724 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
3725 #endif
3726 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
3727 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
3728 #ifdef USE_RESOURCE_MGMT
3729 CLI_COMMAND_DEF("resource", "show/set resources", NULL, cliResource),
3730 CLI_COMMAND_DEF("dma", "list dma utilisation", NULL, cliDma),
3731 #endif
3732 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
3733 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
3734 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
3735 #ifdef USE_SDCARD
3736 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
3737 #endif
3738 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
3739 #ifndef SKIP_SERIAL_PASSTHROUGH
3740 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough),
3741 #endif
3742 #ifdef USE_SERVOS
3743 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
3744 #endif
3745 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
3746 #ifdef USE_SERVOS
3747 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
3748 "\treset\r\n"
3749 "\tload <mixer>\r\n"
3750 "\treverse <servo> <source> r|n", cliServoMix),
3751 #endif
3752 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
3753 #ifndef SKIP_TASK_STATISTICS
3754 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
3755 #endif
3756 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
3757 #ifdef USE_VTX_CONTROL
3758 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
3759 #endif
3762 static void cliHelp(char *cmdline)
3764 UNUSED(cmdline);
3766 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
3767 cliPrint(cmdTable[i].name);
3768 #ifndef MINIMAL_CLI
3769 if (cmdTable[i].description) {
3770 cliPrintf(" - %s", cmdTable[i].description);
3772 if (cmdTable[i].args) {
3773 cliPrintf("\r\n\t%s", cmdTable[i].args);
3775 #endif
3776 cliPrintLinefeed();
3780 void cliProcess(void)
3782 if (!cliWriter) {
3783 return;
3786 // Be a little bit tricky. Flush the last inputs buffer, if any.
3787 bufWriterFlush(cliWriter);
3789 while (serialRxBytesWaiting(cliPort)) {
3790 uint8_t c = serialRead(cliPort);
3791 if (c == '\t' || c == '?') {
3792 // do tab completion
3793 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
3794 uint32_t i = bufferIndex;
3795 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
3796 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
3797 continue;
3798 if (!pstart)
3799 pstart = cmd;
3800 pend = cmd;
3802 if (pstart) { /* Buffer matches one or more commands */
3803 for (; ; bufferIndex++) {
3804 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
3805 break;
3806 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
3807 /* Unambiguous -- append a space */
3808 cliBuffer[bufferIndex++] = ' ';
3809 cliBuffer[bufferIndex] = '\0';
3810 break;
3812 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
3815 if (!bufferIndex || pstart != pend) {
3816 /* Print list of ambiguous matches */
3817 cliPrint("\r\033[K");
3818 for (cmd = pstart; cmd <= pend; cmd++) {
3819 cliPrint(cmd->name);
3820 cliWrite('\t');
3822 cliPrompt();
3823 i = 0; /* Redraw prompt */
3825 for (; i < bufferIndex; i++)
3826 cliWrite(cliBuffer[i]);
3827 } else if (!bufferIndex && c == 4) { // CTRL-D
3828 cliExit(cliBuffer);
3829 return;
3830 } else if (c == 12) { // NewPage / CTRL-L
3831 // clear screen
3832 cliPrint("\033[2J\033[1;1H");
3833 cliPrompt();
3834 } else if (bufferIndex && (c == '\n' || c == '\r')) {
3835 // enter pressed
3836 cliPrintLinefeed();
3838 // Strip comment starting with # from line
3839 char *p = cliBuffer;
3840 p = strchr(p, '#');
3841 if (NULL != p) {
3842 bufferIndex = (uint32_t)(p - cliBuffer);
3845 // Strip trailing whitespace
3846 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
3847 bufferIndex--;
3850 // Process non-empty lines
3851 if (bufferIndex > 0) {
3852 cliBuffer[bufferIndex] = 0; // null terminate
3854 const clicmd_t *cmd;
3855 char *options;
3856 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
3857 if ((options = checkCommand(cliBuffer, cmd->name))) {
3858 break;
3861 if (cmd < cmdTable + ARRAYLEN(cmdTable))
3862 cmd->func(options);
3863 else
3864 cliPrint("Unknown command, try 'help'");
3865 bufferIndex = 0;
3868 memset(cliBuffer, 0, sizeof(cliBuffer));
3870 // 'exit' will reset this flag, so we don't need to print prompt again
3871 if (!cliMode)
3872 return;
3874 cliPrompt();
3875 } else if (c == 127) {
3876 // backspace
3877 if (bufferIndex) {
3878 cliBuffer[--bufferIndex] = 0;
3879 cliPrint("\010 \010");
3881 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
3882 if (!bufferIndex && c == ' ')
3883 continue; // Ignore leading spaces
3884 cliBuffer[bufferIndex++] = c;
3885 cliWrite(c);
3890 void cliEnter(serialPort_t *serialPort)
3892 cliMode = 1;
3893 cliPort = serialPort;
3894 setPrintfSerialPort(cliPort);
3895 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
3897 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
3899 #ifndef MINIMAL_CLI
3900 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
3901 #else
3902 cliPrintLine("\r\nCLI");
3903 #endif
3904 cliPrompt();
3906 setArmingDisabled(ARMING_DISABLED_CLI);
3909 void cliInit(const serialConfig_t *serialConfig)
3911 UNUSED(serialConfig);
3913 #endif // USE_CLI