Moving gyro to rest of hardware in settings.c
[betaflight.git] / src / main / fc / cli.c
blobebdadfda3fb99bccf2aeb1c3f8d3fafb50fbb609
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"
28 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
29 // signal that we're in cli mode
30 uint8_t cliMode = 0;
31 extern uint8_t __config_start; // configured via linker script when building binaries.
32 extern uint8_t __config_end;
34 #ifdef USE_CLI
36 #include "blackbox/blackbox.h"
38 #include "build/build_config.h"
39 #include "build/debug.h"
40 #include "build/version.h"
42 #include "cms/cms.h"
44 #include "common/axis.h"
45 #include "common/color.h"
46 #include "common/maths.h"
47 #include "common/printf.h"
48 #include "common/typeconversion.h"
49 #include "common/utils.h"
51 #include "config/config_eeprom.h"
52 #include "config/feature.h"
53 #include "config/parameter_group.h"
54 #include "config/parameter_group_ids.h"
56 #include "drivers/accgyro/accgyro.h"
57 #include "drivers/buf_writer.h"
58 #include "drivers/bus_i2c.h"
59 #include "drivers/compass/compass.h"
60 #include "drivers/display.h"
61 #include "drivers/dma.h"
62 #include "drivers/flash.h"
63 #include "drivers/io.h"
64 #include "drivers/io_impl.h"
65 #include "drivers/inverter.h"
66 #include "drivers/rx_pwm.h"
67 #include "drivers/sdcard.h"
68 #include "drivers/sensor.h"
69 #include "drivers/serial.h"
70 #include "drivers/serial_escserial.h"
71 #include "drivers/sonar_hcsr04.h"
72 #include "drivers/stack_check.h"
73 #include "drivers/system.h"
74 #include "drivers/time.h"
75 #include "drivers/timer.h"
76 #include "drivers/vcd.h"
77 #include "drivers/light_led.h"
79 #include "fc/settings.h"
80 #include "fc/cli.h"
81 #include "fc/config.h"
82 #include "fc/controlrate_profile.h"
83 #include "fc/fc_core.h"
84 #include "fc/rc_adjustments.h"
85 #include "fc/rc_controls.h"
86 #include "fc/runtime_config.h"
87 #include "fc/fc_msp.h"
89 #include "flight/altitude.h"
90 #include "flight/failsafe.h"
91 #include "flight/imu.h"
92 #include "flight/mixer.h"
93 #include "flight/navigation.h"
94 #include "flight/pid.h"
95 #include "flight/servos.h"
97 #include "io/asyncfatfs/asyncfatfs.h"
98 #include "io/beeper.h"
99 #include "io/flashfs.h"
100 #include "io/displayport_max7456.h"
101 #include "io/displayport_msp.h"
102 #include "io/gimbal.h"
103 #include "io/gps.h"
104 #include "io/ledstrip.h"
105 #include "io/osd.h"
106 #include "io/serial.h"
107 #include "io/vtx_rtc6705.h"
108 #include "io/vtx_control.h"
110 #include "rx/rx.h"
111 #include "rx/spektrum.h"
113 #include "scheduler/scheduler.h"
115 #include "sensors/acceleration.h"
116 #include "sensors/barometer.h"
117 #include "sensors/battery.h"
118 #include "sensors/boardalignment.h"
119 #include "sensors/compass.h"
120 #include "sensors/gyro.h"
121 #include "sensors/sensors.h"
123 #include "telemetry/frsky.h"
124 #include "telemetry/telemetry.h"
127 static serialPort_t *cliPort;
129 #ifdef STM32F1
130 #define CLI_IN_BUFFER_SIZE 128
131 #else
132 // Space required to set array parameters
133 #define CLI_IN_BUFFER_SIZE 256
134 #endif
135 #define CLI_OUT_BUFFER_SIZE 64
137 static bufWriter_t *cliWriter;
138 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_OUT_BUFFER_SIZE];
140 static char cliBuffer[CLI_IN_BUFFER_SIZE];
141 static uint32_t bufferIndex = 0;
143 static bool configIsInCopy = false;
145 static const char* const emptyName = "-";
147 #ifndef USE_QUAD_MIXER_ONLY
148 // sync this with mixerMode_e
149 static const char * const mixerNames[] = {
150 "TRI", "QUADP", "QUADX", "BI",
151 "GIMBAL", "Y6", "HEX6",
152 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
153 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
154 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
155 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
157 #endif
159 // sync this with features_e
160 static const char * const featureNames[] = {
161 "RX_PPM", "VBAT", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
162 "SERVO_TILT", "SOFTSERIAL", "GPS", "FAILSAFE",
163 "SONAR", "TELEMETRY", "CURRENT_METER", "3D", "RX_PARALLEL_PWM",
164 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
165 "UNUSED", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
166 "SDCARD", "VTX", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
169 // sync this with rxFailsafeChannelMode_e
170 static const char rxFailsafeModeCharacters[] = "ahs";
172 static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
173 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_INVALID },
174 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
177 #if defined(USE_SENSOR_NAMES)
178 // sync this with sensors_e
179 static const char * const sensorTypeNames[] = {
180 "GYRO", "ACC", "BARO", "MAG", "SONAR", "GPS", "GPS+MAG", NULL
183 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG)
185 static const char * const *sensorHardwareNames[] = {
186 lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware
188 #endif // USE_SENSOR_NAMES
190 #ifndef MINIMAL_CLI
191 static const char *armingDisableFlagNames[] = {
192 "NOGYRO", "FAILSAFE", "BOXFAILSAFE", "THROTTLE",
193 "ANGLE", "LOAD", "CALIB", "CLI", "CMS", "OSD", "BST"
195 #endif
197 static void cliPrint(const char *str)
199 while (*str) {
200 bufWriterAppend(cliWriter, *str++);
202 bufWriterFlush(cliWriter);
205 static void cliPrintLinefeed()
207 cliPrint("\r\n");
210 static void cliPrintLine(const char *str)
212 cliPrint(str);
213 cliPrintLinefeed();
216 #ifdef MINIMAL_CLI
217 #define cliPrintHashLine(str)
218 #else
219 static void cliPrintHashLine(const char *str)
221 cliPrint("\r\n# ");
222 cliPrintLine(str);
224 #endif
226 static void cliPutp(void *p, char ch)
228 bufWriterAppend(p, ch);
231 typedef enum {
232 DUMP_MASTER = (1 << 0),
233 DUMP_PROFILE = (1 << 1),
234 DUMP_RATES = (1 << 2),
235 DUMP_ALL = (1 << 3),
236 DO_DIFF = (1 << 4),
237 SHOW_DEFAULTS = (1 << 5),
238 HIDE_UNUSED = (1 << 6)
239 } dumpFlags_e;
241 static void cliPrintfva(const char *format, va_list va)
243 tfp_format(cliWriter, cliPutp, format, va);
244 bufWriterFlush(cliWriter);
247 static void cliPrintLinefva(const char *format, va_list va)
249 tfp_format(cliWriter, cliPutp, format, va);
250 bufWriterFlush(cliWriter);
251 cliPrintLinefeed();
254 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
256 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
257 va_list va;
258 va_start(va, format);
259 cliPrintLinefva(format, va);
260 va_end(va);
261 return true;
262 } else {
263 return false;
267 static void cliWrite(uint8_t ch)
269 bufWriterAppend(cliWriter, ch);
272 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
274 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
275 cliWrite('#');
277 va_list va;
278 va_start(va, format);
279 cliPrintLinefva(format, va);
280 va_end(va);
281 return true;
282 } else {
283 return false;
287 static void cliPrintf(const char *format, ...)
289 va_list va;
290 va_start(va, format);
291 cliPrintfva(format, va);
292 va_end(va);
296 static void cliPrintLinef(const char *format, ...)
298 va_list va;
299 va_start(va, format);
300 cliPrintLinefva(format, va);
301 va_end(va);
304 static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
306 if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
307 for (int i = 0; i < var->config.array.length; i++) {
308 uint8_t value = ((uint8_t *)valuePointer)[i];
310 cliPrintf("%d", value);
311 if (i < var->config.array.length - 1) {
312 cliPrint(",");
315 } else {
316 int value = 0;
318 switch (var->type & VALUE_TYPE_MASK) {
319 case VAR_UINT8:
320 value = *(uint8_t *)valuePointer;
321 break;
323 case VAR_INT8:
324 value = *(int8_t *)valuePointer;
325 break;
327 case VAR_UINT16:
328 case VAR_INT16:
329 value = *(int16_t *)valuePointer;
330 break;
333 switch(var->type & VALUE_MODE_MASK) {
334 case MODE_DIRECT:
335 cliPrintf("%d", value);
336 if (full) {
337 cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
339 break;
340 case MODE_LOOKUP:
341 cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
342 break;
347 static bool valuePtrEqualsDefault(uint8_t type, const void *ptr, const void *ptrDefault)
349 bool result = false;
350 switch (type & VALUE_TYPE_MASK) {
351 case VAR_UINT8:
352 result = *(uint8_t *)ptr == *(uint8_t *)ptrDefault;
353 break;
355 case VAR_INT8:
356 result = *(int8_t *)ptr == *(int8_t *)ptrDefault;
357 break;
359 case VAR_UINT16:
360 case VAR_INT16:
361 result = *(int16_t *)ptr == *(int16_t *)ptrDefault;
362 break;
365 return result;
368 static uint16_t getValueOffset(const clivalue_t *value)
370 switch (value->type & VALUE_SECTION_MASK) {
371 case MASTER_VALUE:
372 return value->offset;
373 case PROFILE_VALUE:
374 return value->offset + sizeof(pidProfile_t) * getCurrentPidProfileIndex();
375 case PROFILE_RATE_VALUE:
376 return value->offset + sizeof(controlRateConfig_t) * getCurrentControlRateProfileIndex();
378 return 0;
381 static void *getValuePointer(const clivalue_t *value)
383 const pgRegistry_t* rec = pgFind(value->pgn);
384 return CONST_CAST(void *, rec->address + getValueOffset(value));
387 static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
389 const pgRegistry_t *pg = pgFind(value->pgn);
390 #ifdef DEBUG
391 if (!pg) {
392 cliPrintLinef("VALUE %s ERROR", value->name);
393 return; // if it's not found, the pgn shouldn't be in the value table!
395 #endif
397 const char *format = "set %s = ";
398 const char *defaultFormat = "#set %s = ";
399 const int valueOffset = getValueOffset(value);
400 const bool equalsDefault = valuePtrEqualsDefault(value->type, pg->copy + valueOffset, pg->address + valueOffset);
402 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
403 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
404 cliPrintf(defaultFormat, value->name);
405 printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
406 cliPrintLinefeed();
408 cliPrintf(format, value->name);
409 printValuePointer(value, pg->copy + valueOffset, false);
410 cliPrintLinefeed();
414 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
416 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
417 const clivalue_t *value = &valueTable[i];
418 bufWriterFlush(cliWriter);
419 if ((value->type & VALUE_SECTION_MASK) == valueSection) {
420 dumpPgValue(value, dumpMask);
425 static void cliPrintVar(const clivalue_t *var, bool full)
427 const void *ptr = getValuePointer(var);
429 printValuePointer(var, ptr, full);
432 static void cliPrintVarRange(const clivalue_t *var)
434 switch (var->type & VALUE_MODE_MASK) {
435 case (MODE_DIRECT): {
436 cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
438 break;
439 case (MODE_LOOKUP): {
440 const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
441 cliPrint("Allowed values:");
442 for (uint32_t i = 0; i < tableEntry->valueCount ; i++) {
443 if (i > 0)
444 cliPrint(",");
445 cliPrintf(" %s", tableEntry->values[i]);
447 cliPrintLinefeed();
449 break;
450 case (MODE_ARRAY): {
451 cliPrintLinef("Array length: %d", var->config.array.length);
454 break;
458 static void cliSetVar(const clivalue_t *var, const int16_t value)
460 void *ptr = getValuePointer(var);
462 switch (var->type & VALUE_TYPE_MASK) {
463 case VAR_UINT8:
464 *(uint8_t *)ptr = value;
465 break;
467 case VAR_INT8:
468 *(int8_t *)ptr = value;
469 break;
471 case VAR_UINT16:
472 case VAR_INT16:
473 *(int16_t *)ptr = value;
474 break;
478 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
479 static void cliRepeat(char ch, uint8_t len)
481 for (int i = 0; i < len; i++) {
482 bufWriterAppend(cliWriter, ch);
484 cliPrintLinefeed();
486 #endif
488 static void cliPrompt(void)
490 cliPrint("\r\n# ");
493 static void cliShowParseError(void)
495 cliPrintLine("Parse error");
498 static void cliShowArgumentRangeError(char *name, int min, int max)
500 cliPrintLinef("%s not between %d and %d", name, min, max);
503 static const char *nextArg(const char *currentArg)
505 const char *ptr = strchr(currentArg, ' ');
506 while (ptr && *ptr == ' ') {
507 ptr++;
510 return ptr;
513 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
515 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
516 ptr = nextArg(ptr);
517 if (ptr) {
518 int val = atoi(ptr);
519 val = CHANNEL_VALUE_TO_STEP(val);
520 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
521 if (argIndex == 0) {
522 range->startStep = val;
523 } else {
524 range->endStep = val;
526 (*validArgumentCount)++;
531 return ptr;
534 // Check if a string's length is zero
535 static bool isEmpty(const char *string)
537 return (string == NULL || *string == '\0') ? true : false;
540 static void printRxFailsafe(uint8_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs)
542 // print out rxConfig failsafe settings
543 for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
544 const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
545 const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
546 const bool equalsDefault = channelFailsafeConfig->mode == defaultChannelFailsafeConfig->mode
547 && channelFailsafeConfig->step == defaultChannelFailsafeConfig->step;
548 const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
549 if (requireValue) {
550 const char *format = "rxfail %u %c %d";
551 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
552 channel,
553 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
554 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
556 cliDumpPrintLinef(dumpMask, equalsDefault, format,
557 channel,
558 rxFailsafeModeCharacters[channelFailsafeConfig->mode],
559 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
561 } else {
562 const char *format = "rxfail %u %c";
563 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
564 channel,
565 rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
567 cliDumpPrintLinef(dumpMask, equalsDefault, format,
568 channel,
569 rxFailsafeModeCharacters[channelFailsafeConfig->mode]
575 static void cliRxFailsafe(char *cmdline)
577 uint8_t channel;
578 char buf[3];
580 if (isEmpty(cmdline)) {
581 // print out rxConfig failsafe settings
582 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
583 cliRxFailsafe(itoa(channel, buf, 10));
585 } else {
586 const char *ptr = cmdline;
587 channel = atoi(ptr++);
588 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
590 rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
592 const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
593 rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
594 bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
596 ptr = nextArg(ptr);
597 if (ptr) {
598 const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
599 if (p) {
600 const uint8_t requestedMode = p - rxFailsafeModeCharacters;
601 mode = rxFailsafeModesTable[type][requestedMode];
602 } else {
603 mode = RX_FAILSAFE_MODE_INVALID;
605 if (mode == RX_FAILSAFE_MODE_INVALID) {
606 cliShowParseError();
607 return;
610 requireValue = mode == RX_FAILSAFE_MODE_SET;
612 ptr = nextArg(ptr);
613 if (ptr) {
614 if (!requireValue) {
615 cliShowParseError();
616 return;
618 uint16_t value = atoi(ptr);
619 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
620 if (value > MAX_RXFAIL_RANGE_STEP) {
621 cliPrintLine("Value out of range");
622 return;
625 channelFailsafeConfig->step = value;
626 } else if (requireValue) {
627 cliShowParseError();
628 return;
630 channelFailsafeConfig->mode = mode;
633 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
635 // double use of cliPrintf below
636 // 1. acknowledge interpretation on command,
637 // 2. query current setting on single item,
639 if (requireValue) {
640 cliPrintLinef("rxfail %u %c %d",
641 channel,
642 modeCharacter,
643 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
645 } else {
646 cliPrintLinef("rxfail %u %c",
647 channel,
648 modeCharacter
651 } else {
652 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
657 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
659 const char *format = "aux %u %u %u %u %u";
660 // print out aux channel settings
661 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
662 const modeActivationCondition_t *mac = &modeActivationConditions[i];
663 bool equalsDefault = false;
664 if (defaultModeActivationConditions) {
665 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
666 equalsDefault = mac->modeId == macDefault->modeId
667 && mac->auxChannelIndex == macDefault->auxChannelIndex
668 && mac->range.startStep == macDefault->range.startStep
669 && mac->range.endStep == macDefault->range.endStep;
670 const box_t *box = findBoxByBoxId(macDefault->modeId);
671 if (box) {
672 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
674 box->permanentId,
675 macDefault->auxChannelIndex,
676 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
677 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep)
681 const box_t *box = findBoxByBoxId(mac->modeId);
682 if (box) {
683 cliDumpPrintLinef(dumpMask, equalsDefault, format,
685 box->permanentId,
686 mac->auxChannelIndex,
687 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
688 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep)
694 static void cliAux(char *cmdline)
696 int i, val = 0;
697 const char *ptr;
699 if (isEmpty(cmdline)) {
700 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
701 } else {
702 ptr = cmdline;
703 i = atoi(ptr++);
704 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
705 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
706 uint8_t validArgumentCount = 0;
707 ptr = nextArg(ptr);
708 if (ptr) {
709 val = atoi(ptr);
710 const box_t *box = findBoxByPermanentId(val);
711 if (box) {
712 mac->modeId = box->boxId;
713 validArgumentCount++;
716 ptr = nextArg(ptr);
717 if (ptr) {
718 val = atoi(ptr);
719 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
720 mac->auxChannelIndex = val;
721 validArgumentCount++;
724 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
726 if (validArgumentCount != 4) {
727 memset(mac, 0, sizeof(modeActivationCondition_t));
729 } else {
730 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
735 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
737 const char *format = "serial %d %d %ld %ld %ld %ld";
738 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
739 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
740 continue;
742 bool equalsDefault = false;
743 if (serialConfigDefault) {
744 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
745 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
746 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
747 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
748 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
749 && serialConfig->portConfigs[i].blackbox_baudrateIndex == serialConfigDefault->portConfigs[i].blackbox_baudrateIndex;
750 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
751 serialConfigDefault->portConfigs[i].identifier,
752 serialConfigDefault->portConfigs[i].functionMask,
753 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
754 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
755 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
756 baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
759 cliDumpPrintLinef(dumpMask, equalsDefault, format,
760 serialConfig->portConfigs[i].identifier,
761 serialConfig->portConfigs[i].functionMask,
762 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
763 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
764 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
765 baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
770 static void cliSerial(char *cmdline)
772 if (isEmpty(cmdline)) {
773 printSerial(DUMP_MASTER, serialConfig(), NULL);
774 return;
776 serialPortConfig_t portConfig;
777 memset(&portConfig, 0 , sizeof(portConfig));
779 serialPortConfig_t *currentConfig;
781 uint8_t validArgumentCount = 0;
783 const char *ptr = cmdline;
785 int val = atoi(ptr++);
786 currentConfig = serialFindPortConfiguration(val);
787 if (currentConfig) {
788 portConfig.identifier = val;
789 validArgumentCount++;
792 ptr = nextArg(ptr);
793 if (ptr) {
794 val = atoi(ptr);
795 portConfig.functionMask = val & 0xFFFF;
796 validArgumentCount++;
799 for (int i = 0; i < 4; i ++) {
800 ptr = nextArg(ptr);
801 if (!ptr) {
802 break;
805 val = atoi(ptr);
807 uint8_t baudRateIndex = lookupBaudRateIndex(val);
808 if (baudRates[baudRateIndex] != (uint32_t) val) {
809 break;
812 switch(i) {
813 case 0:
814 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
815 continue;
817 portConfig.msp_baudrateIndex = baudRateIndex;
818 break;
819 case 1:
820 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
821 continue;
823 portConfig.gps_baudrateIndex = baudRateIndex;
824 break;
825 case 2:
826 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
827 continue;
829 portConfig.telemetry_baudrateIndex = baudRateIndex;
830 break;
831 case 3:
832 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
833 continue;
835 portConfig.blackbox_baudrateIndex = baudRateIndex;
836 break;
839 validArgumentCount++;
842 if (validArgumentCount < 6) {
843 cliShowParseError();
844 return;
847 memcpy(currentConfig, &portConfig, sizeof(portConfig));
850 #ifndef SKIP_SERIAL_PASSTHROUGH
851 static void cliSerialPassthrough(char *cmdline)
853 if (isEmpty(cmdline)) {
854 cliShowParseError();
855 return;
858 int id = -1;
859 uint32_t baud = 0;
860 unsigned mode = 0;
861 char *saveptr;
862 char* tok = strtok_r(cmdline, " ", &saveptr);
863 int index = 0;
865 while (tok != NULL) {
866 switch(index) {
867 case 0:
868 id = atoi(tok);
869 break;
870 case 1:
871 baud = atoi(tok);
872 break;
873 case 2:
874 if (strstr(tok, "rx") || strstr(tok, "RX"))
875 mode |= MODE_RX;
876 if (strstr(tok, "tx") || strstr(tok, "TX"))
877 mode |= MODE_TX;
878 break;
880 index++;
881 tok = strtok_r(NULL, " ", &saveptr);
884 tfp_printf("Port %d ", id);
885 serialPort_t *passThroughPort;
886 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
887 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
888 if (!baud) {
889 tfp_printf("closed, specify baud.\r\n");
890 return;
892 if (!mode)
893 mode = MODE_RXTX;
895 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL,
896 baud, mode,
897 SERIAL_NOT_INVERTED);
898 if (!passThroughPort) {
899 tfp_printf("could not be opened.\r\n");
900 return;
902 tfp_printf("opened, baud = %d.\r\n", baud);
903 } else {
904 passThroughPort = passThroughPortUsage->serialPort;
905 // If the user supplied a mode, override the port's mode, otherwise
906 // leave the mode unchanged. serialPassthrough() handles one-way ports.
907 tfp_printf("already open.\r\n");
908 if (mode && passThroughPort->mode != mode) {
909 tfp_printf("mode changed from %d to %d.\r\n",
910 passThroughPort->mode, mode);
911 serialSetMode(passThroughPort, mode);
913 // If this port has a rx callback associated we need to remove it now.
914 // Otherwise no data will be pushed in the serial port buffer!
915 if (passThroughPort->rxCallback) {
916 passThroughPort->rxCallback = 0;
920 tfp_printf("forwarding, power cycle to exit.\r\n");
922 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
924 #endif
926 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
928 const char *format = "adjrange %u %u %u %u %u %u %u";
929 // print out adjustment ranges channel settings
930 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
931 const adjustmentRange_t *ar = &adjustmentRanges[i];
932 bool equalsDefault = false;
933 if (defaultAdjustmentRanges) {
934 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
935 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
936 && ar->range.startStep == arDefault->range.startStep
937 && ar->range.endStep == arDefault->range.endStep
938 && ar->adjustmentFunction == arDefault->adjustmentFunction
939 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
940 && ar->adjustmentIndex == arDefault->adjustmentIndex;
941 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
943 arDefault->adjustmentIndex,
944 arDefault->auxChannelIndex,
945 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
946 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
947 arDefault->adjustmentFunction,
948 arDefault->auxSwitchChannelIndex
951 cliDumpPrintLinef(dumpMask, equalsDefault, format,
953 ar->adjustmentIndex,
954 ar->auxChannelIndex,
955 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
956 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
957 ar->adjustmentFunction,
958 ar->auxSwitchChannelIndex
963 static void cliAdjustmentRange(char *cmdline)
965 int i, val = 0;
966 const char *ptr;
968 if (isEmpty(cmdline)) {
969 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
970 } else {
971 ptr = cmdline;
972 i = atoi(ptr++);
973 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
974 adjustmentRange_t *ar = adjustmentRangesMutable(i);
975 uint8_t validArgumentCount = 0;
977 ptr = nextArg(ptr);
978 if (ptr) {
979 val = atoi(ptr);
980 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
981 ar->adjustmentIndex = val;
982 validArgumentCount++;
985 ptr = nextArg(ptr);
986 if (ptr) {
987 val = atoi(ptr);
988 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
989 ar->auxChannelIndex = val;
990 validArgumentCount++;
994 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
996 ptr = nextArg(ptr);
997 if (ptr) {
998 val = atoi(ptr);
999 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1000 ar->adjustmentFunction = val;
1001 validArgumentCount++;
1004 ptr = nextArg(ptr);
1005 if (ptr) {
1006 val = atoi(ptr);
1007 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1008 ar->auxSwitchChannelIndex = val;
1009 validArgumentCount++;
1013 if (validArgumentCount != 6) {
1014 memset(ar, 0, sizeof(adjustmentRange_t));
1015 cliShowParseError();
1017 } else {
1018 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1023 #ifndef USE_QUAD_MIXER_ONLY
1024 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer)
1026 const char *format = "mmix %d %s %s %s %s";
1027 char buf0[FTOA_BUFFER_LENGTH];
1028 char buf1[FTOA_BUFFER_LENGTH];
1029 char buf2[FTOA_BUFFER_LENGTH];
1030 char buf3[FTOA_BUFFER_LENGTH];
1031 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1032 if (customMotorMixer[i].throttle == 0.0f)
1033 break;
1034 const float thr = customMotorMixer[i].throttle;
1035 const float roll = customMotorMixer[i].roll;
1036 const float pitch = customMotorMixer[i].pitch;
1037 const float yaw = customMotorMixer[i].yaw;
1038 bool equalsDefault = false;
1039 if (defaultCustomMotorMixer) {
1040 const float thrDefault = defaultCustomMotorMixer[i].throttle;
1041 const float rollDefault = defaultCustomMotorMixer[i].roll;
1042 const float pitchDefault = defaultCustomMotorMixer[i].pitch;
1043 const float yawDefault = defaultCustomMotorMixer[i].yaw;
1044 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1046 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1048 ftoa(thrDefault, buf0),
1049 ftoa(rollDefault, buf1),
1050 ftoa(pitchDefault, buf2),
1051 ftoa(yawDefault, buf3));
1053 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1055 ftoa(thr, buf0),
1056 ftoa(roll, buf1),
1057 ftoa(pitch, buf2),
1058 ftoa(yaw, buf3));
1061 #endif // USE_QUAD_MIXER_ONLY
1063 static void cliMotorMix(char *cmdline)
1065 #ifdef USE_QUAD_MIXER_ONLY
1066 UNUSED(cmdline);
1067 #else
1068 int check = 0;
1069 uint8_t len;
1070 const char *ptr;
1072 if (isEmpty(cmdline)) {
1073 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1074 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1075 // erase custom mixer
1076 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1077 customMotorMixerMutable(i)->throttle = 0.0f;
1079 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1080 ptr = nextArg(cmdline);
1081 if (ptr) {
1082 len = strlen(ptr);
1083 for (uint32_t i = 0; ; i++) {
1084 if (mixerNames[i] == NULL) {
1085 cliPrintLine("Invalid name");
1086 break;
1088 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1089 mixerLoadMix(i, customMotorMixerMutable(0));
1090 cliPrintLinef("Loaded %s", mixerNames[i]);
1091 cliMotorMix("");
1092 break;
1096 } else {
1097 ptr = cmdline;
1098 uint32_t i = atoi(ptr); // get motor number
1099 if (i < MAX_SUPPORTED_MOTORS) {
1100 ptr = nextArg(ptr);
1101 if (ptr) {
1102 customMotorMixerMutable(i)->throttle = fastA2F(ptr);
1103 check++;
1105 ptr = nextArg(ptr);
1106 if (ptr) {
1107 customMotorMixerMutable(i)->roll = fastA2F(ptr);
1108 check++;
1110 ptr = nextArg(ptr);
1111 if (ptr) {
1112 customMotorMixerMutable(i)->pitch = fastA2F(ptr);
1113 check++;
1115 ptr = nextArg(ptr);
1116 if (ptr) {
1117 customMotorMixerMutable(i)->yaw = fastA2F(ptr);
1118 check++;
1120 if (check != 4) {
1121 cliShowParseError();
1122 } else {
1123 printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
1125 } else {
1126 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1129 #endif
1132 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1134 const char *format = "rxrange %u %u %u";
1135 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1136 bool equalsDefault = false;
1137 if (defaultChannelRangeConfigs) {
1138 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1139 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1140 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1142 defaultChannelRangeConfigs[i].min,
1143 defaultChannelRangeConfigs[i].max
1146 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1148 channelRangeConfigs[i].min,
1149 channelRangeConfigs[i].max
1154 static void cliRxRange(char *cmdline)
1156 int i, validArgumentCount = 0;
1157 const char *ptr;
1159 if (isEmpty(cmdline)) {
1160 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1161 } else if (strcasecmp(cmdline, "reset") == 0) {
1162 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1163 } else {
1164 ptr = cmdline;
1165 i = atoi(ptr);
1166 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1167 int rangeMin = 0, rangeMax = 0;
1169 ptr = nextArg(ptr);
1170 if (ptr) {
1171 rangeMin = atoi(ptr);
1172 validArgumentCount++;
1175 ptr = nextArg(ptr);
1176 if (ptr) {
1177 rangeMax = atoi(ptr);
1178 validArgumentCount++;
1181 if (validArgumentCount != 2) {
1182 cliShowParseError();
1183 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1184 cliShowParseError();
1185 } else {
1186 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1187 channelRangeConfig->min = rangeMin;
1188 channelRangeConfig->max = rangeMax;
1190 } else {
1191 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1196 #ifdef LED_STRIP
1197 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1199 const char *format = "led %u %s";
1200 char ledConfigBuffer[20];
1201 char ledConfigDefaultBuffer[20];
1202 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1203 ledConfig_t ledConfig = ledConfigs[i];
1204 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1205 bool equalsDefault = false;
1206 if (defaultLedConfigs) {
1207 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1208 equalsDefault = ledConfig == ledConfigDefault;
1209 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1210 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1212 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1216 static void cliLed(char *cmdline)
1218 int i;
1219 const char *ptr;
1221 if (isEmpty(cmdline)) {
1222 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1223 } else {
1224 ptr = cmdline;
1225 i = atoi(ptr);
1226 if (i < LED_MAX_STRIP_LENGTH) {
1227 ptr = nextArg(cmdline);
1228 if (!parseLedStripConfig(i, ptr)) {
1229 cliShowParseError();
1231 } else {
1232 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1237 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1239 const char *format = "color %u %d,%u,%u";
1240 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1241 const hsvColor_t *color = &colors[i];
1242 bool equalsDefault = false;
1243 if (defaultColors) {
1244 const hsvColor_t *colorDefault = &defaultColors[i];
1245 equalsDefault = color->h == colorDefault->h
1246 && color->s == colorDefault->s
1247 && color->v == colorDefault->v;
1248 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1250 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1254 static void cliColor(char *cmdline)
1256 if (isEmpty(cmdline)) {
1257 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1258 } else {
1259 const char *ptr = cmdline;
1260 const int i = atoi(ptr);
1261 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1262 ptr = nextArg(cmdline);
1263 if (!parseColor(i, ptr)) {
1264 cliShowParseError();
1266 } else {
1267 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1272 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1274 const char *format = "mode_color %u %u %u";
1275 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1276 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1277 int colorIndex = ledStripConfig->modeColors[i].color[j];
1278 bool equalsDefault = false;
1279 if (defaultLedStripConfig) {
1280 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1281 equalsDefault = colorIndex == colorIndexDefault;
1282 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1284 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1288 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1289 const int colorIndex = ledStripConfig->specialColors.color[j];
1290 bool equalsDefault = false;
1291 if (defaultLedStripConfig) {
1292 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1293 equalsDefault = colorIndex == colorIndexDefault;
1294 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1296 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1299 const int ledStripAuxChannel = ledStripConfig->ledstrip_aux_channel;
1300 bool equalsDefault = false;
1301 if (defaultLedStripConfig) {
1302 const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
1303 equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
1304 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
1306 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
1309 static void cliModeColor(char *cmdline)
1311 if (isEmpty(cmdline)) {
1312 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1313 } else {
1314 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1315 int args[ARGS_COUNT];
1316 int argNo = 0;
1317 char *saveptr;
1318 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1319 while (ptr && argNo < ARGS_COUNT) {
1320 args[argNo++] = atoi(ptr);
1321 ptr = strtok_r(NULL, " ", &saveptr);
1324 if (ptr != NULL || argNo != ARGS_COUNT) {
1325 cliShowParseError();
1326 return;
1329 int modeIdx = args[MODE];
1330 int funIdx = args[FUNCTION];
1331 int color = args[COLOR];
1332 if(!setModeColor(modeIdx, funIdx, color)) {
1333 cliShowParseError();
1334 return;
1336 // values are validated
1337 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1340 #endif
1342 #ifdef USE_SERVOS
1343 static void printServo(uint8_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams)
1345 // print out servo settings
1346 const char *format = "servo %u %d %d %d %d %d";
1347 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1348 const servoParam_t *servoConf = &servoParams[i];
1349 bool equalsDefault = false;
1350 if (defaultServoParams) {
1351 const servoParam_t *defaultServoConf = &defaultServoParams[i];
1352 equalsDefault = servoConf->min == defaultServoConf->min
1353 && servoConf->max == defaultServoConf->max
1354 && servoConf->middle == defaultServoConf->middle
1355 && servoConf->rate == defaultServoConf->rate
1356 && servoConf->forwardFromChannel == defaultServoConf->forwardFromChannel;
1357 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1359 defaultServoConf->min,
1360 defaultServoConf->max,
1361 defaultServoConf->middle,
1362 defaultServoConf->rate,
1363 defaultServoConf->forwardFromChannel
1366 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1368 servoConf->min,
1369 servoConf->max,
1370 servoConf->middle,
1371 servoConf->rate,
1372 servoConf->forwardFromChannel
1375 // print servo directions
1376 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1377 const char *format = "smix reverse %d %d r";
1378 const servoParam_t *servoConf = &servoParams[i];
1379 const servoParam_t *servoConfDefault = &defaultServoParams[i];
1380 if (defaultServoParams) {
1381 bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
1382 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1383 equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
1384 if (servoConfDefault->reversedSources & (1 << channel)) {
1385 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
1387 if (servoConf->reversedSources & (1 << channel)) {
1388 cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
1391 } else {
1392 for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1393 if (servoConf->reversedSources & (1 << channel)) {
1394 cliDumpPrintLinef(dumpMask, true, format, i , channel);
1401 static void cliServo(char *cmdline)
1403 enum { SERVO_ARGUMENT_COUNT = 6 };
1404 int16_t arguments[SERVO_ARGUMENT_COUNT];
1406 servoParam_t *servo;
1408 int i;
1409 char *ptr;
1411 if (isEmpty(cmdline)) {
1412 printServo(DUMP_MASTER, servoParams(0), NULL);
1413 } else {
1414 int validArgumentCount = 0;
1416 ptr = cmdline;
1418 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1420 // If command line doesn't fit the format, don't modify the config
1421 while (*ptr) {
1422 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1423 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1424 cliShowParseError();
1425 return;
1428 arguments[validArgumentCount++] = atoi(ptr);
1430 do {
1431 ptr++;
1432 } while (*ptr >= '0' && *ptr <= '9');
1433 } else if (*ptr == ' ') {
1434 ptr++;
1435 } else {
1436 cliShowParseError();
1437 return;
1441 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
1443 i = arguments[INDEX];
1445 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1446 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1447 cliShowParseError();
1448 return;
1451 servo = servoParamsMutable(i);
1453 if (
1454 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
1455 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
1456 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1457 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1458 arguments[RATE] < -100 || arguments[RATE] > 100 ||
1459 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1461 cliShowParseError();
1462 return;
1465 servo->min = arguments[MIN];
1466 servo->max = arguments[MAX];
1467 servo->middle = arguments[MIDDLE];
1468 servo->rate = arguments[RATE];
1469 servo->forwardFromChannel = arguments[FORWARD];
1472 #endif
1474 #ifdef USE_SERVOS
1475 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1477 const char *format = "smix %d %d %d %d %d %d %d %d";
1478 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1479 const servoMixer_t customServoMixer = customServoMixers[i];
1480 if (customServoMixer.rate == 0) {
1481 break;
1484 bool equalsDefault = false;
1485 if (defaultCustomServoMixers) {
1486 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1487 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1488 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1489 && customServoMixer.rate == customServoMixerDefault.rate
1490 && customServoMixer.speed == customServoMixerDefault.speed
1491 && customServoMixer.min == customServoMixerDefault.min
1492 && customServoMixer.max == customServoMixerDefault.max
1493 && customServoMixer.box == customServoMixerDefault.box;
1495 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1497 customServoMixerDefault.targetChannel,
1498 customServoMixerDefault.inputSource,
1499 customServoMixerDefault.rate,
1500 customServoMixerDefault.speed,
1501 customServoMixerDefault.min,
1502 customServoMixerDefault.max,
1503 customServoMixerDefault.box
1506 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1508 customServoMixer.targetChannel,
1509 customServoMixer.inputSource,
1510 customServoMixer.rate,
1511 customServoMixer.speed,
1512 customServoMixer.min,
1513 customServoMixer.max,
1514 customServoMixer.box
1518 cliPrintLinefeed();
1521 static void cliServoMix(char *cmdline)
1523 int args[8], check = 0;
1524 int len = strlen(cmdline);
1526 if (len == 0) {
1527 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
1528 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1529 // erase custom mixer
1530 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1531 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1532 servoParamsMutable(i)->reversedSources = 0;
1534 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1535 const char *ptr = nextArg(cmdline);
1536 if (ptr) {
1537 len = strlen(ptr);
1538 for (uint32_t i = 0; ; i++) {
1539 if (mixerNames[i] == NULL) {
1540 cliPrintLine("Invalid name");
1541 break;
1543 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1544 servoMixerLoadMix(i);
1545 cliPrintLinef("Loaded %s", mixerNames[i]);
1546 cliServoMix("");
1547 break;
1551 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
1552 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
1553 char *ptr = strchr(cmdline, ' ');
1555 len = strlen(ptr);
1556 if (len == 0) {
1557 cliPrintf("s");
1558 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1559 cliPrintf("\ti%d", inputSource);
1560 cliPrintLinefeed();
1562 for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
1563 cliPrintf("%d", servoIndex);
1564 for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1565 cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
1566 cliPrintLinefeed();
1568 return;
1571 char *saveptr;
1572 ptr = strtok_r(ptr, " ", &saveptr);
1573 while (ptr != NULL && check < ARGS_COUNT - 1) {
1574 args[check++] = atoi(ptr);
1575 ptr = strtok_r(NULL, " ", &saveptr);
1578 if (ptr == NULL || check != ARGS_COUNT - 1) {
1579 cliShowParseError();
1580 return;
1583 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
1584 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
1585 && (*ptr == 'r' || *ptr == 'n')) {
1586 if (*ptr == 'r')
1587 servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
1588 else
1589 servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
1590 } else
1591 cliShowParseError();
1593 cliServoMix("reverse");
1594 } else {
1595 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
1596 char *saveptr;
1597 char *ptr = strtok_r(cmdline, " ", &saveptr);
1598 while (ptr != NULL && check < ARGS_COUNT) {
1599 args[check++] = atoi(ptr);
1600 ptr = strtok_r(NULL, " ", &saveptr);
1603 if (ptr != NULL || check != ARGS_COUNT) {
1604 cliShowParseError();
1605 return;
1608 int32_t i = args[RULE];
1609 if (i >= 0 && i < MAX_SERVO_RULES &&
1610 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1611 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1612 args[RATE] >= -100 && args[RATE] <= 100 &&
1613 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1614 args[MIN] >= 0 && args[MIN] <= 100 &&
1615 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
1616 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
1617 customServoMixersMutable(i)->targetChannel = args[TARGET];
1618 customServoMixersMutable(i)->inputSource = args[INPUT];
1619 customServoMixersMutable(i)->rate = args[RATE];
1620 customServoMixersMutable(i)->speed = args[SPEED];
1621 customServoMixersMutable(i)->min = args[MIN];
1622 customServoMixersMutable(i)->max = args[MAX];
1623 customServoMixersMutable(i)->box = args[BOX];
1624 cliServoMix("");
1625 } else {
1626 cliShowParseError();
1630 #endif
1632 #ifdef USE_SDCARD
1634 static void cliWriteBytes(const uint8_t *buffer, int count)
1636 while (count > 0) {
1637 cliWrite(*buffer);
1638 buffer++;
1639 count--;
1643 static void cliSdInfo(char *cmdline)
1645 UNUSED(cmdline);
1647 cliPrint("SD card: ");
1649 if (!sdcard_isInserted()) {
1650 cliPrintLine("None inserted");
1651 return;
1654 if (!sdcard_isInitialized()) {
1655 cliPrintLine("Startup failed");
1656 return;
1659 const sdcardMetadata_t *metadata = sdcard_getMetadata();
1661 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1662 metadata->manufacturerID,
1663 metadata->numBlocks / 2, /* One block is half a kB */
1664 metadata->productionMonth,
1665 metadata->productionYear,
1666 metadata->productRevisionMajor,
1667 metadata->productRevisionMinor
1670 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
1672 cliPrint("'\r\n" "Filesystem: ");
1674 switch (afatfs_getFilesystemState()) {
1675 case AFATFS_FILESYSTEM_STATE_READY:
1676 cliPrint("Ready");
1677 break;
1678 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
1679 cliPrint("Initializing");
1680 break;
1681 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
1682 case AFATFS_FILESYSTEM_STATE_FATAL:
1683 cliPrint("Fatal");
1685 switch (afatfs_getLastError()) {
1686 case AFATFS_ERROR_BAD_MBR:
1687 cliPrint(" - no FAT MBR partitions");
1688 break;
1689 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
1690 cliPrint(" - bad FAT header");
1691 break;
1692 case AFATFS_ERROR_GENERIC:
1693 case AFATFS_ERROR_NONE:
1694 ; // Nothing more detailed to print
1695 break;
1697 break;
1699 cliPrintLinefeed();
1702 #endif
1704 #ifdef USE_FLASHFS
1706 static void cliFlashInfo(char *cmdline)
1708 const flashGeometry_t *layout = flashfsGetGeometry();
1710 UNUSED(cmdline);
1712 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
1713 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
1717 static void cliFlashErase(char *cmdline)
1719 UNUSED(cmdline);
1721 #ifndef MINIMAL_CLI
1722 uint32_t i = 0;
1723 cliPrintLine("Erasing, please wait ... ");
1724 #else
1725 cliPrintLine("Erasing,");
1726 #endif
1728 bufWriterFlush(cliWriter);
1729 flashfsEraseCompletely();
1731 while (!flashfsIsReady()) {
1732 #ifndef MINIMAL_CLI
1733 cliPrintf(".");
1734 if (i++ > 120) {
1735 i=0;
1736 cliPrintLinefeed();
1739 bufWriterFlush(cliWriter);
1740 #endif
1741 delay(100);
1743 beeper(BEEPER_BLACKBOX_ERASE);
1744 cliPrintLinefeed();
1745 cliPrintLine("Done.");
1748 #ifdef USE_FLASH_TOOLS
1750 static void cliFlashWrite(char *cmdline)
1752 const uint32_t address = atoi(cmdline);
1753 const char *text = strchr(cmdline, ' ');
1755 if (!text) {
1756 cliShowParseError();
1757 } else {
1758 flashfsSeekAbs(address);
1759 flashfsWrite((uint8_t*)text, strlen(text), true);
1760 flashfsFlushSync();
1762 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
1766 static void cliFlashRead(char *cmdline)
1768 uint32_t address = atoi(cmdline);
1770 const char *nextArg = strchr(cmdline, ' ');
1772 if (!nextArg) {
1773 cliShowParseError();
1774 } else {
1775 uint32_t length = atoi(nextArg);
1777 cliPrintLinef("Reading %u bytes at %u:", length, address);
1779 uint8_t buffer[32];
1780 while (length > 0) {
1781 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
1783 for (int i = 0; i < bytesRead; i++) {
1784 cliWrite(buffer[i]);
1787 length -= bytesRead;
1788 address += bytesRead;
1790 if (bytesRead == 0) {
1791 //Assume we reached the end of the volume or something fatal happened
1792 break;
1795 cliPrintLinefeed();
1799 #endif
1800 #endif
1802 #ifdef VTX_CONTROL
1803 static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault)
1805 // print out vtx channel settings
1806 const char *format = "vtx %u %u %u %u %u %u";
1807 bool equalsDefault = false;
1808 for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
1809 const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
1810 if (vtxConfigDefault) {
1811 const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
1812 equalsDefault = cac->auxChannelIndex == cacDefault->auxChannelIndex
1813 && cac->band == cacDefault->band
1814 && cac->channel == cacDefault->channel
1815 && cac->range.startStep == cacDefault->range.startStep
1816 && cac->range.endStep == cacDefault->range.endStep;
1817 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1819 cacDefault->auxChannelIndex,
1820 cacDefault->band,
1821 cacDefault->channel,
1822 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
1823 MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
1826 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1828 cac->auxChannelIndex,
1829 cac->band,
1830 cac->channel,
1831 MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
1832 MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
1837 // FIXME remove these and use the VTX API
1838 #define VTX_BAND_MIN 1
1839 #define VTX_BAND_MAX 5
1840 #define VTX_CHANNEL_MIN 1
1841 #define VTX_CHANNEL_MAX 8
1843 static void cliVtx(char *cmdline)
1845 int i, val = 0;
1846 const char *ptr;
1848 if (isEmpty(cmdline)) {
1849 printVtx(DUMP_MASTER, vtxConfig(), NULL);
1850 } else {
1851 ptr = cmdline;
1852 i = atoi(ptr++);
1853 if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
1854 vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
1855 uint8_t validArgumentCount = 0;
1856 ptr = nextArg(ptr);
1857 if (ptr) {
1858 val = atoi(ptr);
1859 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1860 cac->auxChannelIndex = val;
1861 validArgumentCount++;
1864 ptr = nextArg(ptr);
1865 if (ptr) {
1866 val = atoi(ptr);
1867 // FIXME Use VTX API to get min/max
1868 if (val >= VTX_BAND_MIN && val <= VTX_BAND_MAX) {
1869 cac->band = val;
1870 validArgumentCount++;
1873 ptr = nextArg(ptr);
1874 if (ptr) {
1875 val = atoi(ptr);
1876 // FIXME Use VTX API to get min/max
1877 if (val >= VTX_CHANNEL_MIN && val <= VTX_CHANNEL_MAX) {
1878 cac->channel = val;
1879 validArgumentCount++;
1882 ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
1884 if (validArgumentCount != 5) {
1885 memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
1887 } else {
1888 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
1893 #endif // VTX_CONTROL
1895 static void printName(uint8_t dumpMask, const systemConfig_t *systemConfig)
1897 const bool equalsDefault = strlen(systemConfig->name) == 0;
1898 cliDumpPrintLinef(dumpMask, equalsDefault, "name %s", equalsDefault ? emptyName : systemConfig->name);
1901 static void cliName(char *cmdline)
1903 const uint32_t len = strlen(cmdline);
1904 if (len > 0) {
1905 memset(systemConfigMutable()->name, 0, ARRAYLEN(systemConfig()->name));
1906 if (strncmp(cmdline, emptyName, len)) {
1907 strncpy(systemConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
1910 printName(DUMP_MASTER, systemConfig());
1913 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
1915 const uint32_t mask = featureConfig->enabledFeatures;
1916 const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
1917 for (uint32_t i = 0; featureNames[i]; i++) { // disable all feature first
1918 const char *format = "feature -%s";
1919 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
1920 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
1922 for (uint32_t i = 0; featureNames[i]; i++) { // reenable what we want.
1923 const char *format = "feature %s";
1924 if (defaultMask & (1 << i)) {
1925 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
1927 if (mask & (1 << i)) {
1928 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
1933 static void cliFeature(char *cmdline)
1935 uint32_t len = strlen(cmdline);
1936 uint32_t mask = featureMask();
1938 if (len == 0) {
1939 cliPrint("Enabled: ");
1940 for (uint32_t i = 0; ; i++) {
1941 if (featureNames[i] == NULL)
1942 break;
1943 if (mask & (1 << i))
1944 cliPrintf("%s ", featureNames[i]);
1946 cliPrintLinefeed();
1947 } else if (strncasecmp(cmdline, "list", len) == 0) {
1948 cliPrint("Available:");
1949 for (uint32_t i = 0; ; i++) {
1950 if (featureNames[i] == NULL)
1951 break;
1952 cliPrintf(" %s", featureNames[i]);
1954 cliPrintLinefeed();
1955 return;
1956 } else {
1957 bool remove = false;
1958 if (cmdline[0] == '-') {
1959 // remove feature
1960 remove = true;
1961 cmdline++; // skip over -
1962 len--;
1965 for (uint32_t i = 0; ; i++) {
1966 if (featureNames[i] == NULL) {
1967 cliPrintLine("Invalid name");
1968 break;
1971 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
1973 mask = 1 << i;
1974 #ifndef GPS
1975 if (mask & FEATURE_GPS) {
1976 cliPrintLine("unavailable");
1977 break;
1979 #endif
1980 #ifndef SONAR
1981 if (mask & FEATURE_SONAR) {
1982 cliPrintLine("unavailable");
1983 break;
1985 #endif
1986 if (remove) {
1987 featureClear(mask);
1988 cliPrint("Disabled");
1989 } else {
1990 featureSet(mask);
1991 cliPrint("Enabled");
1993 cliPrintLinef(" %s", featureNames[i]);
1994 break;
2000 #ifdef BEEPER
2001 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
2003 const uint8_t beeperCount = beeperTableEntryCount();
2004 const uint32_t mask = beeperConfig->beeper_off_flags;
2005 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
2006 for (int32_t i = 0; i < beeperCount - 2; i++) {
2007 const char *formatOff = "beeper -%s";
2008 const char *formatOn = "beeper %s";
2009 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOn : formatOff, beeperNameForTableIndex(i));
2010 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOff : formatOn, beeperNameForTableIndex(i));
2014 static void cliBeeper(char *cmdline)
2016 uint32_t len = strlen(cmdline);
2017 uint8_t beeperCount = beeperTableEntryCount();
2018 uint32_t mask = getBeeperOffMask();
2020 if (len == 0) {
2021 cliPrintf("Disabled:");
2022 for (int32_t i = 0; ; i++) {
2023 if (i == beeperCount - 2){
2024 if (mask == 0)
2025 cliPrint(" none");
2026 break;
2028 if (mask & (1 << i))
2029 cliPrintf(" %s", beeperNameForTableIndex(i));
2031 cliPrintLinefeed();
2032 } else if (strncasecmp(cmdline, "list", len) == 0) {
2033 cliPrint("Available:");
2034 for (uint32_t i = 0; i < beeperCount; i++)
2035 cliPrintf(" %s", beeperNameForTableIndex(i));
2036 cliPrintLinefeed();
2037 return;
2038 } else {
2039 bool remove = false;
2040 if (cmdline[0] == '-') {
2041 remove = true; // this is for beeper OFF condition
2042 cmdline++;
2043 len--;
2046 for (uint32_t i = 0; ; i++) {
2047 if (i == beeperCount) {
2048 cliPrintLine("Invalid name");
2049 break;
2051 if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
2052 if (remove) { // beeper off
2053 if (i == BEEPER_ALL-1)
2054 beeperOffSetAll(beeperCount-2);
2055 else
2056 if (i == BEEPER_PREFERENCE-1)
2057 setBeeperOffMask(getPreferredBeeperOffMask());
2058 else {
2059 mask = 1 << i;
2060 beeperOffSet(mask);
2062 cliPrint("Disabled");
2064 else { // beeper on
2065 if (i == BEEPER_ALL-1)
2066 beeperOffClearAll();
2067 else
2068 if (i == BEEPER_PREFERENCE-1)
2069 setPreferredBeeperOffMask(getBeeperOffMask());
2070 else {
2071 mask = 1 << i;
2072 beeperOffClear(mask);
2074 cliPrint("Enabled");
2076 cliPrintLinef(" %s", beeperNameForTableIndex(i));
2077 break;
2082 #endif
2084 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
2086 bool equalsDefault = true;
2087 char buf[16];
2088 char bufDefault[16];
2089 uint32_t i;
2090 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
2091 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
2092 if (defaultRxConfig) {
2093 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
2094 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
2097 buf[i] = '\0';
2099 const char *formatMap = "map %s";
2100 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
2101 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
2104 static void cliMap(char *cmdline)
2106 uint32_t len;
2107 char out[9];
2109 len = strlen(cmdline);
2111 if (len == 8) {
2112 // uppercase it
2113 for (uint32_t i = 0; i < 8; i++)
2114 cmdline[i] = toupper((unsigned char)cmdline[i]);
2115 for (uint32_t i = 0; i < 8; i++) {
2116 if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i]))
2117 continue;
2118 cliShowParseError();
2119 return;
2121 parseRcChannels(cmdline, rxConfigMutable());
2123 cliPrint("Map: ");
2124 uint32_t i;
2125 for (i = 0; i < 8; i++)
2126 out[rxConfig()->rcmap[i]] = rcChannelLetters[i];
2127 out[i] = '\0';
2128 cliPrintLine(out);
2131 static char *checkCommand(char *cmdLine, const char *command)
2133 if(!strncasecmp(cmdLine, command, strlen(command)) // command names match
2134 && (isspace((unsigned)cmdLine[strlen(command)]) || cmdLine[strlen(command)] == 0)) {
2135 return cmdLine + strlen(command) + 1;
2136 } else {
2137 return 0;
2141 static void cliRebootEx(bool bootLoader)
2143 cliPrint("\r\nRebooting");
2144 bufWriterFlush(cliWriter);
2145 waitForSerialPortToFinishTransmitting(cliPort);
2146 stopPwmAllMotors();
2147 if (bootLoader) {
2148 systemResetToBootloader();
2149 return;
2151 systemReset();
2154 static void cliReboot(void)
2156 cliRebootEx(false);
2159 static void cliBootloader(char *cmdLine)
2161 UNUSED(cmdLine);
2163 cliPrintHashLine("restarting in bootloader mode");
2164 cliRebootEx(true);
2167 static void cliExit(char *cmdline)
2169 UNUSED(cmdline);
2171 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2172 bufWriterFlush(cliWriter);
2174 *cliBuffer = '\0';
2175 bufferIndex = 0;
2176 cliMode = 0;
2177 // incase a motor was left running during motortest, clear it here
2178 mixerResetDisarmedMotors();
2179 cliReboot();
2181 cliWriter = NULL;
2184 #ifdef GPS
2185 static void cliGpsPassthrough(char *cmdline)
2187 UNUSED(cmdline);
2189 gpsEnablePassthrough(cliPort);
2191 #endif
2193 #if defined(USE_ESCSERIAL) || defined(USE_DSHOT)
2195 #ifndef ALL_ESCS
2196 #define ALL_ESCS 255
2197 #endif
2199 static int parseEscNumber(char *pch, bool allowAllEscs) {
2200 int escNumber = atoi(pch);
2201 if ((escNumber >= 0) && (escNumber < getMotorCount())) {
2202 tfp_printf("Programming on ESC %d.\r\n", escNumber);
2203 } else if (allowAllEscs && escNumber == ALL_ESCS) {
2204 tfp_printf("Programming on all ESCs.\r\n");
2205 } else {
2206 tfp_printf("Invalid ESC number, range: 0 to %d.\r\n", getMotorCount() - 1);
2208 return -1;
2211 return escNumber;
2213 #endif
2215 #ifdef USE_DSHOT
2216 static void cliDshotProg(char *cmdline)
2218 if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
2219 cliShowParseError();
2221 return;
2224 char *saveptr;
2225 char *pch = strtok_r(cmdline, " ", &saveptr);
2226 int pos = 0;
2227 int escNumber = 0;
2228 while (pch != NULL) {
2229 switch (pos) {
2230 case 0:
2231 escNumber = parseEscNumber(pch, true);
2232 if (escNumber == -1) {
2233 return;
2236 break;
2237 default:
2238 motorControlEnable = false;
2240 int command = atoi(pch);
2241 if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
2242 if (escNumber == ALL_ESCS) {
2243 for (unsigned i = 0; i < getMotorCount(); i++) {
2244 pwmWriteDshotCommand(i, command);
2246 } else {
2247 pwmWriteDshotCommand(escNumber, command);
2250 if (command <= 5) {
2251 delay(10); // wait for sound output to finish
2254 tfp_printf("Command %d written.\r\n", command);
2255 } else {
2256 tfp_printf("Invalid command, range 1 to %d.\r\n", DSHOT_MIN_THROTTLE - 1);
2259 break;
2262 pos++;
2263 pch = strtok_r(NULL, " ", &saveptr);
2266 motorControlEnable = true;
2268 #endif
2270 #ifdef USE_ESCSERIAL
2271 static void cliEscPassthrough(char *cmdline)
2273 if (isEmpty(cmdline)) {
2274 cliShowParseError();
2276 return;
2279 char *saveptr;
2280 char *pch = strtok_r(cmdline, " ", &saveptr);
2281 int pos = 0;
2282 uint8_t mode = 0;
2283 int escNumber = 0;
2284 while (pch != NULL) {
2285 switch (pos) {
2286 case 0:
2287 if(strncasecmp(pch, "sk", strlen(pch)) == 0) {
2288 mode = PROTOCOL_SIMONK;
2289 } else if(strncasecmp(pch, "bl", strlen(pch)) == 0) {
2290 mode = PROTOCOL_BLHELI;
2291 } else if(strncasecmp(pch, "ki", strlen(pch)) == 0) {
2292 mode = PROTOCOL_KISS;
2293 } else if(strncasecmp(pch, "cc", strlen(pch)) == 0) {
2294 mode = PROTOCOL_KISSALL;
2295 } else {
2296 cliShowParseError();
2298 return;
2300 break;
2301 case 1:
2302 escNumber = parseEscNumber(pch, mode == PROTOCOL_KISS);
2303 if (escNumber == -1) {
2304 return;
2307 break;
2308 default:
2309 cliShowParseError();
2311 return;
2313 break;
2316 pos++;
2317 pch = strtok_r(NULL, " ", &saveptr);
2320 escEnablePassthrough(cliPort, escNumber, mode);
2322 #endif
2324 #ifndef USE_QUAD_MIXER_ONLY
2325 static void cliMixer(char *cmdline)
2327 int len;
2329 len = strlen(cmdline);
2331 if (len == 0) {
2332 cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
2333 return;
2334 } else if (strncasecmp(cmdline, "list", len) == 0) {
2335 cliPrint("Available:");
2336 for (uint32_t i = 0; ; i++) {
2337 if (mixerNames[i] == NULL)
2338 break;
2339 cliPrintf(" %s", mixerNames[i]);
2341 cliPrintLinefeed();
2342 return;
2345 for (uint32_t i = 0; ; i++) {
2346 if (mixerNames[i] == NULL) {
2347 cliPrintLine("Invalid name");
2348 return;
2350 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
2351 mixerConfigMutable()->mixerMode = i + 1;
2352 break;
2356 cliMixer("");
2358 #endif
2360 static void cliMotor(char *cmdline)
2362 int motor_index = 0;
2363 int motor_value = 0;
2364 int index = 0;
2365 char *pch = NULL;
2366 char *saveptr;
2368 if (isEmpty(cmdline)) {
2369 cliShowParseError();
2370 return;
2373 pch = strtok_r(cmdline, " ", &saveptr);
2374 while (pch != NULL) {
2375 switch (index) {
2376 case 0:
2377 motor_index = atoi(pch);
2378 break;
2379 case 1:
2380 motor_value = atoi(pch);
2381 break;
2383 index++;
2384 pch = strtok_r(NULL, " ", &saveptr);
2387 if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
2388 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
2389 return;
2392 if (index == 2) {
2393 if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
2394 cliShowArgumentRangeError("value", 1000, 2000);
2395 } else {
2396 motor_disarmed[motor_index] = convertExternalToMotor(motor_value);
2398 cliPrintLinef("motor %d: %d", motor_index, convertMotorToExternal(motor_disarmed[motor_index]));
2404 #ifndef MINIMAL_CLI
2405 static void cliPlaySound(char *cmdline)
2407 int i;
2408 const char *name;
2409 static int lastSoundIdx = -1;
2411 if (isEmpty(cmdline)) {
2412 i = lastSoundIdx + 1; //next sound index
2413 if ((name=beeperNameForTableIndex(i)) == NULL) {
2414 while (true) { //no name for index; try next one
2415 if (++i >= beeperTableEntryCount())
2416 i = 0; //if end then wrap around to first entry
2417 if ((name=beeperNameForTableIndex(i)) != NULL)
2418 break; //if name OK then play sound below
2419 if (i == lastSoundIdx + 1) { //prevent infinite loop
2420 cliPrintLine("Error playing sound");
2421 return;
2425 } else { //index value was given
2426 i = atoi(cmdline);
2427 if ((name=beeperNameForTableIndex(i)) == NULL) {
2428 cliPrintLinef("No sound for index %d", i);
2429 return;
2432 lastSoundIdx = i;
2433 beeperSilence();
2434 cliPrintLinef("Playing sound %d: %s", i, name);
2435 beeper(beeperModeForTableIndex(i));
2437 #endif
2439 static void cliProfile(char *cmdline)
2441 if (isEmpty(cmdline)) {
2442 cliPrintLinef("profile %d", getCurrentPidProfileIndex());
2443 return;
2444 } else {
2445 const int i = atoi(cmdline);
2446 if (i >= 0 && i < MAX_PROFILE_COUNT) {
2447 systemConfigMutable()->pidProfileIndex = i;
2448 cliProfile("");
2453 static void cliRateProfile(char *cmdline)
2455 if (isEmpty(cmdline)) {
2456 cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
2457 return;
2458 } else {
2459 const int i = atoi(cmdline);
2460 if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
2461 changeControlRateProfile(i);
2462 cliRateProfile("");
2467 static void cliDumpPidProfile(uint8_t pidProfileIndex, uint8_t dumpMask)
2469 if (pidProfileIndex >= MAX_PROFILE_COUNT) {
2470 // Faulty values
2471 return;
2473 changePidProfile(pidProfileIndex);
2474 cliPrintHashLine("profile");
2475 cliProfile("");
2476 cliPrintLinefeed();
2477 dumpAllValues(PROFILE_VALUE, dumpMask);
2480 static void cliDumpRateProfile(uint8_t rateProfileIndex, uint8_t dumpMask)
2482 if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
2483 // Faulty values
2484 return;
2486 changeControlRateProfile(rateProfileIndex);
2487 cliPrintHashLine("rateprofile");
2488 cliRateProfile("");
2489 cliPrintLinefeed();
2490 dumpAllValues(PROFILE_RATE_VALUE, dumpMask);
2493 static void cliSave(char *cmdline)
2495 UNUSED(cmdline);
2497 cliPrintHashLine("saving");
2498 writeEEPROM();
2499 cliReboot();
2502 static void cliDefaults(char *cmdline)
2504 UNUSED(cmdline);
2506 cliPrintHashLine("resetting to defaults");
2507 resetEEPROM();
2508 cliReboot();
2511 static void cliGet(char *cmdline)
2513 const clivalue_t *val;
2514 int matchedCommands = 0;
2516 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
2517 if (strstr(valueTable[i].name, cmdline)) {
2518 val = &valueTable[i];
2519 cliPrintf("%s = ", valueTable[i].name);
2520 cliPrintVar(val, 0);
2521 cliPrintLinefeed();
2522 cliPrintVarRange(val);
2523 cliPrintLinefeed();
2525 matchedCommands++;
2530 if (matchedCommands) {
2531 return;
2534 cliPrintLine("Invalid name");
2537 static char *skipSpace(char *buffer)
2539 while (*(buffer) == ' ') {
2540 buffer++;
2543 return buffer;
2546 static uint8_t getWordLength(char *bufBegin, char *bufEnd)
2548 while (*(bufEnd - 1) == ' ') {
2549 bufEnd--;
2552 return bufEnd - bufBegin;
2555 static void cliSet(char *cmdline)
2557 const uint32_t len = strlen(cmdline);
2558 char *eqptr;
2560 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
2561 cliPrintLine("Current settings: ");
2563 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
2564 const clivalue_t *val = &valueTable[i];
2565 cliPrintf("%s = ", valueTable[i].name);
2566 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
2567 cliPrintLinefeed();
2569 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
2570 // has equals
2572 uint8_t variableNameLength = getWordLength(cmdline, eqptr);
2574 // skip the '=' and any ' ' characters
2575 eqptr++;
2576 eqptr = skipSpace(eqptr);
2578 for (uint32_t i = 0; i < valueTableEntryCount; i++) {
2579 const clivalue_t *val = &valueTable[i];
2580 // ensure exact match when setting to prevent setting variables with shorter names
2581 if (strncasecmp(cmdline, valueTable[i].name, strlen(valueTable[i].name)) == 0 && variableNameLength == strlen(valueTable[i].name)) {
2583 bool valueChanged = false;
2584 int16_t value = 0;
2585 switch (valueTable[i].type & VALUE_MODE_MASK) {
2586 case MODE_DIRECT: {
2587 int16_t value = atoi(eqptr);
2589 if (value >= valueTable[i].config.minmax.min && value <= valueTable[i].config.minmax.max) {
2590 cliSetVar(val, value);
2591 valueChanged = true;
2595 break;
2596 case MODE_LOOKUP: {
2597 const lookupTableEntry_t *tableEntry = &lookupTables[valueTable[i].config.lookup.tableIndex];
2598 bool matched = false;
2599 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
2600 matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
2602 if (matched) {
2603 value = tableValueIndex;
2605 cliSetVar(val, value);
2606 valueChanged = true;
2611 break;
2612 case MODE_ARRAY: {
2613 const uint8_t arrayLength = valueTable[i].config.array.length;
2614 char *valPtr = eqptr;
2615 uint8_t array[256];
2616 char curVal[4];
2617 for (int i = 0; i < arrayLength; i++) {
2618 valPtr = skipSpace(valPtr);
2619 char *valEnd = strstr(valPtr, ",");
2620 if ((valEnd != NULL) && (i < arrayLength - 1)) {
2621 uint8_t varLength = getWordLength(valPtr, valEnd);
2622 if (varLength <= 3) {
2623 strncpy(curVal, valPtr, getWordLength(valPtr, valEnd));
2624 curVal[varLength] = '\0';
2625 array[i] = (uint8_t)atoi((const char *)curVal);
2626 valPtr = valEnd + 1;
2627 } else {
2628 break;
2630 } else if ((valEnd == NULL) && (i == arrayLength - 1)) {
2631 array[i] = atoi(valPtr);
2633 uint8_t *ptr = getValuePointer(val);
2634 memcpy(ptr, array, arrayLength);
2635 valueChanged = true;
2636 } else {
2637 break;
2642 break;
2646 if (valueChanged) {
2647 cliPrintf("%s set to ", valueTable[i].name);
2648 cliPrintVar(val, 0);
2649 } else {
2650 cliPrintLine("Invalid value");
2651 cliPrintVarRange(val);
2654 return;
2657 cliPrintLine("Invalid name");
2658 } else {
2659 // no equals, check for matching variables.
2660 cliGet(cmdline);
2664 static void cliStatus(char *cmdline)
2666 UNUSED(cmdline);
2668 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
2669 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
2671 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
2673 #if defined(USE_SENSOR_NAMES)
2674 const uint32_t detectedSensorsMask = sensorsMask();
2675 for (uint32_t i = 0; ; i++) {
2676 if (sensorTypeNames[i] == NULL) {
2677 break;
2679 const uint32_t mask = (1 << i);
2680 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
2681 const uint8_t sensorHardwareIndex = detectedSensors[i];
2682 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
2683 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
2684 if (mask == SENSOR_ACC && acc.dev.revisionCode) {
2685 cliPrintf(".%c", acc.dev.revisionCode);
2689 #endif /* USE_SENSOR_NAMES */
2690 cliPrintLinefeed();
2692 #ifdef USE_SDCARD
2693 cliSdInfo(NULL);
2694 #endif
2696 #ifdef USE_I2C
2697 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
2698 #else
2699 const uint16_t i2cErrorCounter = 0;
2700 #endif
2702 #ifdef STACK_CHECK
2703 cliPrintf("Stack used: %d, ", stackUsedSize());
2704 #endif
2705 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
2707 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
2709 const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
2710 const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
2711 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
2712 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
2713 constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
2714 #ifdef MINIMAL_CLI
2715 cliPrintLinef("Arming disable flags: 0x%x", getArmingDisableFlags());
2716 #else
2717 cliPrint("Arming disable flags:");
2718 uint16_t flags = getArmingDisableFlags();
2719 while (flags) {
2720 int bitpos = ffs(flags) - 1;
2721 flags &= ~(1 << bitpos);
2722 cliPrintf(" %s", armingDisableFlagNames[bitpos]);
2724 cliPrintLinefeed();
2725 #endif
2728 #ifndef SKIP_TASK_STATISTICS
2729 static void cliTasks(char *cmdline)
2731 UNUSED(cmdline);
2732 int maxLoadSum = 0;
2733 int averageLoadSum = 0;
2735 #ifndef MINIMAL_CLI
2736 if (systemConfig()->task_statistics) {
2737 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
2738 } else {
2739 cliPrintLine("Task list");
2741 #endif
2742 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
2743 cfTaskInfo_t taskInfo;
2744 getTaskInfo(taskId, &taskInfo);
2745 if (taskInfo.isEnabled) {
2746 int taskFrequency;
2747 int subTaskFrequency = 0;
2748 if (taskId == TASK_GYROPID) {
2749 subTaskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
2750 taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
2751 if (pidConfig()->pid_process_denom > 1) {
2752 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
2753 } else {
2754 taskFrequency = subTaskFrequency;
2755 cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
2757 } else {
2758 taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
2759 cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
2761 const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
2762 const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
2763 if (taskId != TASK_SERIAL) {
2764 maxLoadSum += maxLoad;
2765 averageLoadSum += averageLoad;
2767 if (systemConfig()->task_statistics) {
2768 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
2769 taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
2770 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
2771 } else {
2772 cliPrintLinef("%6d", taskFrequency);
2774 if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
2775 cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
2779 if (systemConfig()->task_statistics) {
2780 cfCheckFuncInfo_t checkFuncInfo;
2781 getCheckFuncInfo(&checkFuncInfo);
2782 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
2783 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
2786 #endif
2788 static void cliVersion(char *cmdline)
2790 UNUSED(cmdline);
2792 cliPrintLinef("# %s / %s %s %s / %s (%s)",
2793 FC_FIRMWARE_NAME,
2794 targetName,
2795 FC_VERSION_STRING,
2796 buildDate,
2797 buildTime,
2798 shortGitRevision
2802 #if defined(USE_RESOURCE_MGMT)
2804 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
2806 typedef struct {
2807 const uint8_t owner;
2808 pgn_t pgn;
2809 uint16_t offset;
2810 const uint8_t maxIndex;
2811 } cliResourceValue_t;
2813 const cliResourceValue_t resourceTable[] = {
2814 #ifdef BEEPER
2815 { OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, offsetof(beeperDevConfig_t, ioTag), 0 },
2816 #endif
2817 { OWNER_MOTOR, PG_MOTOR_CONFIG, offsetof(motorConfig_t, dev.ioTags[0]), MAX_SUPPORTED_MOTORS },
2818 #ifdef USE_SERVOS
2819 { OWNER_SERVO, PG_SERVO_CONFIG, offsetof(servoConfig_t, dev.ioTags[0]), MAX_SUPPORTED_SERVOS },
2820 #endif
2821 #if defined(USE_PWM) || defined(USE_PPM)
2822 { OWNER_PPMINPUT, PG_PPM_CONFIG, offsetof(ppmConfig_t, ioTag), 0 },
2823 { OWNER_PWMINPUT, PG_PWM_CONFIG, offsetof(pwmConfig_t, ioTags[0]), PWM_INPUT_PORT_COUNT },
2824 #endif
2825 #ifdef SONAR
2826 { OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, offsetof(sonarConfig_t, triggerTag), 0 },
2827 { OWNER_SONAR_ECHO, PG_SONAR_CONFIG, offsetof(sonarConfig_t, echoTag), 0 },
2828 #endif
2829 #ifdef LED_STRIP
2830 { OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, offsetof(ledStripConfig_t, ioTag), 0 },
2831 #endif
2832 { OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagTx[0]), SERIAL_PORT_MAX_INDEX },
2833 { OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagRx[0]), SERIAL_PORT_MAX_INDEX },
2834 #ifdef USE_INVERTER
2835 { OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagInverter[0]), SERIAL_PORT_MAX_INDEX },
2836 #endif
2837 #ifdef USE_I2C
2838 { OWNER_I2C_SCL, PG_I2C_CONFIG, offsetof(i2cConfig_t, ioTagScl[0]), I2CDEV_COUNT },
2839 { OWNER_I2C_SDA, PG_I2C_CONFIG, offsetof(i2cConfig_t, ioTagSda[0]), I2CDEV_COUNT },
2840 #endif
2841 { OWNER_LED, PG_STATUS_LED_CONFIG, offsetof(statusLedConfig_t, ioTags[0]), STATUS_LED_NUMBER },
2842 #ifdef USE_SPEKTRUM_BIND
2843 { OWNER_RX_BIND, PG_RX_CONFIG, offsetof(rxConfig_t, spektrum_bind_pin_override_ioTag), 0 },
2844 { OWNER_RX_BIND_PLUG, PG_RX_CONFIG, offsetof(rxConfig_t, spektrum_bind_plug_ioTag), 0 },
2845 #endif
2848 static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
2850 const pgRegistry_t* rec = pgFind(value.pgn);
2851 return CONST_CAST(ioTag_t *, rec->address + value.offset + index);
2854 static void printResource(uint8_t dumpMask)
2856 for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
2857 const char* owner = ownerNames[resourceTable[i].owner];
2858 const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
2859 const void *currentConfig;
2860 const void *defaultConfig;
2861 if (configIsInCopy) {
2862 currentConfig = pg->copy;
2863 defaultConfig = pg->address;
2864 } else {
2865 currentConfig = pg->address;
2866 defaultConfig = NULL;
2869 for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
2870 const ioTag_t ioTag = *((const ioTag_t *)currentConfig + resourceTable[i].offset + index);
2871 const ioTag_t ioTagDefault = *((const ioTag_t *)defaultConfig + resourceTable[i].offset + index);
2873 bool equalsDefault = ioTag == ioTagDefault;
2874 const char *format = "resource %s %d %c%02d";
2875 const char *formatUnassigned = "resource %s %d NONE";
2876 if (!ioTagDefault) {
2877 cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
2878 } else {
2879 cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
2881 if (!ioTag) {
2882 if (!(dumpMask & HIDE_UNUSED)) {
2883 cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
2885 } else {
2886 cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
2892 static void printResourceOwner(uint8_t owner, uint8_t index)
2894 cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
2896 if (resourceTable[owner].maxIndex > 0) {
2897 cliPrintf(" %d", RESOURCE_INDEX(index));
2901 static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
2903 if (!newTag) {
2904 return;
2907 const char * format = "\r\nNOTE: %c%02d already assigned to ";
2908 for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
2909 for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
2910 ioTag_t *tag = getIoTag(resourceTable[r], i);
2911 if (*tag == newTag) {
2912 bool cleared = false;
2913 if (r == resourceIndex) {
2914 if (i == index) {
2915 continue;
2917 *tag = IO_TAG_NONE;
2918 cleared = true;
2921 cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
2923 printResourceOwner(r, i);
2925 if (cleared) {
2926 cliPrintf(". ");
2927 printResourceOwner(r, i);
2928 cliPrintf(" disabled");
2931 cliPrintLine(".");
2937 static void cliResource(char *cmdline)
2939 int len = strlen(cmdline);
2941 if (len == 0) {
2942 printResource(DUMP_MASTER | HIDE_UNUSED);
2944 return;
2945 } else if (strncasecmp(cmdline, "list", len) == 0) {
2946 #ifdef MINIMAL_CLI
2947 cliPrintLine("IO");
2948 #else
2949 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
2950 cliRepeat('-', 20);
2951 #endif
2952 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
2953 const char* owner;
2954 owner = ownerNames[ioRecs[i].owner];
2956 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
2957 if (ioRecs[i].index > 0) {
2958 cliPrintf(" %d", ioRecs[i].index);
2960 cliPrintLinefeed();
2963 cliPrintLinefeed();
2965 #ifdef MINIMAL_CLI
2966 cliPrintLine("DMA:");
2967 #else
2968 cliPrintLine("Currently active DMA:");
2969 cliRepeat('-', 20);
2970 #endif
2971 for (int i = 0; i < DMA_MAX_DESCRIPTORS; i++) {
2972 const char* owner;
2973 owner = ownerNames[dmaGetOwner(i)];
2975 cliPrintf(DMA_OUTPUT_STRING, i / DMA_MOD_VALUE + 1, (i % DMA_MOD_VALUE) + DMA_MOD_OFFSET);
2976 uint8_t resourceIndex = dmaGetResourceIndex(i);
2977 if (resourceIndex > 0) {
2978 cliPrintLinef(" %s %d", owner, resourceIndex);
2979 } else {
2980 cliPrintLinef(" %s", owner);
2984 #ifndef MINIMAL_CLI
2985 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
2986 #endif
2988 return;
2991 uint8_t resourceIndex = 0;
2992 int index = 0;
2993 char *pch = NULL;
2994 char *saveptr;
2996 pch = strtok_r(cmdline, " ", &saveptr);
2997 for (resourceIndex = 0; ; resourceIndex++) {
2998 if (resourceIndex >= ARRAYLEN(resourceTable)) {
2999 cliPrintLine("Invalid");
3000 return;
3003 if (strncasecmp(pch, ownerNames[resourceTable[resourceIndex].owner], len) == 0) {
3004 break;
3008 pch = strtok_r(NULL, " ", &saveptr);
3009 index = atoi(pch);
3011 if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
3012 if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
3013 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
3014 return;
3016 index -= 1;
3018 pch = strtok_r(NULL, " ", &saveptr);
3021 ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
3023 uint8_t pin = 0;
3024 if (strlen(pch) > 0) {
3025 if (strcasecmp(pch, "NONE") == 0) {
3026 *tag = IO_TAG_NONE;
3027 #ifdef MINIMAL_CLI
3028 cliPrintLine("Freed");
3029 #else
3030 cliPrintLine("Resource is freed");
3031 #endif
3032 return;
3033 } else {
3034 uint8_t port = (*pch) - 'A';
3035 if (port >= 8) {
3036 port = (*pch) - 'a';
3039 if (port < 8) {
3040 pch++;
3041 pin = atoi(pch);
3042 if (pin < 16) {
3043 ioRec_t *rec = IO_Rec(IOGetByTag(DEFIO_TAG_MAKE(port, pin)));
3044 if (rec) {
3045 resourceCheck(resourceIndex, index, DEFIO_TAG_MAKE(port, pin));
3046 #ifdef MINIMAL_CLI
3047 cliPrintLinef(" %c%02d set", port + 'A', pin);
3048 #else
3049 cliPrintLinef("\r\nResource is set to %c%02d", port + 'A', pin);
3050 #endif
3051 *tag = DEFIO_TAG_MAKE(port, pin);
3052 } else {
3053 cliShowParseError();
3055 return;
3061 cliShowParseError();
3063 #endif /* USE_RESOURCE_MGMT */
3065 static void backupConfigs(void)
3067 // make copies of configs to do differencing
3068 PG_FOREACH(pg) {
3069 memcpy(pg->copy, pg->address, pg->size);
3072 configIsInCopy = true;
3075 static void restoreConfigs(void)
3077 PG_FOREACH(pg) {
3078 memcpy(pg->address, pg->copy, pg->size);
3081 configIsInCopy = false;
3084 static void printConfig(char *cmdline, bool doDiff)
3086 uint8_t dumpMask = DUMP_MASTER;
3087 char *options;
3088 if ((options = checkCommand(cmdline, "master"))) {
3089 dumpMask = DUMP_MASTER; // only
3090 } else if ((options = checkCommand(cmdline, "profile"))) {
3091 dumpMask = DUMP_PROFILE; // only
3092 } else if ((options = checkCommand(cmdline, "rates"))) {
3093 dumpMask = DUMP_RATES; // only
3094 } else if ((options = checkCommand(cmdline, "all"))) {
3095 dumpMask = DUMP_ALL; // all profiles and rates
3096 } else {
3097 options = cmdline;
3100 if (doDiff) {
3101 dumpMask = dumpMask | DO_DIFF;
3104 backupConfigs();
3105 // reset all configs to defaults to do differencing
3106 resetConfigs();
3108 #if defined(TARGET_CONFIG)
3109 targetConfiguration();
3110 #endif
3111 if (checkCommand(options, "defaults")) {
3112 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
3115 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
3116 cliPrintHashLine("version");
3117 cliVersion(NULL);
3119 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
3120 cliPrintHashLine("reset configuration to default settings");
3121 cliPrint("defaults");
3122 cliPrintLinefeed();
3125 cliPrintHashLine("name");
3126 printName(dumpMask, &systemConfig_Copy);
3128 #ifdef USE_RESOURCE_MGMT
3129 cliPrintHashLine("resources");
3130 printResource(dumpMask);
3131 #endif
3133 #ifndef USE_QUAD_MIXER_ONLY
3134 cliPrintHashLine("mixer");
3135 const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
3136 const char *formatMixer = "mixer %s";
3137 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
3138 cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
3140 cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
3142 printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0));
3144 #ifdef USE_SERVOS
3145 cliPrintHashLine("servo");
3146 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
3148 cliPrintHashLine("servo mix");
3149 // print custom servo mixer if exists
3150 cliDumpPrintLinef(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n");
3151 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
3152 #endif
3153 #endif
3155 cliPrintHashLine("feature");
3156 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
3158 #ifdef BEEPER
3159 cliPrintHashLine("beeper");
3160 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
3161 #endif
3163 cliPrintHashLine("map");
3164 printMap(dumpMask, &rxConfig_Copy, rxConfig());
3166 cliPrintHashLine("serial");
3167 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
3169 #ifdef LED_STRIP
3170 cliPrintHashLine("led");
3171 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
3173 cliPrintHashLine("color");
3174 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
3176 cliPrintHashLine("mode_color");
3177 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
3178 #endif
3180 cliPrintHashLine("aux");
3181 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
3183 cliPrintHashLine("adjrange");
3184 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
3186 cliPrintHashLine("rxrange");
3187 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
3189 #ifdef VTX_CONTROL
3190 cliPrintHashLine("vtx");
3191 printVtx(dumpMask, &vtxConfig_Copy, vtxConfig());
3192 #endif
3194 cliPrintHashLine("rxfail");
3195 printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0));
3197 cliPrintHashLine("master");
3198 dumpAllValues(MASTER_VALUE, dumpMask);
3200 if (dumpMask & DUMP_ALL) {
3201 const uint8_t pidProfileIndexSave = systemConfig_Copy.pidProfileIndex;
3202 for (uint32_t pidProfileIndex = 0; pidProfileIndex < MAX_PROFILE_COUNT; pidProfileIndex++) {
3203 cliDumpPidProfile(pidProfileIndex, dumpMask);
3205 changePidProfile(pidProfileIndexSave);
3206 cliPrintHashLine("restore original profile selection");
3207 cliProfile("");
3209 const uint8_t controlRateProfileIndexSave = systemConfig_Copy.activeRateProfile;
3210 for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
3211 cliDumpRateProfile(rateIndex, dumpMask);
3213 changeControlRateProfile(controlRateProfileIndexSave);
3214 cliPrintHashLine("restore original rateprofile selection");
3215 cliRateProfile("");
3217 cliPrintHashLine("save configuration");
3218 cliPrint("save");
3219 } else {
3220 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
3222 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
3226 if (dumpMask & DUMP_PROFILE) {
3227 cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
3230 if (dumpMask & DUMP_RATES) {
3231 cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
3233 // restore configs from copies
3234 restoreConfigs();
3237 static void cliDump(char *cmdline)
3239 printConfig(cmdline, false);
3242 static void cliDiff(char *cmdline)
3244 printConfig(cmdline, true);
3247 typedef struct {
3248 const char *name;
3249 #ifndef MINIMAL_CLI
3250 const char *description;
3251 const char *args;
3252 #endif
3253 void (*func)(char *cmdline);
3254 } clicmd_t;
3256 #ifndef MINIMAL_CLI
3257 #define CLI_COMMAND_DEF(name, description, args, method) \
3259 name , \
3260 description , \
3261 args , \
3262 method \
3264 #else
3265 #define CLI_COMMAND_DEF(name, description, args, method) \
3267 name, \
3268 method \
3270 #endif
3272 static void cliHelp(char *cmdline);
3274 // should be sorted a..z for bsearch()
3275 const clicmd_t cmdTable[] = {
3276 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
3277 CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
3278 #ifdef BEEPER
3279 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3280 "\t<+|->[name]", cliBeeper),
3281 #endif
3282 #ifdef LED_STRIP
3283 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
3284 #endif
3285 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
3286 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL, cliBootloader),
3287 CLI_COMMAND_DEF("diff", "list configuration changes from default",
3288 "[master|profile|rates|all] {showdefaults}", cliDiff),
3289 #ifdef USE_DSHOT
3290 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
3291 #endif
3292 CLI_COMMAND_DEF("dump", "dump configuration",
3293 "[master|profile|rates|all] {showdefaults}", cliDump),
3294 #ifdef USE_ESCSERIAL
3295 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
3296 #endif
3297 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
3298 CLI_COMMAND_DEF("feature", "configure features",
3299 "list\r\n"
3300 "\t<+|->[name]", cliFeature),
3301 #ifdef USE_FLASHFS
3302 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
3303 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
3304 #ifdef USE_FLASH_TOOLS
3305 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
3306 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
3307 #endif
3308 #endif
3309 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
3310 #ifdef GPS
3311 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
3312 #endif
3313 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
3314 #ifdef LED_STRIP
3315 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
3316 #endif
3317 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
3318 #ifndef USE_QUAD_MIXER_ONLY
3319 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
3320 #endif
3321 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
3322 #ifdef LED_STRIP
3323 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
3324 #endif
3325 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
3326 CLI_COMMAND_DEF("name", "name of craft", NULL, cliName),
3327 #ifndef MINIMAL_CLI
3328 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
3329 #endif
3330 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
3331 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
3332 #if defined(USE_RESOURCE_MGMT)
3333 CLI_COMMAND_DEF("resource", "show/set resources", NULL, cliResource),
3334 #endif
3335 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
3336 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
3337 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
3338 #ifdef USE_SDCARD
3339 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
3340 #endif
3341 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
3342 #ifndef SKIP_SERIAL_PASSTHROUGH
3343 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough),
3344 #endif
3345 #ifdef USE_SERVOS
3346 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
3347 #endif
3348 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
3349 #ifdef USE_SERVOS
3350 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
3351 "\treset\r\n"
3352 "\tload <mixer>\r\n"
3353 "\treverse <servo> <source> r|n", cliServoMix),
3354 #endif
3355 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
3356 #ifndef SKIP_TASK_STATISTICS
3357 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
3358 #endif
3359 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
3360 #ifdef VTX_CONTROL
3361 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
3362 #endif
3364 static void cliHelp(char *cmdline)
3366 UNUSED(cmdline);
3368 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
3369 cliPrint(cmdTable[i].name);
3370 #ifndef MINIMAL_CLI
3371 if (cmdTable[i].description) {
3372 cliPrintf(" - %s", cmdTable[i].description);
3374 if (cmdTable[i].args) {
3375 cliPrintf("\r\n\t%s", cmdTable[i].args);
3377 #endif
3378 cliPrintLinefeed();
3382 void cliProcess(void)
3384 if (!cliWriter) {
3385 return;
3388 // Be a little bit tricky. Flush the last inputs buffer, if any.
3389 bufWriterFlush(cliWriter);
3391 while (serialRxBytesWaiting(cliPort)) {
3392 uint8_t c = serialRead(cliPort);
3393 if (c == '\t' || c == '?') {
3394 // do tab completion
3395 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
3396 uint32_t i = bufferIndex;
3397 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
3398 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
3399 continue;
3400 if (!pstart)
3401 pstart = cmd;
3402 pend = cmd;
3404 if (pstart) { /* Buffer matches one or more commands */
3405 for (; ; bufferIndex++) {
3406 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
3407 break;
3408 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
3409 /* Unambiguous -- append a space */
3410 cliBuffer[bufferIndex++] = ' ';
3411 cliBuffer[bufferIndex] = '\0';
3412 break;
3414 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
3417 if (!bufferIndex || pstart != pend) {
3418 /* Print list of ambiguous matches */
3419 cliPrint("\r\033[K");
3420 for (cmd = pstart; cmd <= pend; cmd++) {
3421 cliPrint(cmd->name);
3422 cliWrite('\t');
3424 cliPrompt();
3425 i = 0; /* Redraw prompt */
3427 for (; i < bufferIndex; i++)
3428 cliWrite(cliBuffer[i]);
3429 } else if (!bufferIndex && c == 4) { // CTRL-D
3430 cliExit(cliBuffer);
3431 return;
3432 } else if (c == 12) { // NewPage / CTRL-L
3433 // clear screen
3434 cliPrint("\033[2J\033[1;1H");
3435 cliPrompt();
3436 } else if (bufferIndex && (c == '\n' || c == '\r')) {
3437 // enter pressed
3438 cliPrintLinefeed();
3440 // Strip comment starting with # from line
3441 char *p = cliBuffer;
3442 p = strchr(p, '#');
3443 if (NULL != p) {
3444 bufferIndex = (uint32_t)(p - cliBuffer);
3447 // Strip trailing whitespace
3448 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
3449 bufferIndex--;
3452 // Process non-empty lines
3453 if (bufferIndex > 0) {
3454 cliBuffer[bufferIndex] = 0; // null terminate
3456 const clicmd_t *cmd;
3457 char *options;
3458 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
3459 if ((options = checkCommand(cliBuffer, cmd->name))) {
3460 break;
3463 if(cmd < cmdTable + ARRAYLEN(cmdTable))
3464 cmd->func(options);
3465 else
3466 cliPrint("Unknown command, try 'help'");
3467 bufferIndex = 0;
3470 memset(cliBuffer, 0, sizeof(cliBuffer));
3472 // 'exit' will reset this flag, so we don't need to print prompt again
3473 if (!cliMode)
3474 return;
3476 cliPrompt();
3477 } else if (c == 127) {
3478 // backspace
3479 if (bufferIndex) {
3480 cliBuffer[--bufferIndex] = 0;
3481 cliPrint("\010 \010");
3483 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
3484 if (!bufferIndex && c == ' ')
3485 continue; // Ignore leading spaces
3486 cliBuffer[bufferIndex++] = c;
3487 cliWrite(c);
3492 void cliEnter(serialPort_t *serialPort)
3494 cliMode = 1;
3495 cliPort = serialPort;
3496 setPrintfSerialPort(cliPort);
3497 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
3499 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics);
3501 #ifndef MINIMAL_CLI
3502 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
3503 #else
3504 cliPrintLine("\r\nCLI");
3505 #endif
3506 cliPrompt();
3508 setArmingDisabled(ARMING_DISABLED_CLI);
3511 void cliInit(const serialConfig_t *serialConfig)
3513 UNUSED(serialConfig);
3515 #endif // USE_CLI