[CLI] Add VTX status
[inav.git] / src / main / fc / cli.c
bloba88d67ecc8e11bfda68219b5550b18e46bb09f88
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 uint8_t cliMode = 0;
29 extern uint8_t __config_start; // configured via linker script when building binaries.
30 extern uint8_t __config_end;
32 #include "blackbox/blackbox.h"
34 #include "build/assert.h"
35 #include "build/build_config.h"
36 #include "build/version.h"
38 #include "common/axis.h"
39 #include "common/color.h"
40 #include "common/maths.h"
41 #include "common/printf.h"
42 #include "common/string_light.h"
43 #include "common/memory.h"
44 #include "common/time.h"
45 #include "common/typeconversion.h"
46 #include "common/global_functions.h"
48 #include "config/config_eeprom.h"
49 #include "config/feature.h"
50 #include "config/parameter_group.h"
51 #include "config/parameter_group_ids.h"
53 #include "drivers/accgyro/accgyro.h"
54 #include "drivers/pwm_mapping.h"
55 #include "drivers/buf_writer.h"
56 #include "drivers/bus_i2c.h"
57 #include "drivers/compass/compass.h"
58 #include "drivers/io.h"
59 #include "drivers/io_impl.h"
60 #include "drivers/osd_symbols.h"
61 #include "drivers/rx_pwm.h"
62 #include "drivers/sdcard/sdcard.h"
63 #include "drivers/sensor.h"
64 #include "drivers/serial.h"
65 #include "drivers/stack_check.h"
66 #include "drivers/system.h"
67 #include "drivers/time.h"
68 #include "drivers/timer.h"
69 #include "drivers/usb_msc.h"
70 #include "drivers/vtx_common.h"
72 #include "fc/fc_core.h"
73 #include "fc/cli.h"
74 #include "fc/config.h"
75 #include "fc/controlrate_profile.h"
76 #include "fc/rc_adjustments.h"
77 #include "fc/rc_controls.h"
78 #include "fc/rc_modes.h"
79 #include "fc/runtime_config.h"
80 #include "fc/settings.h"
82 #include "flight/failsafe.h"
83 #include "flight/imu.h"
84 #include "flight/mixer.h"
85 #include "flight/pid.h"
86 #include "flight/servos.h"
88 #include "io/asyncfatfs/asyncfatfs.h"
89 #include "io/beeper.h"
90 #include "io/flashfs.h"
91 #include "io/gps.h"
92 #include "io/ledstrip.h"
93 #include "io/osd.h"
94 #include "io/serial.h"
96 #include "navigation/navigation.h"
97 #include "navigation/navigation_private.h"
99 #include "rx/rx.h"
100 #include "rx/spektrum.h"
101 #include "rx/eleres.h"
103 #include "scheduler/scheduler.h"
105 #include "sensors/acceleration.h"
106 #include "sensors/barometer.h"
107 #include "sensors/battery.h"
108 #include "sensors/boardalignment.h"
109 #include "sensors/compass.h"
110 #include "sensors/diagnostics.h"
111 #include "sensors/gyro.h"
112 #include "sensors/pitotmeter.h"
113 #include "sensors/rangefinder.h"
114 #include "sensors/opflow.h"
115 #include "sensors/sensors.h"
116 #include "sensors/temperature.h"
118 #include "telemetry/frsky_d.h"
119 #include "telemetry/telemetry.h"
120 #include "build/debug.h"
122 #if FLASH_SIZE > 128
123 #define PLAY_SOUND
124 #endif
126 extern timeDelta_t cycleTime; // FIXME dependency on mw.c
127 extern uint8_t detectedSensors[SENSOR_INDEX_COUNT];
129 static serialPort_t *cliPort;
131 static bufWriter_t *cliWriter;
132 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + 128];
134 static char cliBuffer[64];
135 static uint32_t bufferIndex = 0;
137 #if defined(USE_ASSERT)
138 static void cliAssert(char *cmdline);
139 #endif
141 #ifdef USE_CLI_BATCH
142 static bool commandBatchActive = false;
143 static bool commandBatchError = false;
144 #endif
146 // sync this with features_e
147 static const char * const featureNames[] = {
148 "THR_VBAT_COMP", "VBAT", "TX_PROF_SEL", "BAT_PROF_AUTOSWITCH", "MOTOR_STOP",
149 "DYNAMIC_FILTERS", "SOFTSERIAL", "GPS", "RPM_FILTERS",
150 "", "TELEMETRY", "CURRENT_METER", "REVERSIBLE_MOTORS", "",
151 "", "RSSI_ADC", "LED_STRIP", "DASHBOARD", "",
152 "BLACKBOX", "", "TRANSPONDER", "AIRMODE",
153 "SUPEREXPO", "VTX", "", "", "PWM_SERVO_DRIVER", "PWM_OUTPUT_ENABLE",
154 "OSD", "FW_LAUNCH", NULL
157 /* Sensor names (used in lookup tables for *_hardware settings and in status command output) */
158 // sync with gyroSensor_e
159 static const char * const gyroNames[] = { "NONE", "AUTO", "MPU6050", "L3G4200D", "MPU3050", "L3GD20", "MPU6000", "MPU6500", "MPU9250", "BMI160", "ICM20689", "FAKE"};
161 // sync this with sensors_e
162 static const char * const sensorTypeNames[] = {
163 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "PITOT", "OPFLOW", "GPS", "GPS+MAG", NULL
166 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER | SENSOR_PITOT | SENSOR_OPFLOW)
168 static const char * const hardwareSensorStatusNames[] = {
169 "NONE", "OK", "UNAVAILABLE", "FAILING"
172 static const char * const *sensorHardwareNames[] = {
173 gyroNames,
174 table_acc_hardware,
175 #ifdef USE_BARO
176 table_baro_hardware,
177 #else
178 NULL,
179 #endif
180 #ifdef USE_MAG
181 table_mag_hardware,
182 #else
183 NULL,
184 #endif
185 #ifdef USE_RANGEFINDER
186 table_rangefinder_hardware,
187 #else
188 NULL,
189 #endif
190 #ifdef USE_PITOT
191 table_pitot_hardware,
192 #else
193 NULL,
194 #endif
195 #ifdef USE_OPFLOW
196 table_opflow_hardware,
197 #else
198 NULL,
199 #endif
202 static void cliPrint(const char *str)
204 while (*str) {
205 bufWriterAppend(cliWriter, *str++);
209 static void cliPrintLinefeed(void)
211 cliPrint("\r\n");
214 static void cliPrintLine(const char *str)
216 cliPrint(str);
217 cliPrintLinefeed();
220 static void cliPrintError(const char *str)
222 cliPrint("### ERROR: ");
223 cliPrint(str);
224 #ifdef USE_CLI_BATCH
225 if (commandBatchActive) {
226 commandBatchError = true;
228 #endif
231 static void cliPrintErrorLine(const char *str)
233 cliPrint("### ERROR: ");
234 cliPrintLine(str);
235 #ifdef USE_CLI_BATCH
236 if (commandBatchActive) {
237 commandBatchError = true;
239 #endif
242 #ifdef CLI_MINIMAL_VERBOSITY
243 #define cliPrintHashLine(str)
244 #else
245 static void cliPrintHashLine(const char *str)
247 cliPrint("\r\n# ");
248 cliPrintLine(str);
250 #endif
252 static void cliPutp(void *p, char ch)
254 bufWriterAppend(p, ch);
257 typedef enum {
258 DUMP_MASTER = (1 << 0),
259 DUMP_PROFILE = (1 << 1),
260 DUMP_BATTERY_PROFILE = (1 << 2),
261 DUMP_RATES = (1 << 3),
262 DUMP_ALL = (1 << 4),
263 DO_DIFF = (1 << 5),
264 SHOW_DEFAULTS = (1 << 6),
265 HIDE_UNUSED = (1 << 7)
266 } dumpFlags_e;
268 static void cliPrintfva(const char *format, va_list va)
270 tfp_format(cliWriter, cliPutp, format, va);
271 bufWriterFlush(cliWriter);
274 static void cliPrintLinefva(const char *format, va_list va)
276 tfp_format(cliWriter, cliPutp, format, va);
277 bufWriterFlush(cliWriter);
278 cliPrintLinefeed();
281 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
283 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
284 va_list va;
285 va_start(va, format);
286 cliPrintLinefva(format, va);
287 va_end(va);
288 return true;
289 } else {
290 return false;
294 static void cliWrite(uint8_t ch)
296 bufWriterAppend(cliWriter, ch);
299 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
301 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
302 cliWrite('#');
304 va_list va;
305 va_start(va, format);
306 cliPrintLinefva(format, va);
307 va_end(va);
308 return true;
309 } else {
310 return false;
314 static void cliPrintf(const char *format, ...)
316 va_list va;
317 va_start(va, format);
318 cliPrintfva(format, va);
319 va_end(va);
323 static void cliPrintLinef(const char *format, ...)
325 va_list va;
326 va_start(va, format);
327 cliPrintLinefva(format, va);
328 va_end(va);
331 static void cliPrintErrorVa(const char *format, va_list va)
333 cliPrint("### ERROR: ");
334 cliPrintfva(format, va);
335 va_end(va);
337 #ifdef USE_CLI_BATCH
338 if (commandBatchActive) {
339 commandBatchError = true;
341 #endif
344 static void cliPrintErrorLinef(const char *format, ...)
346 va_list va;
347 va_start(va, format);
348 cliPrintErrorVa(format, va);
349 cliPrintLinefeed();
352 static void printValuePointer(const setting_t *var, const void *valuePointer, uint32_t full)
354 int32_t value = 0;
355 char buf[SETTING_MAX_NAME_LENGTH];
357 switch (SETTING_TYPE(var)) {
358 case VAR_UINT8:
359 value = *(uint8_t *)valuePointer;
360 break;
362 case VAR_INT8:
363 value = *(int8_t *)valuePointer;
364 break;
366 case VAR_UINT16:
367 value = *(uint16_t *)valuePointer;
368 break;
370 case VAR_INT16:
371 value = *(int16_t *)valuePointer;
372 break;
374 case VAR_UINT32:
375 value = *(uint32_t *)valuePointer;
376 break;
378 case VAR_FLOAT:
379 cliPrintf("%s", ftoa(*(float *)valuePointer, buf));
380 if (full) {
381 if (SETTING_MODE(var) == MODE_DIRECT) {
382 cliPrintf(" %s", ftoa((float)settingGetMin(var), buf));
383 cliPrintf(" %s", ftoa((float)settingGetMax(var), buf));
386 return; // return from case for float only
388 case VAR_STRING:
389 cliPrintf("%s", (const char *)valuePointer);
390 return;
393 switch (SETTING_MODE(var)) {
394 case MODE_DIRECT:
395 if (SETTING_TYPE(var) == VAR_UINT32)
396 cliPrintf("%u", value);
397 else
398 cliPrintf("%d", value);
399 if (full) {
400 if (SETTING_MODE(var) == MODE_DIRECT) {
401 cliPrintf(" %d %u", settingGetMin(var), settingGetMax(var));
404 break;
405 case MODE_LOOKUP:
407 const char *name = settingLookupValueName(var, value);
408 if (name) {
409 cliPrintf(name);
410 } else {
411 settingGetName(var, buf);
412 cliPrintErrorLinef("VALUE %d OUT OF RANGE FOR %s", (int)value, buf);
414 break;
419 static bool valuePtrEqualsDefault(const setting_t *value, const void *ptr, const void *ptrDefault)
421 bool result = false;
422 switch (SETTING_TYPE(value)) {
423 case VAR_UINT8:
424 result = *(uint8_t *)ptr == *(uint8_t *)ptrDefault;
425 break;
427 case VAR_INT8:
428 result = *(int8_t *)ptr == *(int8_t *)ptrDefault;
429 break;
431 case VAR_UINT16:
432 result = *(uint16_t *)ptr == *(uint16_t *)ptrDefault;
433 break;
435 case VAR_INT16:
436 result = *(int16_t *)ptr == *(int16_t *)ptrDefault;
437 break;
439 case VAR_UINT32:
440 result = *(uint32_t *)ptr == *(uint32_t *)ptrDefault;
441 break;
443 case VAR_FLOAT:
444 result = *(float *)ptr == *(float *)ptrDefault;
445 break;
447 case VAR_STRING:
448 result = strncmp(ptr, ptrDefault, settingGetStringMaxLength(value) + 1) == 0;
449 break;
451 return result;
454 static void dumpPgValue(const setting_t *value, uint8_t dumpMask)
456 char name[SETTING_MAX_NAME_LENGTH];
457 const char *format = "set %s = ";
458 const char *defaultFormat = "#set %s = ";
459 // During a dump, the PGs have been backed up to their "copy"
460 // regions and the actual values have been reset to its
461 // defaults. This means that settingGetValuePointer() will
462 // return the default value while settingGetCopyValuePointer()
463 // will return the actual value.
464 const void *valuePointer = settingGetCopyValuePointer(value);
465 const void *defaultValuePointer = settingGetValuePointer(value);
466 const bool equalsDefault = valuePtrEqualsDefault(value, valuePointer, defaultValuePointer);
467 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
468 settingGetName(value, name);
469 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
470 cliPrintf(defaultFormat, name);
471 printValuePointer(value, defaultValuePointer, 0);
472 cliPrintLinefeed();
474 cliPrintf(format, name);
475 printValuePointer(value, valuePointer, 0);
476 cliPrintLinefeed();
480 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
482 for (unsigned i = 0; i < SETTINGS_TABLE_COUNT; i++) {
483 const setting_t *value = settingGet(i);
484 bufWriterFlush(cliWriter);
485 if (SETTING_SECTION(value) == valueSection) {
486 dumpPgValue(value, dumpMask);
491 static void cliPrintVar(const setting_t *var, uint32_t full)
493 const void *ptr = settingGetValuePointer(var);
495 printValuePointer(var, ptr, full);
498 static void cliPrintVarRange(const setting_t *var)
500 switch (SETTING_MODE(var)) {
501 case MODE_DIRECT:
502 if (SETTING_TYPE(var) == VAR_STRING) {
503 cliPrintLinef("Max. length: %u", settingGetStringMaxLength(var));
504 break;
506 cliPrintLinef("Allowed range: %d - %u", settingGetMin(var), settingGetMax(var));
507 break;
508 case MODE_LOOKUP:
510 const lookupTableEntry_t *tableEntry = settingLookupTable(var);
511 cliPrint("Allowed values:");
512 for (uint32_t i = 0; i < tableEntry->valueCount ; i++) {
513 if (i > 0)
514 cliPrint(",");
515 cliPrintf(" %s", tableEntry->values[i]);
517 cliPrintLinefeed();
519 break;
523 typedef union {
524 uint32_t uint_value;
525 int32_t int_value;
526 float float_value;
527 } int_float_value_t;
529 static void cliSetIntFloatVar(const setting_t *var, const int_float_value_t value)
531 void *ptr = settingGetValuePointer(var);
533 switch (SETTING_TYPE(var)) {
534 case VAR_UINT8:
535 case VAR_INT8:
536 *(int8_t *)ptr = value.int_value;
537 break;
539 case VAR_UINT16:
540 case VAR_INT16:
541 *(int16_t *)ptr = value.int_value;
542 break;
544 case VAR_UINT32:
545 *(uint32_t *)ptr = value.uint_value;
546 break;
548 case VAR_FLOAT:
549 *(float *)ptr = (float)value.float_value;
550 break;
552 case VAR_STRING:
553 // Handled by cliSet directly
554 break;
558 static void cliPrompt(void)
560 cliPrint("\r\n# ");
561 bufWriterFlush(cliWriter);
564 static void cliShowParseError(void)
566 cliPrintErrorLinef("Parse error");
569 static void cliShowArgumentRangeError(char *name, int min, int max)
571 cliPrintErrorLinef("%s must be between %d and %d", name, min, max);
574 static const char *nextArg(const char *currentArg)
576 const char *ptr = strchr(currentArg, ' ');
577 while (ptr && *ptr == ' ') {
578 ptr++;
581 return ptr;
584 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
586 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
587 ptr = nextArg(ptr);
588 if (ptr) {
589 int val = fastA2I(ptr);
590 val = CHANNEL_VALUE_TO_STEP(val);
591 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
592 if (argIndex == 0) {
593 range->startStep = val;
594 } else {
595 range->endStep = val;
597 (*validArgumentCount)++;
602 return ptr;
605 // Check if a string's length is zero
606 static bool isEmpty(const char *string)
608 return (string == NULL || *string == '\0') ? true : false;
611 #if defined(USE_ASSERT)
612 static void cliAssert(char *cmdline)
614 UNUSED(cmdline);
616 if (assertFailureLine) {
617 if (assertFailureFile) {
618 cliPrintErrorLinef("Assertion failed at line %d, file %s", assertFailureLine, assertFailureFile);
620 else {
621 cliPrintErrorLinef("Assertion failed at line %d", assertFailureLine);
623 #ifdef USE_CLI_BATCH
624 if (commandBatchActive) {
625 commandBatchError = true;
627 #endif
629 else {
630 cliPrintLine("No assert() failed");
633 #endif
635 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
637 const char *format = "aux %u %u %u %u %u";
638 // print out aux channel settings
639 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
640 const modeActivationCondition_t *mac = &modeActivationConditions[i];
641 bool equalsDefault = false;
642 if (defaultModeActivationConditions) {
643 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
644 equalsDefault = mac->modeId == macDefault->modeId
645 && mac->auxChannelIndex == macDefault->auxChannelIndex
646 && mac->range.startStep == macDefault->range.startStep
647 && mac->range.endStep == macDefault->range.endStep;
648 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
650 macDefault->modeId,
651 macDefault->auxChannelIndex,
652 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
653 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep)
656 cliDumpPrintLinef(dumpMask, equalsDefault, format,
658 mac->modeId,
659 mac->auxChannelIndex,
660 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
661 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep)
666 static void cliAux(char *cmdline)
668 int i, val = 0;
669 const char *ptr;
671 if (isEmpty(cmdline)) {
672 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
673 } else {
674 ptr = cmdline;
675 i = fastA2I(ptr++);
676 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
677 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
678 uint8_t validArgumentCount = 0;
679 ptr = nextArg(ptr);
680 if (ptr) {
681 val = fastA2I(ptr);
682 if (val >= 0 && val < CHECKBOX_ITEM_COUNT) {
683 mac->modeId = val;
684 validArgumentCount++;
687 ptr = nextArg(ptr);
688 if (ptr) {
689 val = fastA2I(ptr);
690 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
691 mac->auxChannelIndex = val;
692 validArgumentCount++;
695 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
697 if (validArgumentCount != 4) {
698 memset(mac, 0, sizeof(modeActivationCondition_t));
700 } else {
701 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
706 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
708 const char *format = "serial %d %d %ld %ld %ld %ld";
709 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
710 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
711 continue;
713 bool equalsDefault = false;
714 if (serialConfigDefault) {
715 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
716 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
717 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
718 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
719 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
720 && serialConfig->portConfigs[i].peripheral_baudrateIndex == serialConfigDefault->portConfigs[i].peripheral_baudrateIndex;
721 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
722 serialConfigDefault->portConfigs[i].identifier,
723 serialConfigDefault->portConfigs[i].functionMask,
724 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
725 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
726 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
727 baudRates[serialConfigDefault->portConfigs[i].peripheral_baudrateIndex]
730 cliDumpPrintLinef(dumpMask, equalsDefault, format,
731 serialConfig->portConfigs[i].identifier,
732 serialConfig->portConfigs[i].functionMask,
733 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
734 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
735 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
736 baudRates[serialConfig->portConfigs[i].peripheral_baudrateIndex]
741 static void cliSerial(char *cmdline)
743 if (isEmpty(cmdline)) {
744 printSerial(DUMP_MASTER, serialConfig(), NULL);
745 return;
747 serialPortConfig_t portConfig;
749 serialPortConfig_t *currentConfig;
751 uint8_t validArgumentCount = 0;
753 const char *ptr = cmdline;
755 int val = fastA2I(ptr++);
756 currentConfig = serialFindPortConfiguration(val);
757 if (!currentConfig) {
758 // Invalid port ID
759 cliPrintErrorLinef("Invalid port ID %d", val);
760 return;
762 memcpy(&portConfig, currentConfig, sizeof(portConfig));
763 validArgumentCount++;
765 ptr = nextArg(ptr);
766 if (ptr) {
767 switch (*ptr) {
768 case '+':
769 // Add function
770 ptr++;
771 val = fastA2I(ptr);
772 portConfig.functionMask |= (1 << val);
773 break;
774 case '-':
775 // Remove function
776 ptr++;
777 val = fastA2I(ptr);
778 portConfig.functionMask &= 0xFFFFFFFF ^ (1 << val);
779 break;
780 default:
781 // Set functions
782 val = fastA2I(ptr);
783 portConfig.functionMask = val & 0xFFFFFFFF;
784 break;
786 validArgumentCount++;
789 for (int i = 0; i < 4; i ++) {
790 ptr = nextArg(ptr);
791 if (!ptr) {
792 break;
795 val = fastA2I(ptr);
797 uint8_t baudRateIndex = lookupBaudRateIndex(val);
798 if (baudRates[baudRateIndex] != (uint32_t) val) {
799 break;
802 switch (i) {
803 case 0:
804 if (baudRateIndex < BAUD_1200 || baudRateIndex > BAUD_2470000) {
805 continue;
807 portConfig.msp_baudrateIndex = baudRateIndex;
808 break;
809 case 1:
810 if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
811 continue;
813 portConfig.gps_baudrateIndex = baudRateIndex;
814 break;
815 case 2:
816 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
817 continue;
819 portConfig.telemetry_baudrateIndex = baudRateIndex;
820 break;
821 case 3:
822 if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_250000) {
823 continue;
825 portConfig.peripheral_baudrateIndex = baudRateIndex;
826 break;
829 validArgumentCount++;
832 if (validArgumentCount < 2) {
833 cliShowParseError();
834 return;
837 memcpy(currentConfig, &portConfig, sizeof(portConfig));
840 #ifdef USE_SERIAL_PASSTHROUGH
841 static void cliSerialPassthrough(char *cmdline)
843 char * saveptr;
845 if (isEmpty(cmdline)) {
846 cliShowParseError();
847 return;
850 int id = -1;
851 uint32_t baud = 0;
852 unsigned mode = 0;
853 char* tok = strtok_r(cmdline, " ", &saveptr);
854 int index = 0;
856 while (tok != NULL) {
857 switch (index) {
858 case 0:
859 id = fastA2I(tok);
860 break;
861 case 1:
862 baud = fastA2I(tok);
863 break;
864 case 2:
865 if (strstr(tok, "rx") || strstr(tok, "RX"))
866 mode |= MODE_RX;
867 if (strstr(tok, "tx") || strstr(tok, "TX"))
868 mode |= MODE_TX;
869 break;
871 index++;
872 tok = strtok_r(NULL, " ", &saveptr);
875 serialPort_t *passThroughPort;
876 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
877 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
878 if (!baud) {
879 tfp_printf("Port %d is closed, must specify baud.\r\n", id);
880 return;
882 if (!mode)
883 mode = MODE_RXTX;
885 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
886 baud, mode,
887 SERIAL_NOT_INVERTED);
888 if (!passThroughPort) {
889 tfp_printf("Port %d could not be opened.\r\n", id);
890 return;
892 tfp_printf("Port %d opened, baud = %u.\r\n", id, (unsigned)baud);
893 } else {
894 passThroughPort = passThroughPortUsage->serialPort;
895 // If the user supplied a mode, override the port's mode, otherwise
896 // leave the mode unchanged. serialPassthrough() handles one-way ports.
897 tfp_printf("Port %d already open.\r\n", id);
898 if (mode && passThroughPort->mode != mode) {
899 tfp_printf("Adjusting mode from %d to %d.\r\n",
900 passThroughPort->mode, mode);
901 serialSetMode(passThroughPort, mode);
903 // If this port has a rx callback associated we need to remove it now.
904 // Otherwise no data will be pushed in the serial port buffer!
905 if (passThroughPort->rxCallback) {
906 tfp_printf("Removing rxCallback\r\n");
907 passThroughPort->rxCallback = 0;
911 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id);
913 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
915 #endif
917 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
919 const char *format = "adjrange %u %u %u %u %u %u %u";
920 // print out adjustment ranges channel settings
921 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
922 const adjustmentRange_t *ar = &adjustmentRanges[i];
923 bool equalsDefault = false;
924 if (defaultAdjustmentRanges) {
925 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
926 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
927 && ar->range.startStep == arDefault->range.startStep
928 && ar->range.endStep == arDefault->range.endStep
929 && ar->adjustmentFunction == arDefault->adjustmentFunction
930 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
931 && ar->adjustmentIndex == arDefault->adjustmentIndex;
932 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
934 arDefault->adjustmentIndex,
935 arDefault->auxChannelIndex,
936 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
937 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
938 arDefault->adjustmentFunction,
939 arDefault->auxSwitchChannelIndex
942 cliDumpPrintLinef(dumpMask, equalsDefault, format,
944 ar->adjustmentIndex,
945 ar->auxChannelIndex,
946 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
947 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
948 ar->adjustmentFunction,
949 ar->auxSwitchChannelIndex
954 static void cliAdjustmentRange(char *cmdline)
956 int i, val = 0;
957 const char *ptr;
959 if (isEmpty(cmdline)) {
960 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
961 } else {
962 ptr = cmdline;
963 i = fastA2I(ptr++);
964 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
965 adjustmentRange_t *ar = adjustmentRangesMutable(i);
966 uint8_t validArgumentCount = 0;
968 ptr = nextArg(ptr);
969 if (ptr) {
970 val = fastA2I(ptr);
971 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
972 ar->adjustmentIndex = val;
973 validArgumentCount++;
976 ptr = nextArg(ptr);
977 if (ptr) {
978 val = fastA2I(ptr);
979 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
980 ar->auxChannelIndex = val;
981 validArgumentCount++;
985 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
987 ptr = nextArg(ptr);
988 if (ptr) {
989 val = fastA2I(ptr);
990 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
991 ar->adjustmentFunction = val;
992 validArgumentCount++;
995 ptr = nextArg(ptr);
996 if (ptr) {
997 val = fastA2I(ptr);
998 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
999 ar->auxSwitchChannelIndex = val;
1000 validArgumentCount++;
1004 if (validArgumentCount != 6) {
1005 memset(ar, 0, sizeof(adjustmentRange_t));
1006 cliShowParseError();
1008 } else {
1009 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1014 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *primaryMotorMixer, const motorMixer_t *defaultprimaryMotorMixer)
1016 const char *format = "mmix %d %s %s %s %s";
1017 char buf0[FTOA_BUFFER_SIZE];
1018 char buf1[FTOA_BUFFER_SIZE];
1019 char buf2[FTOA_BUFFER_SIZE];
1020 char buf3[FTOA_BUFFER_SIZE];
1021 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1022 if (primaryMotorMixer[i].throttle == 0.0f)
1023 break;
1024 const float thr = primaryMotorMixer[i].throttle;
1025 const float roll = primaryMotorMixer[i].roll;
1026 const float pitch = primaryMotorMixer[i].pitch;
1027 const float yaw = primaryMotorMixer[i].yaw;
1028 bool equalsDefault = false;
1029 if (defaultprimaryMotorMixer) {
1030 const float thrDefault = defaultprimaryMotorMixer[i].throttle;
1031 const float rollDefault = defaultprimaryMotorMixer[i].roll;
1032 const float pitchDefault = defaultprimaryMotorMixer[i].pitch;
1033 const float yawDefault = defaultprimaryMotorMixer[i].yaw;
1034 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1036 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1038 ftoa(thrDefault, buf0),
1039 ftoa(rollDefault, buf1),
1040 ftoa(pitchDefault, buf2),
1041 ftoa(yawDefault, buf3));
1043 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1045 ftoa(thr, buf0),
1046 ftoa(roll, buf1),
1047 ftoa(pitch, buf2),
1048 ftoa(yaw, buf3));
1052 static void cliMotorMix(char *cmdline)
1054 int check = 0;
1055 const char *ptr;
1057 if (isEmpty(cmdline)) {
1058 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1059 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1060 // erase custom mixer
1061 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1062 primaryMotorMixerMutable(i)->throttle = 0.0f;
1064 } else {
1065 ptr = cmdline;
1066 uint32_t i = fastA2I(ptr); // get motor number
1067 if (i < MAX_SUPPORTED_MOTORS) {
1068 ptr = nextArg(ptr);
1069 if (ptr) {
1070 primaryMotorMixerMutable(i)->throttle = fastA2F(ptr);
1071 check++;
1073 ptr = nextArg(ptr);
1074 if (ptr) {
1075 primaryMotorMixerMutable(i)->roll = fastA2F(ptr);
1076 check++;
1078 ptr = nextArg(ptr);
1079 if (ptr) {
1080 primaryMotorMixerMutable(i)->pitch = fastA2F(ptr);
1081 check++;
1083 ptr = nextArg(ptr);
1084 if (ptr) {
1085 primaryMotorMixerMutable(i)->yaw = fastA2F(ptr);
1086 check++;
1088 if (check != 4) {
1089 cliShowParseError();
1090 } else {
1091 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1093 } else {
1094 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1099 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1101 const char *format = "rxrange %u %u %u";
1102 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1103 bool equalsDefault = false;
1104 if (defaultChannelRangeConfigs) {
1105 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1106 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1107 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1109 defaultChannelRangeConfigs[i].min,
1110 defaultChannelRangeConfigs[i].max
1113 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1115 channelRangeConfigs[i].min,
1116 channelRangeConfigs[i].max
1121 static void cliRxRange(char *cmdline)
1123 int i, validArgumentCount = 0;
1124 const char *ptr;
1126 if (isEmpty(cmdline)) {
1127 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1128 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1129 resetAllRxChannelRangeConfigurations();
1130 } else {
1131 ptr = cmdline;
1132 i = fastA2I(ptr);
1133 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1134 int rangeMin, rangeMax;
1136 ptr = nextArg(ptr);
1137 if (ptr) {
1138 rangeMin = fastA2I(ptr);
1139 validArgumentCount++;
1142 ptr = nextArg(ptr);
1143 if (ptr) {
1144 rangeMax = fastA2I(ptr);
1145 validArgumentCount++;
1148 if (validArgumentCount != 2) {
1149 cliShowParseError();
1150 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1151 cliShowParseError();
1152 } else {
1153 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1154 channelRangeConfig->min = rangeMin;
1155 channelRangeConfig->max = rangeMax;
1157 } else {
1158 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1163 #ifdef USE_TEMPERATURE_SENSOR
1164 static void printTempSensor(uint8_t dumpMask, const tempSensorConfig_t *tempSensorConfigs, const tempSensorConfig_t *defaultTempSensorConfigs)
1166 const char *format = "temp_sensor %u %u %s %d %d %u %s";
1167 for (uint8_t i = 0; i < MAX_TEMP_SENSORS; i++) {
1168 bool equalsDefault = false;
1169 char label[5], hex_address[17];
1170 strncpy(label, tempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN);
1171 label[4] = '\0';
1172 tempSensorAddressToString(tempSensorConfigs[i].address, hex_address);
1173 if (defaultTempSensorConfigs) {
1174 equalsDefault = tempSensorConfigs[i].type == defaultTempSensorConfigs[i].type
1175 && tempSensorConfigs[i].address == defaultTempSensorConfigs[i].address
1176 && tempSensorConfigs[i].osdSymbol == defaultTempSensorConfigs[i].osdSymbol
1177 && !memcmp(tempSensorConfigs[i].label, defaultTempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN)
1178 && tempSensorConfigs[i].alarm_min == defaultTempSensorConfigs[i].alarm_min
1179 && tempSensorConfigs[i].alarm_max == defaultTempSensorConfigs[i].alarm_max;
1180 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1182 defaultTempSensorConfigs[i].type,
1183 "0",
1184 defaultTempSensorConfigs[i].alarm_min,
1185 defaultTempSensorConfigs[i].alarm_max,
1190 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1192 tempSensorConfigs[i].type,
1193 hex_address,
1194 tempSensorConfigs[i].alarm_min,
1195 tempSensorConfigs[i].alarm_max,
1196 tempSensorConfigs[i].osdSymbol,
1197 label
1202 static void cliTempSensor(char *cmdline)
1204 if (isEmpty(cmdline)) {
1205 printTempSensor(DUMP_MASTER, tempSensorConfig(0), NULL);
1206 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1207 resetTempSensorConfig();
1208 } else {
1209 int16_t i;
1210 const char *ptr = cmdline, *label;
1211 int16_t type, alarm_min, alarm_max;
1212 bool addressValid = false;
1213 uint64_t address;
1214 int8_t osdSymbol;
1215 uint8_t validArgumentCount = 0;
1216 i = fastA2I(ptr);
1217 if (i >= 0 && i < MAX_TEMP_SENSORS) {
1219 ptr = nextArg(ptr);
1220 if (ptr) {
1221 type = fastA2I(ptr);
1222 validArgumentCount++;
1225 ptr = nextArg(ptr);
1226 if (ptr) {
1227 addressValid = tempSensorStringToAddress(ptr, &address);
1228 validArgumentCount++;
1231 ptr = nextArg(ptr);
1232 if (ptr) {
1233 alarm_min = fastA2I(ptr);
1234 validArgumentCount++;
1237 ptr = nextArg(ptr);
1238 if (ptr) {
1239 alarm_max = fastA2I(ptr);
1240 validArgumentCount++;
1243 ptr = nextArg(ptr);
1244 if (ptr) {
1245 osdSymbol = fastA2I(ptr);
1246 validArgumentCount++;
1249 label = nextArg(ptr);
1250 if (label)
1251 ++validArgumentCount;
1252 else
1253 label = "";
1255 if (validArgumentCount < 4) {
1256 cliShowParseError();
1257 } else if (type < 0 || type > TEMP_SENSOR_DS18B20 || alarm_min < -550 || alarm_min > 1250 || alarm_max < -550 || alarm_max > 1250 || osdSymbol < 0 || osdSymbol > TEMP_SENSOR_SYM_COUNT || strlen(label) > TEMPERATURE_LABEL_LEN || !addressValid) {
1258 cliShowParseError();
1259 } else {
1260 tempSensorConfig_t *sensorConfig = tempSensorConfigMutable(i);
1261 sensorConfig->type = type;
1262 sensorConfig->address = address;
1263 sensorConfig->alarm_min = alarm_min;
1264 sensorConfig->alarm_max = alarm_max;
1265 sensorConfig->osdSymbol = osdSymbol;
1266 for (uint8_t index = 0; index < TEMPERATURE_LABEL_LEN; ++index) {
1267 sensorConfig->label[index] = toupper(label[index]);
1268 if (label[index] == '\0') break;
1271 } else {
1272 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS - 1);
1276 #endif
1278 #if defined(USE_NAV) && defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1279 static void printWaypoints(uint8_t dumpMask, const navWaypoint_t *navWaypoint, const navWaypoint_t *defaultNavWaypoint)
1281 cliPrintLinef("#wp %d %svalid", posControl.waypointCount, posControl.waypointListValid ? "" : "in"); //int8_t bool
1282 const char *format = "wp %u %u %d %d %d %d %u"; //uint8_t action; int32_t lat; int32_t lon; int32_t alt; int16_t p1; uint8_t flag
1283 for (uint8_t i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1284 bool equalsDefault = false;
1285 if (defaultNavWaypoint) {
1286 equalsDefault = navWaypoint[i].action == defaultNavWaypoint[i].action
1287 && navWaypoint[i].lat == defaultNavWaypoint[i].lat
1288 && navWaypoint[i].lon == defaultNavWaypoint[i].lon
1289 && navWaypoint[i].alt == defaultNavWaypoint[i].alt
1290 && navWaypoint[i].p1 == defaultNavWaypoint[i].p1
1291 && navWaypoint[i].flag == defaultNavWaypoint[i].flag;
1292 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1294 defaultNavWaypoint[i].action,
1295 defaultNavWaypoint[i].lat,
1296 defaultNavWaypoint[i].lon,
1297 defaultNavWaypoint[i].alt,
1298 defaultNavWaypoint[i].p1,
1299 defaultNavWaypoint[i].flag
1302 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1304 navWaypoint[i].action,
1305 navWaypoint[i].lat,
1306 navWaypoint[i].lon,
1307 navWaypoint[i].alt,
1308 navWaypoint[i].p1,
1309 navWaypoint[i].flag
1314 static void cliWaypoints(char *cmdline)
1316 if (isEmpty(cmdline)) {
1317 printWaypoints(DUMP_MASTER, posControl.waypointList, NULL);
1318 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1319 resetWaypointList();
1320 } else if (sl_strcasecmp(cmdline, "load") == 0) {
1321 loadNonVolatileWaypointList();
1322 } else if (sl_strcasecmp(cmdline, "save") == 0) {
1323 posControl.waypointListValid = false;
1324 for (int i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1325 if (!(posControl.waypointList[i].action == NAV_WP_ACTION_WAYPOINT || posControl.waypointList[i].action == NAV_WP_ACTION_RTH)) break;
1326 if (posControl.waypointList[i].flag == NAV_WP_FLAG_LAST) {
1327 posControl.waypointCount = i + 1;
1328 posControl.waypointListValid = true;
1329 break;
1332 if (posControl.waypointListValid) {
1333 saveNonVolatileWaypointList();
1334 } else {
1335 cliShowParseError();
1337 } else {
1338 int16_t i, p1;
1339 uint8_t action, flag;
1340 int32_t lat, lon, alt;
1341 uint8_t validArgumentCount = 0;
1342 const char *ptr = cmdline;
1343 i = fastA2I(ptr);
1344 if (i >= 0 && i < NAV_MAX_WAYPOINTS) {
1345 ptr = nextArg(ptr);
1346 if (ptr) {
1347 action = fastA2I(ptr);
1348 validArgumentCount++;
1350 ptr = nextArg(ptr);
1351 if (ptr) {
1352 lat = fastA2I(ptr);
1353 validArgumentCount++;
1355 ptr = nextArg(ptr);
1356 if (ptr) {
1357 lon = fastA2I(ptr);
1358 validArgumentCount++;
1360 ptr = nextArg(ptr);
1361 if (ptr) {
1362 alt = fastA2I(ptr);
1363 validArgumentCount++;
1365 ptr = nextArg(ptr);
1366 if (ptr) {
1367 p1 = fastA2I(ptr);
1368 validArgumentCount++;
1370 ptr = nextArg(ptr);
1371 if (ptr) {
1372 flag = fastA2I(ptr);
1373 validArgumentCount++;
1375 if (validArgumentCount < 4) {
1376 cliShowParseError();
1377 } else if (!(action == 0 || action == NAV_WP_ACTION_WAYPOINT || action == NAV_WP_ACTION_RTH) || (p1 < 0) || !(flag == 0 || flag == NAV_WP_FLAG_LAST)) {
1378 cliShowParseError();
1379 } else {
1380 posControl.waypointList[i].action = action;
1381 posControl.waypointList[i].lat = lat;
1382 posControl.waypointList[i].lon = lon;
1383 posControl.waypointList[i].alt = alt;
1384 posControl.waypointList[i].p1 = p1;
1385 posControl.waypointList[i].flag = flag;
1387 } else {
1388 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS - 1);
1393 #endif
1395 #ifdef USE_LED_STRIP
1396 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1398 const char *format = "led %u %s";
1399 char ledConfigBuffer[20];
1400 char ledConfigDefaultBuffer[20];
1401 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1402 ledConfig_t ledConfig = ledConfigs[i];
1403 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1404 bool equalsDefault = false;
1405 if (defaultLedConfigs) {
1406 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1407 equalsDefault = ledConfig == ledConfigDefault;
1408 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1409 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1411 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1415 static void cliLed(char *cmdline)
1417 int i;
1418 const char *ptr;
1420 if (isEmpty(cmdline)) {
1421 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1422 } else {
1423 ptr = cmdline;
1424 i = fastA2I(ptr);
1425 if (i < LED_MAX_STRIP_LENGTH) {
1426 ptr = nextArg(cmdline);
1427 if (!parseLedStripConfig(i, ptr)) {
1428 cliShowParseError();
1430 } else {
1431 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1436 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1438 const char *format = "color %u %d,%u,%u";
1439 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1440 const hsvColor_t *color = &colors[i];
1441 bool equalsDefault = false;
1442 if (defaultColors) {
1443 const hsvColor_t *colorDefault = &defaultColors[i];
1444 equalsDefault = color->h == colorDefault->h
1445 && color->s == colorDefault->s
1446 && color->v == colorDefault->v;
1447 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1449 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1453 static void cliColor(char *cmdline)
1455 if (isEmpty(cmdline)) {
1456 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1457 } else {
1458 const char *ptr = cmdline;
1459 const int i = fastA2I(ptr);
1460 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1461 ptr = nextArg(cmdline);
1462 if (!parseColor(i, ptr)) {
1463 cliShowParseError();
1465 } else {
1466 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1471 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1473 const char *format = "mode_color %u %u %u";
1474 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1475 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1476 int colorIndex = ledStripConfig->modeColors[i].color[j];
1477 bool equalsDefault = false;
1478 if (defaultLedStripConfig) {
1479 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1480 equalsDefault = colorIndex == colorIndexDefault;
1481 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1483 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1487 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1488 const int colorIndex = ledStripConfig->specialColors.color[j];
1489 bool equalsDefault = false;
1490 if (defaultLedStripConfig) {
1491 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1492 equalsDefault = colorIndex == colorIndexDefault;
1493 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1495 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1499 static void cliModeColor(char *cmdline)
1501 char * saveptr;
1503 if (isEmpty(cmdline)) {
1504 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1505 } else {
1506 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1507 int args[ARGS_COUNT];
1508 int argNo = 0;
1509 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1510 while (ptr && argNo < ARGS_COUNT) {
1511 args[argNo++] = fastA2I(ptr);
1512 ptr = strtok_r(NULL, " ", &saveptr);
1515 if (ptr != NULL || argNo != ARGS_COUNT) {
1516 cliShowParseError();
1517 return;
1520 int modeIdx = args[MODE];
1521 int funIdx = args[FUNCTION];
1522 int color = args[COLOR];
1523 if (!setModeColor(modeIdx, funIdx, color)) {
1524 cliShowParseError();
1525 return;
1527 // values are validated
1528 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1531 #endif
1533 static void printServo(uint8_t dumpMask, const servoParam_t *servoParam, const servoParam_t *defaultServoParam)
1535 // print out servo settings
1536 const char *format = "servo %u %d %d %d %d";
1537 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1538 const servoParam_t *servoConf = &servoParam[i];
1539 bool equalsDefault = false;
1540 if (defaultServoParam) {
1541 const servoParam_t *servoConfDefault = &defaultServoParam[i];
1542 equalsDefault = servoConf->min == servoConfDefault->min
1543 && servoConf->max == servoConfDefault->max
1544 && servoConf->middle == servoConfDefault->middle
1545 && servoConf->rate == servoConfDefault->rate;
1546 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1548 servoConfDefault->min,
1549 servoConfDefault->max,
1550 servoConfDefault->middle,
1551 servoConfDefault->rate
1554 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1556 servoConf->min,
1557 servoConf->max,
1558 servoConf->middle,
1559 servoConf->rate
1564 static void cliServo(char *cmdline)
1566 enum { SERVO_ARGUMENT_COUNT = 5 };
1567 int16_t arguments[SERVO_ARGUMENT_COUNT];
1569 servoParam_t *servo;
1571 int i;
1572 const char *ptr;
1574 if (isEmpty(cmdline)) {
1575 printServo(DUMP_MASTER, servoParams(0), NULL);
1576 } else {
1577 int validArgumentCount = 0;
1579 ptr = cmdline;
1581 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1583 // If command line doesn't fit the format, don't modify the config
1584 while (*ptr) {
1585 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1586 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1587 cliShowParseError();
1588 return;
1591 arguments[validArgumentCount++] = fastA2I(ptr);
1593 do {
1594 ptr++;
1595 } while (*ptr >= '0' && *ptr <= '9');
1596 } else if (*ptr == ' ') {
1597 ptr++;
1598 } else {
1599 cliShowParseError();
1600 return;
1604 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE};
1606 i = arguments[INDEX];
1608 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1609 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1610 cliShowParseError();
1611 return;
1614 servo = servoParamsMutable(i);
1616 if (
1617 arguments[MIN] < SERVO_OUTPUT_MIN || arguments[MIN] > SERVO_OUTPUT_MAX ||
1618 arguments[MAX] < SERVO_OUTPUT_MIN || arguments[MAX] > SERVO_OUTPUT_MAX ||
1619 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1620 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1621 arguments[RATE] < -125 || arguments[RATE] > 125
1623 cliShowParseError();
1624 return;
1627 servo->min = arguments[MIN];
1628 servo->max = arguments[MAX];
1629 servo->middle = arguments[MIDDLE];
1630 servo->rate = arguments[RATE];
1634 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1636 const char *format = "smix %d %d %d %d %d %d";
1637 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1638 const servoMixer_t customServoMixer = customServoMixers[i];
1639 if (customServoMixer.rate == 0) {
1640 break;
1643 bool equalsDefault = false;
1644 if (defaultCustomServoMixers) {
1645 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1646 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1647 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1648 && customServoMixer.rate == customServoMixerDefault.rate
1649 && customServoMixer.speed == customServoMixerDefault.speed
1650 #ifdef USE_LOGIC_CONDITIONS
1651 && customServoMixer.conditionId == customServoMixerDefault.conditionId
1652 #endif
1655 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1657 customServoMixerDefault.targetChannel,
1658 customServoMixerDefault.inputSource,
1659 customServoMixerDefault.rate,
1660 customServoMixerDefault.speed,
1661 #ifdef USE_LOGIC_CONDITIONS
1662 customServoMixer.conditionId
1663 #else
1665 #endif
1668 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1670 customServoMixer.targetChannel,
1671 customServoMixer.inputSource,
1672 customServoMixer.rate,
1673 customServoMixer.speed,
1674 #ifdef USE_LOGIC_CONDITIONS
1675 customServoMixer.conditionId
1676 #else
1678 #endif
1683 static void cliServoMix(char *cmdline)
1685 char * saveptr;
1686 int args[6], check = 0;
1687 uint8_t len = strlen(cmdline);
1689 if (len == 0) {
1690 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
1691 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1692 // erase custom mixer
1693 pgResetCopy(customServoMixersMutable(0), PG_SERVO_MIXER);
1694 } else {
1695 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, CONDITION, ARGS_COUNT};
1696 char *ptr = strtok_r(cmdline, " ", &saveptr);
1697 args[CONDITION] = -1;
1698 while (ptr != NULL && check < ARGS_COUNT) {
1699 args[check++] = fastA2I(ptr);
1700 ptr = strtok_r(NULL, " ", &saveptr);
1703 if (ptr != NULL || (check < ARGS_COUNT - 1)) {
1704 cliShowParseError();
1705 return;
1708 int32_t i = args[RULE];
1709 if (
1710 i >= 0 && i < MAX_SERVO_RULES &&
1711 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1712 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1713 args[RATE] >= -1000 && args[RATE] <= 1000 &&
1714 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1715 args[CONDITION] >= -1 && args[CONDITION] < MAX_LOGIC_CONDITIONS
1717 customServoMixersMutable(i)->targetChannel = args[TARGET];
1718 customServoMixersMutable(i)->inputSource = args[INPUT];
1719 customServoMixersMutable(i)->rate = args[RATE];
1720 customServoMixersMutable(i)->speed = args[SPEED];
1721 #ifdef USE_LOGIC_CONDITIONS
1722 customServoMixersMutable(i)->conditionId = args[CONDITION];
1723 #endif
1724 cliServoMix("");
1725 } else {
1726 cliShowParseError();
1731 #ifdef USE_LOGIC_CONDITIONS
1733 static void printLogic(uint8_t dumpMask, const logicCondition_t *logicConditions, const logicCondition_t *defaultLogicConditions)
1735 const char *format = "logic %d %d %d %d %d %d %d %d";
1736 for (uint32_t i = 0; i < MAX_LOGIC_CONDITIONS; i++) {
1737 const logicCondition_t logic = logicConditions[i];
1739 bool equalsDefault = false;
1740 if (defaultLogicConditions) {
1741 logicCondition_t defaultValue = defaultLogicConditions[i];
1742 equalsDefault =
1743 logic.enabled == defaultValue.enabled &&
1744 logic.operation == defaultValue.operation &&
1745 logic.operandA.type == defaultValue.operandA.type &&
1746 logic.operandA.value == defaultValue.operandA.value &&
1747 logic.operandB.type == defaultValue.operandB.type &&
1748 logic.operandB.value == defaultValue.operandB.value &&
1749 logic.flags == defaultValue.flags;
1751 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1753 logic.enabled,
1754 logic.operation,
1755 logic.operandA.type,
1756 logic.operandA.value,
1757 logic.operandB.type,
1758 logic.operandB.value,
1759 logic.flags
1762 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1764 logic.enabled,
1765 logic.operation,
1766 logic.operandA.type,
1767 logic.operandA.value,
1768 logic.operandB.type,
1769 logic.operandB.value,
1770 logic.flags
1775 static void cliLogic(char *cmdline) {
1776 char * saveptr;
1777 int args[8], check = 0;
1778 uint8_t len = strlen(cmdline);
1780 if (len == 0) {
1781 printLogic(DUMP_MASTER, logicConditions(0), NULL);
1782 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1783 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS);
1784 } else {
1785 enum {
1786 INDEX = 0,
1787 ENABLED,
1788 OPERATION,
1789 OPERAND_A_TYPE,
1790 OPERAND_A_VALUE,
1791 OPERAND_B_TYPE,
1792 OPERAND_B_VALUE,
1793 FLAGS,
1794 ARGS_COUNT
1796 char *ptr = strtok_r(cmdline, " ", &saveptr);
1797 while (ptr != NULL && check < ARGS_COUNT) {
1798 args[check++] = fastA2I(ptr);
1799 ptr = strtok_r(NULL, " ", &saveptr);
1802 if (ptr != NULL || check != ARGS_COUNT) {
1803 cliShowParseError();
1804 return;
1807 int32_t i = args[INDEX];
1808 if (
1809 i >= 0 && i < MAX_LOGIC_CONDITIONS &&
1810 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
1811 args[OPERATION] >= 0 && args[OPERATION] < LOGIC_CONDITION_LAST &&
1812 args[OPERAND_A_TYPE] >= 0 && args[OPERAND_A_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
1813 args[OPERAND_A_VALUE] >= -1000000 && args[OPERAND_A_VALUE] <= 1000000 &&
1814 args[OPERAND_B_TYPE] >= 0 && args[OPERAND_B_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
1815 args[OPERAND_B_VALUE] >= -1000000 && args[OPERAND_B_VALUE] <= 1000000 &&
1816 args[FLAGS] >= 0 && args[FLAGS] <= 255
1819 logicConditionsMutable(i)->enabled = args[ENABLED];
1820 logicConditionsMutable(i)->operation = args[OPERATION];
1821 logicConditionsMutable(i)->operandA.type = args[OPERAND_A_TYPE];
1822 logicConditionsMutable(i)->operandA.value = args[OPERAND_A_VALUE];
1823 logicConditionsMutable(i)->operandB.type = args[OPERAND_B_TYPE];
1824 logicConditionsMutable(i)->operandB.value = args[OPERAND_B_VALUE];
1825 logicConditionsMutable(i)->flags = args[FLAGS];
1827 cliLogic("");
1828 } else {
1829 cliShowParseError();
1833 #endif
1835 #ifdef USE_GLOBAL_FUNCTIONS
1837 static void printGlobalFunctions(uint8_t dumpMask, const globalFunction_t *globalFunctions, const globalFunction_t *defaultGlobalFunctions)
1839 const char *format = "gf %d %d %d %d %d %d %d";
1840 for (uint32_t i = 0; i < MAX_GLOBAL_FUNCTIONS; i++) {
1841 const globalFunction_t gf = globalFunctions[i];
1843 bool equalsDefault = false;
1844 if (defaultGlobalFunctions) {
1845 globalFunction_t defaultValue = defaultGlobalFunctions[i];
1846 equalsDefault =
1847 gf.enabled == defaultValue.enabled &&
1848 gf.conditionId == defaultValue.conditionId &&
1849 gf.action == defaultValue.action &&
1850 gf.withValue.type == defaultValue.withValue.type &&
1851 gf.withValue.value == defaultValue.withValue.value &&
1852 gf.flags == defaultValue.flags;
1854 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1856 gf.enabled,
1857 gf.conditionId,
1858 gf.action,
1859 gf.withValue.type,
1860 gf.withValue.value,
1861 gf.flags
1864 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1866 gf.enabled,
1867 gf.conditionId,
1868 gf.action,
1869 gf.withValue.type,
1870 gf.withValue.value,
1871 gf.flags
1876 static void cliGlobalFunctions(char *cmdline) {
1877 char * saveptr;
1878 int args[7], check = 0;
1879 uint8_t len = strlen(cmdline);
1881 if (len == 0) {
1882 printGlobalFunctions(DUMP_MASTER, globalFunctions(0), NULL);
1883 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1884 pgResetCopy(globalFunctionsMutable(0), PG_GLOBAL_FUNCTIONS);
1885 } else {
1886 enum {
1887 INDEX = 0,
1888 ENABLED,
1889 CONDITION_ID,
1890 ACTION,
1891 VALUE_TYPE,
1892 VALUE_VALUE,
1893 FLAGS,
1894 ARGS_COUNT
1896 char *ptr = strtok_r(cmdline, " ", &saveptr);
1897 while (ptr != NULL && check < ARGS_COUNT) {
1898 args[check++] = fastA2I(ptr);
1899 ptr = strtok_r(NULL, " ", &saveptr);
1902 if (ptr != NULL || check != ARGS_COUNT) {
1903 cliShowParseError();
1904 return;
1907 int32_t i = args[INDEX];
1908 if (
1909 i >= 0 && i < MAX_GLOBAL_FUNCTIONS &&
1910 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
1911 args[CONDITION_ID] >= 0 && args[CONDITION_ID] < MAX_LOGIC_CONDITIONS &&
1912 args[ACTION] >= 0 && args[ACTION] < GLOBAL_FUNCTION_ACTION_LAST &&
1913 args[VALUE_TYPE] >= 0 && args[VALUE_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
1914 args[VALUE_VALUE] >= -1000000 && args[VALUE_VALUE] <= 1000000 &&
1915 args[FLAGS] >= 0 && args[FLAGS] <= 255
1918 globalFunctionsMutable(i)->enabled = args[ENABLED];
1919 globalFunctionsMutable(i)->conditionId = args[CONDITION_ID];
1920 globalFunctionsMutable(i)->action = args[ACTION];
1921 globalFunctionsMutable(i)->withValue.type = args[VALUE_TYPE];
1922 globalFunctionsMutable(i)->withValue.value = args[VALUE_VALUE];
1923 globalFunctionsMutable(i)->flags = args[FLAGS];
1925 cliGlobalFunctions("");
1926 } else {
1927 cliShowParseError();
1931 #endif
1933 #ifdef USE_SDCARD
1935 static void cliWriteBytes(const uint8_t *buffer, int count)
1937 while (count > 0) {
1938 cliWrite(*buffer);
1939 buffer++;
1940 count--;
1944 static void cliSdInfo(char *cmdline)
1946 UNUSED(cmdline);
1948 cliPrint("SD card: ");
1950 if (!sdcard_isInserted()) {
1951 cliPrintLine("None inserted");
1952 return;
1955 if (!sdcard_isInitialized()) {
1956 cliPrintLine("Startup failed");
1957 return;
1960 const sdcardMetadata_t *metadata = sdcard_getMetadata();
1962 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1963 metadata->manufacturerID,
1964 metadata->numBlocks / 2, /* One block is half a kB */
1965 metadata->productionMonth,
1966 metadata->productionYear,
1967 metadata->productRevisionMajor,
1968 metadata->productRevisionMinor
1971 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
1973 cliPrint("'\r\n" "Filesystem: ");
1975 switch (afatfs_getFilesystemState()) {
1976 case AFATFS_FILESYSTEM_STATE_READY:
1977 cliPrint("Ready");
1978 break;
1979 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
1980 cliPrint("Initializing");
1981 break;
1982 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
1983 case AFATFS_FILESYSTEM_STATE_FATAL:
1984 cliPrint("Fatal");
1986 switch (afatfs_getLastError()) {
1987 case AFATFS_ERROR_BAD_MBR:
1988 cliPrint(" - no FAT MBR partitions");
1989 break;
1990 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
1991 cliPrint(" - bad FAT header");
1992 break;
1993 case AFATFS_ERROR_GENERIC:
1994 case AFATFS_ERROR_NONE:
1995 ; // Nothing more detailed to print
1996 break;
1998 break;
2000 cliPrintLinefeed();
2003 #endif
2005 #ifdef USE_FLASHFS
2007 static void cliFlashInfo(char *cmdline)
2009 const flashGeometry_t *layout = flashfsGetGeometry();
2011 UNUSED(cmdline);
2013 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
2014 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
2017 static void cliFlashErase(char *cmdline)
2019 UNUSED(cmdline);
2021 cliPrintLine("Erasing...");
2022 flashfsEraseCompletely();
2024 while (!flashfsIsReady()) {
2025 delay(100);
2028 cliPrintLine("Done.");
2031 #ifdef USE_FLASH_TOOLS
2033 static void cliFlashWrite(char *cmdline)
2035 const uint32_t address = fastA2I(cmdline);
2036 const char *text = strchr(cmdline, ' ');
2038 if (!text) {
2039 cliShowParseError();
2040 } else {
2041 flashfsSeekAbs(address);
2042 flashfsWrite((uint8_t*)text, strlen(text), true);
2043 flashfsFlushSync();
2045 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2049 static void cliFlashRead(char *cmdline)
2051 uint32_t address = fastA2I(cmdline);
2053 const char *nextArg = strchr(cmdline, ' ');
2055 if (!nextArg) {
2056 cliShowParseError();
2057 } else {
2058 uint32_t length = fastA2I(nextArg);
2060 cliPrintLinef("Reading %u bytes at %u:", length, address);
2062 uint8_t buffer[32];
2063 while (length > 0) {
2064 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2066 for (int i = 0; i < bytesRead; i++) {
2067 cliWrite(buffer[i]);
2070 length -= bytesRead;
2071 address += bytesRead;
2073 if (bytesRead == 0) {
2074 //Assume we reached the end of the volume or something fatal happened
2075 break;
2078 cliPrintLinefeed();
2082 #endif
2083 #endif
2085 #ifdef USE_OSD
2086 static void printOsdLayout(uint8_t dumpMask, const osdConfig_t *osdConfig, const osdConfig_t *osdConfigDefault, int layout, int item)
2088 // "<layout> <item> <col> <row> <visible>"
2089 const char *format = "osd_layout %d %d %d %d %c";
2090 for (int ii = 0; ii < OSD_LAYOUT_COUNT; ii++) {
2091 if (layout >= 0 && layout != ii) {
2092 continue;
2094 const uint16_t *layoutItems = osdConfig->item_pos[ii];
2095 const uint16_t *defaultLayoutItems = osdConfigDefault->item_pos[ii];
2096 for (int jj = 0; jj < OSD_ITEM_COUNT; jj++) {
2097 if (item >= 0 && item != jj) {
2098 continue;
2100 bool equalsDefault = layoutItems[jj] == defaultLayoutItems[jj];
2101 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2102 ii, jj,
2103 OSD_X(defaultLayoutItems[jj]),
2104 OSD_Y(defaultLayoutItems[jj]),
2105 OSD_VISIBLE(defaultLayoutItems[jj]) ? 'V' : 'H');
2107 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2108 ii, jj,
2109 OSD_X(layoutItems[jj]),
2110 OSD_Y(layoutItems[jj]),
2111 OSD_VISIBLE(layoutItems[jj]) ? 'V' : 'H');
2116 static void cliOsdLayout(char *cmdline)
2118 char * saveptr;
2120 int layout = -1;
2121 int item = -1;
2122 int col = 0;
2123 int row = 0;
2124 bool visible = false;
2125 char *tok = strtok_r(cmdline, " ", &saveptr);
2127 int ii;
2129 for (ii = 0; tok != NULL; ii++, tok = strtok_r(NULL, " ", &saveptr)) {
2130 switch (ii) {
2131 case 0:
2132 layout = fastA2I(tok);
2133 if (layout < 0 || layout >= OSD_LAYOUT_COUNT) {
2134 cliShowParseError();
2135 return;
2137 break;
2138 case 1:
2139 item = fastA2I(tok);
2140 if (item < 0 || item >= OSD_ITEM_COUNT) {
2141 cliShowParseError();
2142 return;
2144 break;
2145 case 2:
2146 col = fastA2I(tok);
2147 if (col < 0 || col > OSD_X(OSD_POS_MAX)) {
2148 cliShowParseError();
2149 return;
2151 break;
2152 case 3:
2153 row = fastA2I(tok);
2154 if (row < 0 || row > OSD_Y(OSD_POS_MAX)) {
2155 cliShowParseError();
2156 return;
2158 break;
2159 case 4:
2160 switch (*tok) {
2161 case 'H':
2162 visible = false;
2163 break;
2164 case 'V':
2165 visible = true;
2166 break;
2167 default:
2168 cliShowParseError();
2169 return;
2171 break;
2172 default:
2173 cliShowParseError();
2174 return;
2178 switch (ii) {
2179 case 0:
2180 FALLTHROUGH;
2181 case 1:
2182 FALLTHROUGH;
2183 case 2:
2184 // No args, or just layout or layout and item. If any of them not provided,
2185 // it will be the -1 that we used during initialization, so printOsdLayout()
2186 // won't use them for filtering.
2187 printOsdLayout(DUMP_MASTER, osdConfig(), osdConfig(), layout, item);
2188 break;
2189 case 4:
2190 // No visibility provided. Keep the previous one.
2191 visible = OSD_VISIBLE(osdConfig()->item_pos[layout][item]);
2192 FALLTHROUGH;
2193 case 5:
2194 // Layout, item, pos and visibility. Set the item.
2195 osdConfigMutable()->item_pos[layout][item] = OSD_POS(col, row) | (visible ? OSD_VISIBLE_FLAG : 0);
2196 break;
2197 default:
2198 // Unhandled
2199 cliShowParseError();
2200 return;
2204 #endif
2206 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2208 uint32_t mask = featureConfig->enabledFeatures;
2209 uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2210 for (uint32_t i = 0; ; i++) { // disable all feature first
2211 if (featureNames[i] == NULL)
2212 break;
2213 if (featureNames[i][0] == '\0')
2214 continue;
2215 const char *format = "feature -%s";
2216 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2217 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2219 for (uint32_t i = 0; ; i++) { // reenable what we want.
2220 if (featureNames[i] == NULL)
2221 break;
2222 if (featureNames[i][0] == '\0')
2223 continue;
2224 const char *format = "feature %s";
2225 if (defaultMask & (1 << i)) {
2226 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2228 if (mask & (1 << i)) {
2229 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2234 static void cliFeature(char *cmdline)
2236 uint32_t len = strlen(cmdline);
2237 uint32_t mask = featureMask();
2239 if (len == 0) {
2240 cliPrint("Enabled: ");
2241 for (uint32_t i = 0; ; i++) {
2242 if (featureNames[i] == NULL)
2243 break;
2244 if (featureNames[i][0] == '\0')
2245 continue;
2246 if (mask & (1 << i))
2247 cliPrintf("%s ", featureNames[i]);
2249 cliPrintLinefeed();
2250 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
2251 cliPrint("Available: ");
2252 for (uint32_t i = 0; ; i++) {
2253 if (featureNames[i] == NULL)
2254 break;
2255 if (featureNames[i][0] == '\0')
2256 continue;
2257 cliPrintf("%s ", featureNames[i]);
2259 cliPrintLinefeed();
2260 return;
2261 } else {
2262 bool remove = false;
2263 if (cmdline[0] == '-') {
2264 // remove feature
2265 remove = true;
2266 cmdline++; // skip over -
2267 len--;
2270 for (uint32_t i = 0; ; i++) {
2271 if (featureNames[i] == NULL) {
2272 cliPrintErrorLine("Invalid name");
2273 break;
2276 if (sl_strncasecmp(cmdline, featureNames[i], len) == 0) {
2278 mask = 1 << i;
2279 #ifndef USE_GPS
2280 if (mask & FEATURE_GPS) {
2281 cliPrintErrorLine("unavailable");
2282 break;
2284 #endif
2285 if (remove) {
2286 featureClear(mask);
2287 cliPrint("Disabled");
2288 } else {
2289 featureSet(mask);
2290 cliPrint("Enabled");
2292 cliPrintLinef(" %s", featureNames[i]);
2293 break;
2299 #ifdef BEEPER
2300 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
2302 const uint8_t beeperCount = beeperTableEntryCount();
2303 const uint32_t mask = beeperConfig->beeper_off_flags;
2304 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
2305 for (int i = 0; i < beeperCount - 2; i++) {
2306 const char *formatOff = "beeper -%s";
2307 const char *formatOn = "beeper %s";
2308 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOn : formatOff, beeperNameForTableIndex(i));
2309 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOff : formatOn, beeperNameForTableIndex(i));
2313 static void cliBeeper(char *cmdline)
2315 uint32_t len = strlen(cmdline);
2316 uint8_t beeperCount = beeperTableEntryCount();
2317 uint32_t mask = getBeeperOffMask();
2319 if (len == 0) {
2320 cliPrintf("Disabled:");
2321 for (int32_t i = 0; ; i++) {
2322 if (i == beeperCount - 2){
2323 if (mask == 0)
2324 cliPrint(" none");
2325 break;
2327 if (mask & (1 << (beeperModeForTableIndex(i) - 1)))
2328 cliPrintf(" %s", beeperNameForTableIndex(i));
2330 cliPrintLinefeed();
2331 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
2332 cliPrint("Available:");
2333 for (uint32_t i = 0; i < beeperCount; i++)
2334 cliPrintf(" %s", beeperNameForTableIndex(i));
2335 cliPrintLinefeed();
2336 return;
2337 } else {
2338 bool remove = false;
2339 if (cmdline[0] == '-') {
2340 remove = true; // this is for beeper OFF condition
2341 cmdline++;
2342 len--;
2345 for (uint32_t i = 0; ; i++) {
2346 if (i == beeperCount) {
2347 cliPrintErrorLine("Invalid name");
2348 break;
2350 if (sl_strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
2351 if (remove) { // beeper off
2352 if (i == BEEPER_ALL-1)
2353 beeperOffSetAll(beeperCount-2);
2354 else
2355 if (i == BEEPER_PREFERENCE-1)
2356 setBeeperOffMask(getPreferredBeeperOffMask());
2357 else {
2358 mask = 1 << (beeperModeForTableIndex(i) - 1);
2359 beeperOffSet(mask);
2361 cliPrint("Disabled");
2363 else { // beeper on
2364 if (i == BEEPER_ALL-1)
2365 beeperOffClearAll();
2366 else
2367 if (i == BEEPER_PREFERENCE-1)
2368 setPreferredBeeperOffMask(getBeeperOffMask());
2369 else {
2370 mask = 1 << (beeperModeForTableIndex(i) - 1);
2371 beeperOffClear(mask);
2373 cliPrint("Enabled");
2375 cliPrintLinef(" %s", beeperNameForTableIndex(i));
2376 break;
2381 #endif
2383 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
2385 bool equalsDefault = true;
2386 char buf[16];
2387 char bufDefault[16];
2388 uint32_t i;
2390 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
2391 buf[i] = bufDefault[i] = 0;
2394 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
2395 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
2396 if (defaultRxConfig) {
2397 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
2398 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
2401 buf[i] = '\0';
2403 const char *formatMap = "map %s";
2404 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
2405 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
2408 static void cliMap(char *cmdline)
2410 uint32_t len;
2411 char out[5];
2413 len = strlen(cmdline);
2415 if (len == 4) {
2416 // uppercase it
2417 for (uint32_t i = 0; i < 4; i++)
2418 cmdline[i] = sl_toupper((unsigned char)cmdline[i]);
2419 for (uint32_t i = 0; i < 4; i++) {
2420 if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i]))
2421 continue;
2422 cliShowParseError();
2423 return;
2425 parseRcChannels(cmdline);
2426 } else if (len != 0)
2427 cliShowParseError();
2428 cliPrint("Map: ");
2429 uint32_t i;
2430 for (i = 0; i < 4; i++)
2431 out[rxConfig()->rcmap[i]] = rcChannelLetters[i];
2432 out[i] = '\0';
2433 cliPrintLinef("%s", out);
2436 static const char *checkCommand(const char *cmdLine, const char *command)
2438 if (!sl_strncasecmp(cmdLine, command, strlen(command)) // command names match
2439 && !sl_isalnum((unsigned)cmdLine[strlen(command)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
2440 return cmdLine + strlen(command) + 1;
2441 } else {
2442 return 0;
2446 static void cliRebootEx(bool bootLoader)
2448 cliPrint("\r\nRebooting");
2449 bufWriterFlush(cliWriter);
2450 waitForSerialPortToFinishTransmitting(cliPort);
2452 fcReboot(bootLoader);
2455 static void cliReboot(void)
2457 cliRebootEx(false);
2460 static void cliDfu(char *cmdline)
2462 UNUSED(cmdline);
2463 #ifndef CLI_MINIMAL_VERBOSITY
2464 cliPrint("\r\nRestarting in DFU mode");
2465 #endif
2466 cliRebootEx(true);
2469 #ifdef USE_RX_ELERES
2470 static void cliEleresBind(char *cmdline)
2472 UNUSED(cmdline);
2474 if (!(rxConfig()->receiverType == RX_TYPE_SPI && rxConfig()->rx_spi_protocol == RFM22_ELERES)) {
2475 cliPrintLine("Eleres not active. Please enable feature ELERES and restart IMU");
2476 return;
2479 cliPrintLine("Waiting for correct bind signature....");
2480 bufWriterFlush(cliWriter);
2481 if (eleresBind()) {
2482 cliPrintLine("Bind timeout!");
2483 } else {
2484 cliPrintLine("Bind OK!\r\nPlease restart your transmitter.");
2487 #endif // USE_RX_ELERES
2489 static void cliExit(char *cmdline)
2491 UNUSED(cmdline);
2493 #ifndef CLI_MINIMAL_VERBOSITY
2494 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
2495 #endif
2496 bufWriterFlush(cliWriter);
2498 *cliBuffer = '\0';
2499 bufferIndex = 0;
2500 cliMode = 0;
2501 // incase a motor was left running during motortest, clear it here
2502 mixerResetDisarmedMotors();
2503 cliReboot();
2505 cliWriter = NULL;
2508 #ifdef USE_GPS
2509 static void cliGpsPassthrough(char *cmdline)
2511 UNUSED(cmdline);
2513 gpsEnablePassthrough(cliPort);
2515 #endif
2517 static void cliMotor(char *cmdline)
2519 int motor_index = 0;
2520 int motor_value = 0;
2521 int index = 0;
2522 char *pch = NULL;
2523 char *saveptr;
2525 if (isEmpty(cmdline)) {
2526 cliShowParseError();
2528 return;
2531 pch = strtok_r(cmdline, " ", &saveptr);
2532 while (pch != NULL) {
2533 switch (index) {
2534 case 0:
2535 motor_index = fastA2I(pch);
2536 break;
2537 case 1:
2538 motor_value = fastA2I(pch);
2539 break;
2541 index++;
2542 pch = strtok_r(NULL, " ", &saveptr);
2545 if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
2546 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
2547 return;
2550 if (index == 2) {
2551 if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
2552 cliShowArgumentRangeError("value", 1000, 2000);
2553 return;
2554 } else {
2555 motor_disarmed[motor_index] = motor_value;
2559 cliPrintLinef("motor %d: %d", motor_index, motor_disarmed[motor_index]);
2562 #ifdef PLAY_SOUND
2563 static void cliPlaySound(char *cmdline)
2565 int i;
2566 const char *name;
2567 static int lastSoundIdx = -1;
2569 if (isEmpty(cmdline)) {
2570 i = lastSoundIdx + 1; //next sound index
2571 if ((name=beeperNameForTableIndex(i)) == NULL) {
2572 while (true) { //no name for index; try next one
2573 if (++i >= beeperTableEntryCount())
2574 i = 0; //if end then wrap around to first entry
2575 if ((name=beeperNameForTableIndex(i)) != NULL)
2576 break; //if name OK then play sound below
2577 if (i == lastSoundIdx + 1) { //prevent infinite loop
2578 cliPrintLine("Error playing sound");
2579 return;
2583 } else { //index value was given
2584 i = fastA2I(cmdline);
2585 if ((name=beeperNameForTableIndex(i)) == NULL) {
2586 cliPrintLinef("No sound for index %d", i);
2587 return;
2590 lastSoundIdx = i;
2591 beeperSilence();
2592 cliPrintLinef("Playing sound %d: %s", i, name);
2593 beeper(beeperModeForTableIndex(i));
2595 #endif
2597 static void cliProfile(char *cmdline)
2599 // CLI profile index is 1-based
2600 if (isEmpty(cmdline)) {
2601 cliPrintLinef("profile %d", getConfigProfile() + 1);
2602 return;
2603 } else {
2604 const int i = fastA2I(cmdline) - 1;
2605 if (i >= 0 && i < MAX_PROFILE_COUNT) {
2606 setConfigProfileAndWriteEEPROM(i);
2607 cliProfile("");
2612 static void cliDumpProfile(uint8_t profileIndex, uint8_t dumpMask)
2614 if (profileIndex >= MAX_PROFILE_COUNT) {
2615 // Faulty values
2616 return;
2618 setConfigProfile(profileIndex);
2619 cliPrintHashLine("profile");
2620 cliPrintLinef("profile %d\r\n", getConfigProfile() + 1);
2621 dumpAllValues(PROFILE_VALUE, dumpMask);
2622 dumpAllValues(CONTROL_RATE_VALUE, dumpMask);
2625 static void cliBatteryProfile(char *cmdline)
2627 // CLI profile index is 1-based
2628 if (isEmpty(cmdline)) {
2629 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
2630 return;
2631 } else {
2632 const int i = fastA2I(cmdline) - 1;
2633 if (i >= 0 && i < MAX_PROFILE_COUNT) {
2634 setConfigBatteryProfileAndWriteEEPROM(i);
2635 cliBatteryProfile("");
2640 static void cliDumpBatteryProfile(uint8_t profileIndex, uint8_t dumpMask)
2642 if (profileIndex >= MAX_BATTERY_PROFILE_COUNT) {
2643 // Faulty values
2644 return;
2646 setConfigBatteryProfile(profileIndex);
2647 cliPrintHashLine("battery_profile");
2648 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
2649 dumpAllValues(BATTERY_CONFIG_VALUE, dumpMask);
2652 #ifdef USE_CLI_BATCH
2653 static void cliPrintCommandBatchWarning(const char *warning)
2655 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
2656 if (warning) {
2657 cliPrintErrorLinef(warning);
2661 static void resetCommandBatch(void)
2663 commandBatchActive = false;
2664 commandBatchError = false;
2667 static void cliBatch(char *cmdline)
2669 if (strncasecmp(cmdline, "start", 5) == 0) {
2670 if (!commandBatchActive) {
2671 commandBatchActive = true;
2672 commandBatchError = false;
2674 cliPrintLine("Command batch started");
2675 } else if (strncasecmp(cmdline, "end", 3) == 0) {
2676 if (commandBatchActive && commandBatchError) {
2677 cliPrintCommandBatchWarning(NULL);
2678 } else {
2679 cliPrintLine("Command batch ended");
2681 resetCommandBatch();
2682 } else {
2683 cliPrintErrorLinef("Invalid option");
2686 #endif
2688 static void cliSave(char *cmdline)
2690 UNUSED(cmdline);
2692 #ifdef USE_CLI_BATCH
2693 if (commandBatchActive && commandBatchError) {
2694 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
2695 resetCommandBatch();
2696 return;
2698 #endif
2700 cliPrint("Saving");
2701 //copyCurrentProfileToProfileSlot(getConfigProfile();
2702 writeEEPROM();
2703 cliReboot();
2706 static void cliDefaults(char *cmdline)
2708 UNUSED(cmdline);
2710 cliPrint("Resetting to defaults");
2711 resetEEPROM();
2713 #ifdef USE_CLI_BATCH
2714 commandBatchError = false;
2715 #endif
2717 if (!checkCommand(cmdline, "noreboot"))
2718 cliReboot();
2721 static void cliGet(char *cmdline)
2723 const setting_t *val;
2724 int matchedCommands = 0;
2725 char name[SETTING_MAX_NAME_LENGTH];
2727 while(*cmdline == ' ') ++cmdline; // ignore spaces
2729 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
2730 val = settingGet(i);
2731 if (settingNameContains(val, name, cmdline)) {
2732 cliPrintf("%s = ", name);
2733 cliPrintVar(val, 0);
2734 cliPrintLinefeed();
2735 cliPrintVarRange(val);
2736 cliPrintLinefeed();
2738 matchedCommands++;
2743 if (matchedCommands) {
2744 return;
2747 cliPrintErrorLine("Invalid name");
2750 static void cliSet(char *cmdline)
2752 uint32_t len;
2753 const setting_t *val;
2754 char *eqptr = NULL;
2755 char name[SETTING_MAX_NAME_LENGTH];
2757 while(*cmdline == ' ') ++cmdline; // ignore spaces
2759 len = strlen(cmdline);
2761 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
2762 cliPrintLine("Current settings:");
2763 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
2764 val = settingGet(i);
2765 settingGetName(val, name);
2766 cliPrintf("%s = ", name);
2767 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
2768 cliPrintLinefeed();
2770 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
2771 // has equals
2773 char *lastNonSpaceCharacter = eqptr;
2774 while (*(lastNonSpaceCharacter - 1) == ' ') {
2775 lastNonSpaceCharacter--;
2777 uint8_t variableNameLength = lastNonSpaceCharacter - cmdline;
2779 // skip the '=' and any ' ' characters
2780 eqptr++;
2781 while (*(eqptr) == ' ') {
2782 eqptr++;
2785 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
2786 val = settingGet(i);
2787 // ensure exact match when setting to prevent setting variables with shorter names
2788 if (settingNameIsExactMatch(val, name, cmdline, variableNameLength)) {
2789 const setting_type_e type = SETTING_TYPE(val);
2790 if (type == VAR_STRING) {
2791 settingSetString(val, eqptr, strlen(eqptr));
2792 return;
2794 const setting_mode_e mode = SETTING_MODE(val);
2795 bool changeValue = false;
2796 int_float_value_t tmp = {0};
2797 switch (mode) {
2798 case MODE_DIRECT: {
2799 if (*eqptr != 0 && strspn(eqptr, "0123456789.+-") == strlen(eqptr)) {
2800 float valuef = fastA2F(eqptr);
2801 // note: compare float values
2802 if (valuef >= (float)settingGetMin(val) && valuef <= (float)settingGetMax(val)) {
2804 if (type == VAR_FLOAT)
2805 tmp.float_value = valuef;
2806 else if (type == VAR_UINT32)
2807 tmp.uint_value = fastA2UL(eqptr);
2808 else
2809 tmp.int_value = fastA2I(eqptr);
2811 changeValue = true;
2815 break;
2816 case MODE_LOOKUP: {
2817 const lookupTableEntry_t *tableEntry = settingLookupTable(val);
2818 bool matched = false;
2819 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
2820 matched = sl_strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
2822 if (matched) {
2823 tmp.int_value = tableValueIndex;
2824 changeValue = true;
2828 break;
2831 if (changeValue) {
2832 cliSetIntFloatVar(val, tmp);
2834 cliPrintf("%s set to ", name);
2835 cliPrintVar(val, 0);
2836 } else {
2837 cliPrintError("Invalid value. ");
2838 cliPrintVarRange(val);
2839 cliPrintLinefeed();
2842 return;
2845 cliPrintErrorLine("Invalid name");
2846 } else {
2847 // no equals, check for matching variables.
2848 cliGet(cmdline);
2852 static const char * getBatteryStateString(void)
2854 static const char * const batteryStateStrings[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
2856 return batteryStateStrings[getBatteryState()];
2859 static void cliStatus(char *cmdline)
2861 UNUSED(cmdline);
2863 char buf[MAX(FORMATTED_DATE_TIME_BUFSIZE, SETTING_MAX_NAME_LENGTH)];
2864 dateTime_t dt;
2866 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
2867 rtcGetDateTime(&dt);
2868 dateTimeFormatLocal(buf, &dt);
2869 cliPrintLinef("Current Time: %s", buf);
2870 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
2871 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
2873 const uint32_t detectedSensorsMask = sensorsMask();
2875 for (int i = 0; i < SENSOR_INDEX_COUNT; i++) {
2877 const uint32_t mask = (1 << i);
2878 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
2879 const int sensorHardwareIndex = detectedSensors[i];
2880 if (sensorHardwareNames[i]) {
2881 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
2882 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
2886 cliPrintLinefeed();
2888 cliPrintLine("STM32 system clocks:");
2889 #if defined(USE_HAL_DRIVER)
2890 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
2891 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
2892 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
2893 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
2894 #else
2895 RCC_ClocksTypeDef clocks;
2896 RCC_GetClocksFreq(&clocks);
2897 cliPrintLinef(" SYSCLK = %d MHz", clocks.SYSCLK_Frequency / 1000000);
2898 cliPrintLinef(" HCLK = %d MHz", clocks.HCLK_Frequency / 1000000);
2899 cliPrintLinef(" PCLK1 = %d MHz", clocks.PCLK1_Frequency / 1000000);
2900 cliPrintLinef(" PCLK2 = %d MHz", clocks.PCLK2_Frequency / 1000000);
2901 #endif
2903 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
2904 hardwareSensorStatusNames[getHwGyroStatus()],
2905 hardwareSensorStatusNames[getHwAccelerometerStatus()],
2906 hardwareSensorStatusNames[getHwCompassStatus()],
2907 hardwareSensorStatusNames[getHwBarometerStatus()],
2908 hardwareSensorStatusNames[getHwRangefinderStatus()],
2909 hardwareSensorStatusNames[getHwOpticalFlowStatus()],
2910 hardwareSensorStatusNames[getHwGPSStatus()]
2913 #ifdef USE_SDCARD
2914 cliSdInfo(NULL);
2915 #endif
2916 #ifdef USE_I2C
2917 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
2918 #else
2919 const uint16_t i2cErrorCounter = 0;
2920 #endif
2922 #ifdef STACK_CHECK
2923 cliPrintf("Stack used: %d, ", stackUsedSize());
2924 #endif
2925 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
2927 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
2929 #ifdef USE_ADC
2930 static char * adcFunctions[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
2931 cliPrintLine("ADC channel usage:");
2932 for (int i = 0; i < ADC_FUNCTION_COUNT; i++) {
2933 cliPrintf(" %8s :", adcFunctions[i]);
2935 cliPrint(" configured = ");
2936 if (adcChannelConfig()->adcFunctionChannel[i] == ADC_CHN_NONE) {
2937 cliPrint("none");
2939 else {
2940 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel[i]);
2943 cliPrint(", used = ");
2944 if (adcGetFunctionChannelAllocation(i) == ADC_CHN_NONE) {
2945 cliPrintLine("none");
2947 else {
2948 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i));
2951 #endif
2953 cliPrintf("System load: %d", averageSystemLoadPercent);
2954 const timeDelta_t pidTaskDeltaTime = getTaskDeltaTime(TASK_GYROPID);
2955 const int pidRate = pidTaskDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)pidTaskDeltaTime));
2956 const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
2957 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
2958 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime, pidRate, rxRate, systemRate);
2959 #if !defined(CLI_MINIMAL_VERBOSITY)
2960 cliPrint("Arming disabled flags:");
2961 uint32_t flags = armingFlags & ARMING_DISABLED_ALL_FLAGS;
2962 while (flags) {
2963 int bitpos = ffs(flags) - 1;
2964 flags &= ~(1 << bitpos);
2965 if (bitpos > 6) cliPrintf(" %s", armingDisableFlagNames[bitpos - 7]);
2967 cliPrintLinefeed();
2968 if (armingFlags & ARMING_DISABLED_INVALID_SETTING) {
2969 unsigned invalidIndex;
2970 if (!settingsValidate(&invalidIndex)) {
2971 settingGetName(settingGet(invalidIndex), buf);
2972 cliPrintErrorLinef("Invalid setting: %s", buf);
2975 #else
2976 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags & ARMING_DISABLED_ALL_FLAGS);
2977 #endif
2979 #if defined(USE_VTX_CONTROL) && !defined(CLI_MINIMAL_VERBOSITY)
2980 cliPrint("VTX: ");
2982 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
2983 vtxDeviceOsdInfo_t osdInfo;
2984 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo);
2985 cliPrintf("band: %c, chan: %s, power: %c", osdInfo.bandLetter, osdInfo.channelName, osdInfo.powerIndexLetter);
2987 if (osdInfo.powerMilliwatt) {
2988 cliPrintf(" (%d mW)", osdInfo.powerMilliwatt);
2991 if (osdInfo.frequency) {
2992 cliPrintf(", freq: %d MHz", osdInfo.frequency);
2995 else {
2996 cliPrint("not detected");
2999 cliPrintLinefeed();
3000 #endif
3002 // If we are blocked by PWM init - provide more information
3003 if (getPwmInitError() != PWM_INIT_ERROR_NONE) {
3004 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3008 #ifndef SKIP_TASK_STATISTICS
3009 static void cliTasks(char *cmdline)
3011 UNUSED(cmdline);
3012 int maxLoadSum = 0;
3013 int averageLoadSum = 0;
3014 cfCheckFuncInfo_t checkFuncInfo;
3016 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3017 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3018 cfTaskInfo_t taskInfo;
3019 getTaskInfo(taskId, &taskInfo);
3020 if (taskInfo.isEnabled) {
3021 const int taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3022 const int maxLoad = (taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3023 const int averageLoad = (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3024 if (taskId != TASK_SERIAL) {
3025 maxLoadSum += maxLoad;
3026 averageLoadSum += averageLoad;
3028 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
3029 taskId, taskInfo.taskName, taskFrequency, (uint32_t)taskInfo.maxExecutionTime, (uint32_t)taskInfo.averageExecutionTime,
3030 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, (uint32_t)taskInfo.totalExecutionTime / 1000);
3033 getCheckFuncInfo(&checkFuncInfo);
3034 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo.maxExecutionTime, (uint32_t)checkFuncInfo.averageExecutionTime, (uint32_t)checkFuncInfo.totalExecutionTime / 1000);
3035 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3037 #endif
3039 static void cliVersion(char *cmdline)
3041 UNUSED(cmdline);
3043 cliPrintLinef("# %s/%s %s %s / %s (%s)",
3044 FC_FIRMWARE_NAME,
3045 targetName,
3046 FC_VERSION_STRING,
3047 buildDate,
3048 buildTime,
3049 shortGitRevision
3051 cliPrintLinef("# GCC-%s",
3052 compilerVersion
3056 static void cliMemory(char *cmdline)
3058 UNUSED(cmdline);
3059 cliPrintLinef("Dynamic memory usage:");
3060 for (unsigned i = 0; i < OWNER_TOTAL_COUNT; i++) {
3061 const char * owner = ownerNames[i];
3062 const uint32_t memUsed = memGetUsedBytesByOwner(i);
3064 if (memUsed) {
3065 cliPrintLinef("%s : %d bytes", owner, memUsed);
3070 #if !defined(SKIP_TASK_STATISTICS) && !defined(SKIP_CLI_RESOURCES)
3071 static void cliResource(char *cmdline)
3073 UNUSED(cmdline);
3074 cliPrintLinef("IO:\r\n----------------------");
3075 for (unsigned i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3076 const char* owner;
3077 owner = ownerNames[ioRecs[i].owner];
3079 const char* resource;
3080 resource = resourceNames[ioRecs[i].resource];
3082 if (ioRecs[i].index > 0) {
3083 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, ioRecs[i].index, resource);
3084 } else {
3085 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, resource);
3089 #endif
3091 static void backupConfigs(void)
3093 // make copies of configs to do differencing
3094 PG_FOREACH(pg) {
3095 if (pgIsProfile(pg)) {
3096 memcpy(pg->copy, pg->address, pgSize(pg) * MAX_PROFILE_COUNT);
3097 } else {
3098 memcpy(pg->copy, pg->address, pgSize(pg));
3103 static void restoreConfigs(void)
3105 PG_FOREACH(pg) {
3106 if (pgIsProfile(pg)) {
3107 memcpy(pg->address, pg->copy, pgSize(pg) * MAX_PROFILE_COUNT);
3108 } else {
3109 memcpy(pg->address, pg->copy, pgSize(pg));
3114 static void printConfig(const char *cmdline, bool doDiff)
3116 uint8_t dumpMask = DUMP_MASTER;
3117 const char *options;
3118 if ((options = checkCommand(cmdline, "master"))) {
3119 dumpMask = DUMP_MASTER; // only
3120 } else if ((options = checkCommand(cmdline, "profile"))) {
3121 dumpMask = DUMP_PROFILE; // only
3122 } else if ((options = checkCommand(cmdline, "battery_profile"))) {
3123 dumpMask = DUMP_BATTERY_PROFILE; // only
3124 } else if ((options = checkCommand(cmdline, "all"))) {
3125 dumpMask = DUMP_ALL; // all profiles and rates
3126 } else {
3127 options = cmdline;
3130 if (doDiff) {
3131 dumpMask = dumpMask | DO_DIFF;
3134 const int currentProfileIndexSave = getConfigProfile();
3135 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
3136 backupConfigs();
3137 // reset all configs to defaults to do differencing
3138 resetConfigs();
3139 // restore the profile indices, since they should not be reset for proper comparison
3140 setConfigProfile(currentProfileIndexSave);
3141 setConfigBatteryProfile(currentBatteryProfileIndexSave);
3143 if (checkCommand(options, "showdefaults")) {
3144 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
3147 #ifdef USE_CLI_BATCH
3148 bool batchModeEnabled = false;
3149 #endif
3151 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
3152 cliPrintHashLine("version");
3153 cliVersion(NULL);
3155 #ifdef USE_CLI_BATCH
3156 cliPrintHashLine("start the command batch");
3157 cliPrintLine("batch start");
3158 batchModeEnabled = true;
3159 #endif
3161 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
3162 #ifndef CLI_MINIMAL_VERBOSITY
3163 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
3164 #else
3165 cliPrintLinef("defaults noreboot");
3166 #endif
3169 cliPrintHashLine("resources");
3170 //printResource(dumpMask, &defaultConfig);
3172 cliPrintHashLine("mixer");
3173 cliDumpPrintLinef(dumpMask, primaryMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
3175 printMotorMix(dumpMask, primaryMotorMixer_CopyArray, primaryMotorMixer(0));
3177 // print custom servo mixer if exists
3178 cliPrintHashLine("servo mix");
3179 cliDumpPrintLinef(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n");
3180 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
3182 // print servo parameters
3183 cliPrintHashLine("servo");
3184 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
3186 #ifdef USE_LOGIC_CONDITIONS
3187 cliPrintHashLine("logic");
3188 printLogic(dumpMask, logicConditions_CopyArray, logicConditions(0));
3189 #endif
3191 #ifdef USE_GLOBAL_FUNCTIONS
3192 cliPrintHashLine("gf");
3193 printGlobalFunctions(dumpMask, globalFunctions_CopyArray, globalFunctions(0));
3194 #endif
3196 cliPrintHashLine("feature");
3197 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
3199 #ifdef BEEPER
3200 cliPrintHashLine("beeper");
3201 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
3202 #endif
3204 cliPrintHashLine("map");
3205 printMap(dumpMask, &rxConfig_Copy, rxConfig());
3207 cliPrintHashLine("serial");
3208 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
3210 #ifdef USE_LED_STRIP
3211 cliPrintHashLine("led");
3212 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
3214 cliPrintHashLine("color");
3215 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
3217 cliPrintHashLine("mode_color");
3218 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
3219 #endif
3221 cliPrintHashLine("aux");
3222 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
3224 cliPrintHashLine("adjrange");
3225 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
3227 cliPrintHashLine("rxrange");
3228 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
3230 #ifdef USE_TEMPERATURE_SENSOR
3231 cliPrintHashLine("temp_sensor");
3232 printTempSensor(dumpMask, tempSensorConfig_CopyArray, tempSensorConfig(0));
3233 #endif
3235 #if defined(USE_NAV) && defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
3236 cliPrintHashLine("wp");
3237 printWaypoints(dumpMask, posControl.waypointList, nonVolatileWaypointList(0));
3238 #endif
3240 #ifdef USE_OSD
3241 cliPrintHashLine("osd_layout");
3242 printOsdLayout(dumpMask, &osdConfig_Copy, osdConfig(), -1, -1);
3243 #endif
3245 cliPrintHashLine("master");
3246 dumpAllValues(MASTER_VALUE, dumpMask);
3248 if (dumpMask & DUMP_ALL) {
3249 // dump all profiles
3250 const int currentProfileIndexSave = getConfigProfile();
3251 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
3252 for (int ii = 0; ii < MAX_PROFILE_COUNT; ++ii) {
3253 cliDumpProfile(ii, dumpMask);
3255 for (int ii = 0; ii < MAX_BATTERY_PROFILE_COUNT; ++ii) {
3256 cliDumpBatteryProfile(ii, dumpMask);
3258 setConfigProfile(currentProfileIndexSave);
3259 setConfigBatteryProfile(currentBatteryProfileIndexSave);
3261 cliPrintHashLine("restore original profile selection");
3262 cliPrintLinef("profile %d", currentProfileIndexSave + 1);
3263 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave + 1);
3265 cliPrintHashLine("save configuration\r\nsave");
3266 #ifdef USE_CLI_BATCH
3267 batchModeEnabled = false;
3268 #endif
3269 } else {
3270 // dump just the current profiles
3271 cliDumpProfile(getConfigProfile(), dumpMask);
3272 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
3276 if (dumpMask & DUMP_PROFILE) {
3277 cliDumpProfile(getConfigProfile(), dumpMask);
3280 if (dumpMask & DUMP_BATTERY_PROFILE) {
3281 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
3284 #ifdef USE_CLI_BATCH
3285 if (batchModeEnabled) {
3286 cliPrintHashLine("end the command batch");
3287 cliPrintLine("batch end");
3289 #endif
3291 // restore configs from copies
3292 restoreConfigs();
3295 static void cliDump(char *cmdline)
3297 printConfig(cmdline, false);
3300 static void cliDiff(char *cmdline)
3302 printConfig(cmdline, true);
3305 #ifdef USE_USB_MSC
3306 static void cliMsc(char *cmdline)
3308 UNUSED(cmdline);
3310 if (false
3311 #ifdef USE_SDCARD
3312 || sdcard_isFunctional()
3313 #endif
3314 #ifdef USE_FLASHFS
3315 || flashfsGetSize() > 0
3316 #endif
3318 cliPrintHashLine("restarting in mass storage mode");
3319 cliPrint("\r\nRebooting");
3320 bufWriterFlush(cliWriter);
3321 delay(1000);
3322 waitForSerialPortToFinishTransmitting(cliPort);
3323 stopPwmAllMotors();
3325 #ifdef STM32F7
3326 *((__IO uint32_t*) BKPSRAM_BASE + 16) = MSC_MAGIC;
3327 #elif defined(STM32F4)
3328 *((uint32_t *)0x2001FFF0) = MSC_MAGIC;
3329 #endif
3331 __disable_irq();
3332 NVIC_SystemReset();
3333 } else {
3334 cliPrint("\r\nStorage not present or failed to initialize!");
3335 bufWriterFlush(cliWriter);
3338 #endif
3341 typedef struct {
3342 const char *name;
3343 #ifndef SKIP_CLI_COMMAND_HELP
3344 const char *description;
3345 const char *args;
3346 #endif
3347 void (*func)(char *cmdline);
3348 } clicmd_t;
3350 #ifndef SKIP_CLI_COMMAND_HELP
3351 #define CLI_COMMAND_DEF(name, description, args, method) \
3353 name , \
3354 description , \
3355 args , \
3356 method \
3358 #else
3359 #define CLI_COMMAND_DEF(name, description, args, method) \
3361 name, \
3362 method \
3364 #endif
3366 static void cliHelp(char *cmdline);
3368 // should be sorted a..z for bsearch()
3369 const clicmd_t cmdTable[] = {
3370 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
3371 #if defined(USE_ASSERT)
3372 CLI_COMMAND_DEF("assert", "", NULL, cliAssert),
3373 #endif
3374 CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
3375 #ifdef USE_CLI_BATCH
3376 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
3377 #endif
3378 #ifdef BEEPER
3379 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3380 "\t<+|->[name]", cliBeeper),
3381 #endif
3382 #if defined(USE_BOOTLOG)
3383 CLI_COMMAND_DEF("bootlog", "show boot events", NULL, cliBootlog),
3384 #endif
3385 #ifdef USE_LED_STRIP
3386 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
3387 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
3388 #endif
3389 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
3390 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL, cliDfu),
3391 CLI_COMMAND_DEF("diff", "list configuration changes from default",
3392 "[master|battery_profile|profile|rates|all] {showdefaults}", cliDiff),
3393 CLI_COMMAND_DEF("dump", "dump configuration",
3394 "[master|battery_profile|profile|rates|all] {showdefaults}", cliDump),
3395 #ifdef USE_RX_ELERES
3396 CLI_COMMAND_DEF("eleres_bind", NULL, NULL, cliEleresBind),
3397 #endif // USE_RX_ELERES
3398 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
3399 CLI_COMMAND_DEF("feature", "configure features",
3400 "list\r\n"
3401 "\t<+|->[name]", cliFeature),
3402 #ifdef USE_FLASHFS
3403 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
3404 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
3405 #ifdef USE_FLASH_TOOLS
3406 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
3407 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
3408 #endif
3409 #endif
3410 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
3411 #ifdef USE_GPS
3412 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
3413 #endif
3414 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
3415 #ifdef USE_LED_STRIP
3416 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
3417 #endif
3418 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
3419 CLI_COMMAND_DEF("memory", "view memory usage", NULL, cliMemory),
3420 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
3421 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
3422 #ifdef USE_USB_MSC
3423 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
3424 #endif
3425 #ifdef PLAY_SOUND
3426 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]\r\n", cliPlaySound),
3427 #endif
3428 CLI_COMMAND_DEF("profile", "change profile",
3429 "[<index>]", cliProfile),
3430 CLI_COMMAND_DEF("battery_profile", "change battery profile",
3431 "[<index>]", cliBatteryProfile),
3432 #if !defined(SKIP_TASK_STATISTICS) && !defined(SKIP_CLI_RESOURCES)
3433 CLI_COMMAND_DEF("resource", "view currently used resources", NULL, cliResource),
3434 #endif
3435 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
3436 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
3437 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
3438 #ifdef USE_SERIAL_PASSTHROUGH
3439 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough),
3440 #endif
3441 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
3442 #ifdef USE_LOGIC_CONDITIONS
3443 CLI_COMMAND_DEF("logic", "configure logic conditions",
3444 "<rule> <enabled> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
3445 "\treset\r\n", cliLogic),
3446 #endif
3447 #ifdef USE_GLOBAL_FUNCTIONS
3448 CLI_COMMAND_DEF("gf", "configure global functions",
3449 "<rule> <enabled> <logic condition> <action> <operand type> <operand value> <flags>\r\n"
3450 "\treset\r\n", cliGlobalFunctions),
3451 #endif
3452 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
3453 CLI_COMMAND_DEF("smix", "servo mixer",
3454 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
3455 "\treset\r\n", cliServoMix),
3456 #ifdef USE_SDCARD
3457 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
3458 #endif
3459 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
3460 #ifndef SKIP_TASK_STATISTICS
3461 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
3462 #endif
3463 #ifdef USE_TEMPERATURE_SENSOR
3464 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL, cliTempSensor),
3465 #endif
3466 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
3467 #if defined(USE_NAV) && defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
3468 CLI_COMMAND_DEF("wp", "waypoint list", NULL, cliWaypoints),
3469 #endif
3470 #ifdef USE_OSD
3471 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout),
3472 #endif
3475 static void cliHelp(char *cmdline)
3477 UNUSED(cmdline);
3479 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
3480 cliPrint(cmdTable[i].name);
3481 #ifndef SKIP_CLI_COMMAND_HELP
3482 if (cmdTable[i].description) {
3483 cliPrintf(" - %s", cmdTable[i].description);
3485 if (cmdTable[i].args) {
3486 cliPrintf("\r\n\t%s", cmdTable[i].args);
3488 #endif
3489 cliPrintLinefeed();
3493 void cliProcess(void)
3495 if (!cliWriter) {
3496 return;
3499 // Be a little bit tricky. Flush the last inputs buffer, if any.
3500 bufWriterFlush(cliWriter);
3502 while (serialRxBytesWaiting(cliPort)) {
3503 uint8_t c = serialRead(cliPort);
3504 if (c == '\t' || c == '?') {
3505 // do tab completion
3506 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
3507 uint32_t i = bufferIndex;
3508 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
3509 if (bufferIndex && (sl_strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
3510 continue;
3511 if (!pstart)
3512 pstart = cmd;
3513 pend = cmd;
3515 if (pstart) { /* Buffer matches one or more commands */
3516 for (; ; bufferIndex++) {
3517 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
3518 break;
3519 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
3520 /* Unambiguous -- append a space */
3521 cliBuffer[bufferIndex++] = ' ';
3522 cliBuffer[bufferIndex] = '\0';
3523 break;
3525 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
3528 if (!bufferIndex || pstart != pend) {
3529 /* Print list of ambiguous matches */
3530 cliPrint("\r\033[K");
3531 for (cmd = pstart; cmd <= pend; cmd++) {
3532 cliPrint(cmd->name);
3533 cliWrite('\t');
3535 cliPrompt();
3536 i = 0; /* Redraw prompt */
3538 for (; i < bufferIndex; i++)
3539 cliWrite(cliBuffer[i]);
3540 } else if (!bufferIndex && c == 4) { // CTRL-D
3541 cliExit(cliBuffer);
3542 return;
3543 } else if (c == 12) { // NewPage / CTRL-L
3544 // clear screen
3545 cliPrint("\033[2J\033[1;1H");
3546 cliPrompt();
3547 } else if (bufferIndex && (c == '\n' || c == '\r')) {
3548 // enter pressed
3549 cliPrintLinefeed();
3551 // Strip comment starting with # from line
3552 char *p = cliBuffer;
3553 p = strchr(p, '#');
3554 if (NULL != p) {
3555 bufferIndex = (uint32_t)(p - cliBuffer);
3558 // Strip trailing whitespace
3559 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
3560 bufferIndex--;
3563 // Process non-empty lines
3564 if (bufferIndex > 0) {
3565 cliBuffer[bufferIndex] = 0; // null terminate
3567 const clicmd_t *cmd;
3568 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
3569 if (!sl_strncasecmp(cliBuffer, cmd->name, strlen(cmd->name)) // command names match
3570 && !sl_isalnum((unsigned)cliBuffer[strlen(cmd->name)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
3571 break;
3573 if (cmd < cmdTable + ARRAYLEN(cmdTable))
3574 cmd->func(cliBuffer + strlen(cmd->name) + 1);
3575 else
3576 cliPrintError("Unknown command, try 'help'");
3577 bufferIndex = 0;
3580 memset(cliBuffer, 0, sizeof(cliBuffer));
3582 // 'exit' will reset this flag, so we don't need to print prompt again
3583 if (!cliMode)
3584 return;
3586 cliPrompt();
3587 } else if (c == 127) {
3588 // backspace
3589 if (bufferIndex) {
3590 cliBuffer[--bufferIndex] = 0;
3591 cliPrint("\010 \010");
3593 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
3594 if (!bufferIndex && c == ' ')
3595 continue; // Ignore leading spaces
3596 cliBuffer[bufferIndex++] = c;
3597 cliWrite(c);
3602 void cliEnter(serialPort_t *serialPort)
3604 if (cliMode) {
3605 return;
3608 cliMode = 1;
3609 cliPort = serialPort;
3610 setPrintfSerialPort(cliPort);
3611 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
3613 #ifndef CLI_MINIMAL_VERBOSITY
3614 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
3615 #else
3616 cliPrintLine("\r\nCLI");
3617 #endif
3618 cliPrompt();
3620 #ifdef USE_CLI_BATCH
3621 resetCommandBatch();
3622 #endif
3624 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI);
3627 void cliInit(const serialConfig_t *serialConfig)
3629 UNUSED(serialConfig);