add SERVOS to blackbox optional fields list (#10272)
[inav.git] / src / main / fc / cli.c
blob32210d6ceeeb87b5b9f033d2e78782024ba3ca70
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 bool cliMode = false;
30 #include "blackbox/blackbox.h"
32 #include "build/assert.h"
33 #include "build/build_config.h"
34 #include "build/version.h"
36 #include "common/axis.h"
37 #include "common/color.h"
38 #include "common/maths.h"
39 #include "common/printf.h"
40 #include "common/string_light.h"
41 #include "common/memory.h"
42 #include "common/time.h"
43 #include "common/typeconversion.h"
44 #include "common/fp_pid.h"
45 #include "programming/global_variables.h"
46 #include "programming/pid.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/flash.h"
59 #include "drivers/io.h"
60 #include "drivers/io_impl.h"
61 #include "drivers/osd_symbols.h"
62 #include "drivers/persistent.h"
63 #include "drivers/sdcard/sdcard.h"
64 #include "drivers/sensor.h"
65 #include "drivers/serial.h"
66 #include "drivers/stack_check.h"
67 #include "drivers/system.h"
68 #include "drivers/time.h"
69 #include "drivers/usb_msc.h"
70 #include "drivers/vtx_common.h"
71 #include "drivers/light_ws2811strip.h"
73 #include "fc/fc_core.h"
74 #include "fc/cli.h"
75 #include "fc/config.h"
76 #include "fc/controlrate_profile.h"
77 #include "fc/rc_adjustments.h"
78 #include "fc/rc_controls.h"
79 #include "fc/rc_modes.h"
80 #include "fc/runtime_config.h"
81 #include "fc/settings.h"
83 #include "flight/failsafe.h"
84 #include "flight/imu.h"
85 #include "flight/mixer_profile.h"
86 #include "flight/pid.h"
87 #include "flight/servos.h"
89 #include "io/asyncfatfs/asyncfatfs.h"
90 #include "io/beeper.h"
91 #include "io/flashfs.h"
92 #include "io/gps.h"
93 #include "io/gps_ublox.h"
94 #include "io/ledstrip.h"
95 #include "io/osd.h"
96 #include "io/osd/custom_elements.h"
97 #include "io/serial.h"
99 #include "fc/fc_msp_box.h"
101 #include "navigation/navigation.h"
102 #include "navigation/navigation_private.h"
104 #include "rx/rx.h"
105 #include "rx/spektrum.h"
106 #include "rx/srxl2.h"
107 #include "rx/crsf.h"
109 #include "scheduler/scheduler.h"
111 #include "sensors/acceleration.h"
112 #include "sensors/barometer.h"
113 #include "sensors/battery.h"
114 #include "sensors/boardalignment.h"
115 #include "sensors/compass.h"
116 #include "sensors/diagnostics.h"
117 #include "sensors/gyro.h"
118 #include "sensors/pitotmeter.h"
119 #include "sensors/rangefinder.h"
120 #include "sensors/opflow.h"
121 #include "sensors/sensors.h"
122 #include "sensors/temperature.h"
123 #ifdef USE_ESC_SENSOR
124 #include "sensors/esc_sensor.h"
125 #endif
127 #include "telemetry/telemetry.h"
128 #include "build/debug.h"
130 extern timeDelta_t cycleTime; // FIXME dependency on mw.c
131 extern uint8_t detectedSensors[SENSOR_INDEX_COUNT];
133 static serialPort_t *cliPort;
135 static bufWriter_t *cliWriter;
136 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + 128];
138 static char cliBuffer[64];
139 static uint32_t bufferIndex = 0;
140 static uint16_t cliDelayMs = 0;
142 #if defined(USE_ASSERT)
143 static void cliAssert(char *cmdline);
144 #endif
146 #ifdef USE_CLI_BATCH
147 static bool commandBatchActive = false;
148 static bool commandBatchError = false;
149 static uint8_t commandBatchErrorCount = 0;
150 #endif
152 // sync this with features_e
153 static const char * const featureNames[] = {
154 "THR_VBAT_COMP", "VBAT", "TX_PROF_SEL", "BAT_PROF_AUTOSWITCH", "MOTOR_STOP",
155 "", "SOFTSERIAL", "GPS", "RPM_FILTERS",
156 "", "TELEMETRY", "CURRENT_METER", "REVERSIBLE_MOTORS", "",
157 "", "RSSI_ADC", "LED_STRIP", "DASHBOARD", "",
158 "BLACKBOX", "", "TRANSPONDER", "AIRMODE",
159 "SUPEREXPO", "VTX", "", "", "", "PWM_OUTPUT_ENABLE",
160 "OSD", "FW_LAUNCH", "FW_AUTOTRIM", NULL
163 static const char * outputModeNames[] = {
164 "AUTO",
165 "MOTORS",
166 "SERVOS",
167 "LED",
168 NULL
171 #ifdef USE_BLACKBOX
172 static const char * const blackboxIncludeFlagNames[] = {
173 "NAV_ACC",
174 "NAV_POS",
175 "NAV_PID",
176 "MAG",
177 "ACC",
178 "ATTI",
179 "RC_DATA",
180 "RC_COMMAND",
181 "MOTORS",
182 "GYRO_RAW",
183 "PEAKS_R",
184 "PEAKS_P",
185 "PEAKS_Y",
186 "SERVOS",
187 NULL
189 #endif
191 static const char *debugModeNames[DEBUG_COUNT] = {
192 "NONE",
193 "AGL",
194 "FLOW_RAW",
195 "FLOW",
196 "ALWAYS",
197 "SAG_COMP_VOLTAGE",
198 "VIBE",
199 "CRUISE",
200 "REM_FLIGHT_TIME",
201 "SMARTAUDIO",
202 "ACC",
203 "NAV_YAW",
204 "PCF8574",
205 "DYN_GYRO_LPF",
206 "AUTOLEVEL",
207 "ALTITUDE",
208 "AUTOTRIM",
209 "AUTOTUNE",
210 "RATE_DYNAMICS",
211 "LANDING",
212 "POS_EST",
213 "ADAPTIVE_FILTER",
214 "HEADTRACKER",
215 "GPS",
216 "LULU",
217 "SBUS2"
220 /* Sensor names (used in lookup tables for *_hardware settings and in status
221 command output) */
222 // sync with gyroSensor_e
223 static const char *const gyroNames[] = {
224 "NONE", "AUTO", "MPU6000", "MPU6500", "MPU9250", "BMI160",
225 "ICM20689", "BMI088", "ICM42605", "BMI270", "LSM6DXX", "FAKE"};
227 // sync this with sensors_e
228 static const char * const sensorTypeNames[] = {
229 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "PITOT", "OPFLOW", "GPS", "GPS+MAG", NULL
232 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER | SENSOR_PITOT | SENSOR_OPFLOW)
234 static const char * const hardwareSensorStatusNames[] = {
235 "NONE", "OK", "UNAVAILABLE", "FAILING"
238 static const char * const *sensorHardwareNames[] = {
239 gyroNames,
240 table_acc_hardware,
241 #ifdef USE_BARO
242 table_baro_hardware,
243 #else
244 NULL,
245 #endif
246 #ifdef USE_MAG
247 table_mag_hardware,
248 #else
249 NULL,
250 #endif
251 #ifdef USE_RANGEFINDER
252 table_rangefinder_hardware,
253 #else
254 NULL,
255 #endif
256 #ifdef USE_PITOT
257 table_pitot_hardware,
258 #else
259 NULL,
260 #endif
261 #ifdef USE_OPFLOW
262 table_opflow_hardware,
263 #else
264 NULL,
265 #endif
268 static void cliPrint(const char *str)
270 while (*str) {
271 bufWriterAppend(cliWriter, *str++);
275 static void cliPrintLinefeed(void)
277 cliPrint("\r\n");
278 if (cliDelayMs) {
279 delay(cliDelayMs);
283 static void cliPrintLine(const char *str)
285 cliPrint(str);
286 cliPrintLinefeed();
290 static void cliPrintError(const char *str)
292 cliPrint("### ERROR: ");
293 cliPrint(str);
294 #ifdef USE_CLI_BATCH
295 if (commandBatchActive) {
296 commandBatchError = true;
297 commandBatchErrorCount++;
299 #endif
302 static void cliPrintErrorLine(const char *str)
304 cliPrint("### ERROR: ");
305 cliPrintLine(str);
306 #ifdef USE_CLI_BATCH
307 if (commandBatchActive) {
308 commandBatchError = true;
309 commandBatchErrorCount++;
311 #endif
314 #ifdef CLI_MINIMAL_VERBOSITY
315 #define cliPrintHashLine(str)
316 #else
317 static void cliPrintHashLine(const char *str)
319 cliPrint("\r\n# ");
320 cliPrintLine(str);
322 #endif
324 static void cliPutp(void *p, char ch)
326 bufWriterAppend(p, ch);
329 typedef enum {
330 DUMP_MASTER = (1 << 0),
331 DUMP_CONTROL_PROFILE = (1 << 1),
332 DUMP_BATTERY_PROFILE = (1 << 2),
333 DUMP_MIXER_PROFILE = (1 << 3),
334 DUMP_ALL = (1 << 4),
335 DO_DIFF = (1 << 5),
336 SHOW_DEFAULTS = (1 << 6),
337 HIDE_UNUSED = (1 << 7)
338 } dumpFlags_e;
340 static void cliPrintfva(const char *format, va_list va)
342 tfp_format(cliWriter, cliPutp, format, va);
343 bufWriterFlush(cliWriter);
346 static void cliPrintLinefva(const char *format, va_list va)
348 tfp_format(cliWriter, cliPutp, format, va);
349 bufWriterFlush(cliWriter);
350 cliPrintLinefeed();
353 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
355 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
356 va_list va;
357 va_start(va, format);
358 cliPrintLinefva(format, va);
359 va_end(va);
360 return true;
361 } else {
362 return false;
366 static void cliWrite(uint8_t ch)
368 bufWriterAppend(cliWriter, ch);
371 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
373 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
374 cliWrite('#');
376 va_list va;
377 va_start(va, format);
378 cliPrintLinefva(format, va);
379 va_end(va);
380 return true;
381 } else {
382 return false;
386 static void cliPrintf(const char *format, ...)
388 va_list va;
389 va_start(va, format);
390 cliPrintfva(format, va);
391 va_end(va);
395 static void cliPrintLinef(const char *format, ...)
397 va_list va;
398 va_start(va, format);
399 cliPrintLinefva(format, va);
400 va_end(va);
403 static void cliPrintErrorVa(const char *format, va_list va)
405 cliPrint("### ERROR: ");
406 cliPrintfva(format, va);
407 va_end(va);
409 #ifdef USE_CLI_BATCH
410 if (commandBatchActive) {
411 commandBatchError = true;
412 commandBatchErrorCount++;
414 #endif
417 static void cliPrintErrorLinef(const char *format, ...)
419 va_list va;
420 va_start(va, format);
421 cliPrintErrorVa(format, va);
422 cliPrintLinefeed();
425 static void printValuePointer(const setting_t *var, const void *valuePointer, uint32_t full)
427 int32_t value = 0;
428 char buf[SETTING_MAX_NAME_LENGTH];
430 switch (SETTING_TYPE(var)) {
431 case VAR_UINT8:
432 value = *(uint8_t *)valuePointer;
433 break;
435 case VAR_INT8:
436 value = *(int8_t *)valuePointer;
437 break;
439 case VAR_UINT16:
440 value = *(uint16_t *)valuePointer;
441 break;
443 case VAR_INT16:
444 value = *(int16_t *)valuePointer;
445 break;
447 case VAR_UINT32:
448 value = *(uint32_t *)valuePointer;
449 break;
451 case VAR_FLOAT:
452 cliPrintf("%s", ftoa(*(float *)valuePointer, buf));
453 if (full) {
454 if (SETTING_MODE(var) == MODE_DIRECT) {
455 cliPrintf(" %s", ftoa((float)settingGetMin(var), buf));
456 cliPrintf(" %s", ftoa((float)settingGetMax(var), buf));
459 return; // return from case for float only
461 case VAR_STRING:
462 cliPrintf("%s", (const char *)valuePointer);
463 return;
466 switch (SETTING_MODE(var)) {
467 case MODE_DIRECT:
468 if (SETTING_TYPE(var) == VAR_UINT32)
469 cliPrintf("%u", value);
470 else
471 cliPrintf("%d", value);
472 if (full) {
473 if (SETTING_MODE(var) == MODE_DIRECT) {
474 cliPrintf(" %d %u", settingGetMin(var), settingGetMax(var));
477 break;
478 case MODE_LOOKUP:
480 const char *name = settingLookupValueName(var, value);
481 if (name) {
482 cliPrintf(name);
483 } else {
484 settingGetName(var, buf);
485 cliPrintErrorLinef("VALUE %d OUT OF RANGE FOR %s", (int)value, buf);
487 break;
492 static bool valuePtrEqualsDefault(const setting_t *value, const void *ptr, const void *ptrDefault)
494 bool result = false;
495 switch (SETTING_TYPE(value)) {
496 case VAR_UINT8:
497 result = *(uint8_t *)ptr == *(uint8_t *)ptrDefault;
498 break;
500 case VAR_INT8:
501 result = *(int8_t *)ptr == *(int8_t *)ptrDefault;
502 break;
504 case VAR_UINT16:
505 result = *(uint16_t *)ptr == *(uint16_t *)ptrDefault;
506 break;
508 case VAR_INT16:
509 result = *(int16_t *)ptr == *(int16_t *)ptrDefault;
510 break;
512 case VAR_UINT32:
513 result = *(uint32_t *)ptr == *(uint32_t *)ptrDefault;
514 break;
516 case VAR_FLOAT:
517 result = *(float *)ptr == *(float *)ptrDefault;
518 break;
520 case VAR_STRING:
521 result = strncmp(ptr, ptrDefault, settingGetStringMaxLength(value) + 1) == 0;
522 break;
524 return result;
527 static void dumpPgValue(const setting_t *value, uint8_t dumpMask)
529 char name[SETTING_MAX_NAME_LENGTH];
530 const char *format = "set %s = ";
531 const char *defaultFormat = "#set %s = ";
532 // During a dump, the PGs have been backed up to their "copy"
533 // regions and the actual values have been reset to its
534 // defaults. This means that settingGetValuePointer() will
535 // return the default value while settingGetCopyValuePointer()
536 // will return the actual value.
537 const void *valuePointer = settingGetCopyValuePointer(value);
538 const void *defaultValuePointer = settingGetValuePointer(value);
539 const bool equalsDefault = valuePtrEqualsDefault(value, valuePointer, defaultValuePointer);
540 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
541 settingGetName(value, name);
542 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
543 cliPrintf(defaultFormat, name);
544 // if the craftname has a leading space, then enclose the name in quotes
545 if (strcmp(name, "name") == 0 && ((const char *)valuePointer)[0] == ' ') {
546 cliPrintf("\"%s\"", (const char *)valuePointer);
547 } else {
548 printValuePointer(value, valuePointer, 0);
550 cliPrintLinefeed();
552 cliPrintf(format, name);
553 printValuePointer(value, valuePointer, 0);
554 cliPrintLinefeed();
558 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
560 for (unsigned i = 0; i < SETTINGS_TABLE_COUNT; i++) {
561 const setting_t *value = settingGet(i);
562 bufWriterFlush(cliWriter);
563 if (SETTING_SECTION(value) == valueSection) {
564 dumpPgValue(value, dumpMask);
569 static void cliPrintVar(const setting_t *var, uint32_t full)
571 const void *ptr = settingGetValuePointer(var);
573 printValuePointer(var, ptr, full);
576 static void cliPrintVarRange(const setting_t *var)
578 switch (SETTING_MODE(var)) {
579 case MODE_DIRECT:
580 if (SETTING_TYPE(var) == VAR_STRING) {
581 cliPrintLinef("Max. length: %u", settingGetStringMaxLength(var));
582 break;
584 cliPrintLinef("Allowed range: %d - %u", settingGetMin(var), settingGetMax(var));
585 break;
586 case MODE_LOOKUP:
588 const lookupTableEntry_t *tableEntry = settingLookupTable(var);
589 cliPrint("Allowed values:");
590 for (uint32_t i = 0; i < tableEntry->valueCount ; i++) {
591 if (i > 0)
592 cliPrint(",");
593 cliPrintf(" %s", tableEntry->values[i]);
595 cliPrintLinefeed();
597 break;
601 typedef union {
602 uint32_t uint_value;
603 int32_t int_value;
604 float float_value;
605 } int_float_value_t;
607 static void cliSetIntFloatVar(const setting_t *var, const int_float_value_t value)
609 void *ptr = settingGetValuePointer(var);
611 switch (SETTING_TYPE(var)) {
612 case VAR_UINT8:
613 case VAR_INT8:
614 *(int8_t *)ptr = value.int_value;
615 break;
617 case VAR_UINT16:
618 case VAR_INT16:
619 *(int16_t *)ptr = value.int_value;
620 break;
622 case VAR_UINT32:
623 *(uint32_t *)ptr = value.uint_value;
624 break;
626 case VAR_FLOAT:
627 *(float *)ptr = (float)value.float_value;
628 break;
630 case VAR_STRING:
631 // Handled by cliSet directly
632 break;
636 static void cliPrompt(void)
638 cliPrint("\r\n# ");
639 bufWriterFlush(cliWriter);
642 static void cliShowParseError(void)
644 cliPrintErrorLinef("Parse error");
647 static void cliShowArgumentRangeError(char *name, int min, int max)
649 cliPrintErrorLinef("%s must be between %d and %d", name, min, max);
652 static const char *nextArg(const char *currentArg)
654 const char *ptr = strchr(currentArg, ' ');
655 while (ptr && *ptr == ' ') {
656 ptr++;
659 return ptr;
662 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
664 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
665 ptr = nextArg(ptr);
666 if (ptr) {
667 int val = fastA2I(ptr);
668 val = CHANNEL_VALUE_TO_STEP(val);
669 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
670 if (argIndex == 0) {
671 range->startStep = val;
672 } else {
673 range->endStep = val;
675 (*validArgumentCount)++;
680 return ptr;
683 // Check if a string's length is zero
684 static bool isEmpty(const char *string)
686 return (string == NULL || *string == '\0') ? true : false;
689 #if defined(USE_ASSERT)
690 static void cliAssert(char *cmdline)
692 UNUSED(cmdline);
694 if (assertFailureLine) {
695 if (assertFailureFile) {
696 cliPrintErrorLinef("Assertion failed at line %d, file %s", assertFailureLine, assertFailureFile);
698 else {
699 cliPrintErrorLinef("Assertion failed at line %d", assertFailureLine);
701 #ifdef USE_CLI_BATCH
702 if (commandBatchActive) {
703 commandBatchError = true;
704 commandBatchErrorCount++;
706 #endif
708 else {
709 cliPrintLine("No assert() failed");
712 #endif
714 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
716 const char *format = "aux %u %u %u %u %u";
717 // print out aux channel settings
718 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
719 const modeActivationCondition_t *mac = &modeActivationConditions[i];
720 bool equalsDefault = false;
721 if (defaultModeActivationConditions) {
722 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
723 equalsDefault = mac->modeId == macDefault->modeId
724 && mac->auxChannelIndex == macDefault->auxChannelIndex
725 && mac->range.startStep == macDefault->range.startStep
726 && mac->range.endStep == macDefault->range.endStep;
727 const box_t *box = findBoxByActiveBoxId(macDefault->modeId);
728 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
730 box->permanentId,
731 macDefault->auxChannelIndex,
732 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
733 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep)
736 const box_t *box = findBoxByActiveBoxId(mac->modeId);
737 cliDumpPrintLinef(dumpMask, equalsDefault, format,
739 box->permanentId,
740 mac->auxChannelIndex,
741 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
742 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep)
747 static void cliAux(char *cmdline)
749 int i, val = 0;
750 const char *ptr;
752 if (isEmpty(cmdline)) {
753 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
754 } else {
755 ptr = cmdline;
756 i = fastA2I(ptr++);
757 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
758 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
759 uint8_t validArgumentCount = 0;
760 ptr = nextArg(ptr);
761 if (ptr) {
762 val = fastA2I(ptr);
763 if (val >= 0) {
764 const box_t *box = findBoxByPermanentId(val);
765 if (box != NULL) {
766 mac->modeId = box->boxId;
767 validArgumentCount++;
771 ptr = nextArg(ptr);
772 if (ptr) {
773 val = fastA2I(ptr);
774 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
775 mac->auxChannelIndex = val;
776 validArgumentCount++;
779 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
781 if (validArgumentCount != 4) {
782 memset(mac, 0, sizeof(modeActivationCondition_t));
784 } else {
785 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
790 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
792 const char *format = "serial %d %d %ld %ld %ld %ld";
793 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
794 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
795 continue;
797 bool equalsDefault = false;
798 if (serialConfigDefault) {
799 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
800 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
801 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
802 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
803 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
804 && serialConfig->portConfigs[i].peripheral_baudrateIndex == serialConfigDefault->portConfigs[i].peripheral_baudrateIndex;
805 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
806 serialConfigDefault->portConfigs[i].identifier,
807 serialConfigDefault->portConfigs[i].functionMask,
808 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
809 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
810 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
811 baudRates[serialConfigDefault->portConfigs[i].peripheral_baudrateIndex]
814 cliDumpPrintLinef(dumpMask, equalsDefault, format,
815 serialConfig->portConfigs[i].identifier,
816 serialConfig->portConfigs[i].functionMask,
817 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
818 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
819 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
820 baudRates[serialConfig->portConfigs[i].peripheral_baudrateIndex]
825 static void cliSerial(char *cmdline)
827 if (isEmpty(cmdline)) {
828 printSerial(DUMP_MASTER, serialConfig(), NULL);
829 return;
831 serialPortConfig_t portConfig;
833 serialPortConfig_t *currentConfig;
835 uint8_t validArgumentCount = 0;
837 const char *ptr = cmdline;
839 int val = fastA2I(ptr++);
840 currentConfig = serialFindPortConfiguration(val);
841 if (!currentConfig) {
842 // Invalid port ID
843 cliPrintErrorLinef("Invalid port ID %d", val);
844 return;
846 memcpy(&portConfig, currentConfig, sizeof(portConfig));
847 validArgumentCount++;
849 ptr = nextArg(ptr);
850 if (ptr) {
851 switch (*ptr) {
852 case '+':
853 // Add function
854 ptr++;
855 val = fastA2I(ptr);
856 portConfig.functionMask |= (1 << val);
857 break;
858 case '-':
859 // Remove function
860 ptr++;
861 val = fastA2I(ptr);
862 portConfig.functionMask &= 0xFFFFFFFF ^ (1 << val);
863 break;
864 default:
865 // Set functions
866 val = fastA2I(ptr);
867 portConfig.functionMask = val & 0xFFFFFFFF;
868 break;
870 validArgumentCount++;
873 for (int i = 0; i < 4; i ++) {
874 ptr = nextArg(ptr);
875 if (!ptr) {
876 break;
879 val = fastA2I(ptr);
881 uint8_t baudRateIndex = lookupBaudRateIndex(val);
882 if (baudRates[baudRateIndex] != (uint32_t) val) {
883 break;
886 switch (i) {
887 case 0:
888 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
889 portConfig.msp_baudrateIndex = baudRateIndex;
890 break;
891 case 1:
892 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
893 portConfig.gps_baudrateIndex = baudRateIndex;
894 break;
895 case 2:
896 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
897 portConfig.telemetry_baudrateIndex = baudRateIndex;
898 break;
899 case 3:
900 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
901 portConfig.peripheral_baudrateIndex = baudRateIndex;
902 break;
905 validArgumentCount++;
908 if (validArgumentCount < 2) {
909 cliShowParseError();
910 return;
913 memcpy(currentConfig, &portConfig, sizeof(portConfig));
916 #ifdef USE_SERIAL_PASSTHROUGH
917 static void cliSerialPassthrough(char *cmdline)
919 char * saveptr;
921 if (isEmpty(cmdline)) {
922 cliShowParseError();
923 return;
926 int id = -1;
927 uint32_t baud = 0;
928 unsigned mode = 0;
929 char* tok = strtok_r(cmdline, " ", &saveptr);
930 int index = 0;
932 while (tok != NULL) {
933 switch (index) {
934 case 0:
935 id = fastA2I(tok);
936 break;
937 case 1:
938 baud = fastA2I(tok);
939 break;
940 case 2:
941 if (strstr(tok, "rx") || strstr(tok, "RX"))
942 mode |= MODE_RX;
943 if (strstr(tok, "tx") || strstr(tok, "TX"))
944 mode |= MODE_TX;
945 break;
947 index++;
948 tok = strtok_r(NULL, " ", &saveptr);
951 serialPort_t *passThroughPort;
952 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
953 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
954 if (!baud) {
955 tfp_printf("Port %d is closed, must specify baud.\r\n", id);
956 return;
958 if (!mode)
959 mode = MODE_RXTX;
961 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
962 baud, mode,
963 SERIAL_NOT_INVERTED);
964 if (!passThroughPort) {
965 tfp_printf("Port %d could not be opened.\r\n", id);
966 return;
968 tfp_printf("Port %d opened, baud = %u.\r\n", id, (unsigned)baud);
969 } else {
970 passThroughPort = passThroughPortUsage->serialPort;
971 // If the user supplied a mode, override the port's mode, otherwise
972 // leave the mode unchanged. serialPassthrough() handles one-way ports.
973 tfp_printf("Port %d already open.\r\n", id);
974 if (mode && passThroughPort->mode != mode) {
975 tfp_printf("Adjusting mode from %d to %d.\r\n",
976 passThroughPort->mode, mode);
977 serialSetMode(passThroughPort, mode);
979 // If this port has a rx callback associated we need to remove it now.
980 // Otherwise no data will be pushed in the serial port buffer!
981 if (passThroughPort->rxCallback) {
982 tfp_printf("Removing rxCallback\r\n");
983 passThroughPort->rxCallback = 0;
987 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id);
989 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
991 #endif
993 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
995 const char *format = "adjrange %u %u %u %u %u %u %u";
996 // print out adjustment ranges channel settings
997 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
998 const adjustmentRange_t *ar = &adjustmentRanges[i];
999 bool equalsDefault = false;
1000 if (defaultAdjustmentRanges) {
1001 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1002 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
1003 && ar->range.startStep == arDefault->range.startStep
1004 && ar->range.endStep == arDefault->range.endStep
1005 && ar->adjustmentFunction == arDefault->adjustmentFunction
1006 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
1007 && ar->adjustmentIndex == arDefault->adjustmentIndex;
1008 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1010 arDefault->adjustmentIndex,
1011 arDefault->auxChannelIndex,
1012 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1013 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1014 arDefault->adjustmentFunction,
1015 arDefault->auxSwitchChannelIndex
1018 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1020 ar->adjustmentIndex,
1021 ar->auxChannelIndex,
1022 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1023 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1024 ar->adjustmentFunction,
1025 ar->auxSwitchChannelIndex
1030 static void cliAdjustmentRange(char *cmdline)
1032 int i, val = 0;
1033 const char *ptr;
1035 if (isEmpty(cmdline)) {
1036 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
1037 } else {
1038 ptr = cmdline;
1039 i = fastA2I(ptr++);
1040 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1041 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1042 uint8_t validArgumentCount = 0;
1044 ptr = nextArg(ptr);
1045 if (ptr) {
1046 val = fastA2I(ptr);
1047 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1048 ar->adjustmentIndex = val;
1049 validArgumentCount++;
1052 ptr = nextArg(ptr);
1053 if (ptr) {
1054 val = fastA2I(ptr);
1055 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1056 ar->auxChannelIndex = val;
1057 validArgumentCount++;
1061 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1063 ptr = nextArg(ptr);
1064 if (ptr) {
1065 val = fastA2I(ptr);
1066 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1067 ar->adjustmentFunction = val;
1068 validArgumentCount++;
1071 ptr = nextArg(ptr);
1072 if (ptr) {
1073 val = fastA2I(ptr);
1074 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1075 ar->auxSwitchChannelIndex = val;
1076 validArgumentCount++;
1080 if (validArgumentCount != 6) {
1081 memset(ar, 0, sizeof(adjustmentRange_t));
1082 cliShowParseError();
1084 } else {
1085 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1090 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *primaryMotorMixer, const motorMixer_t *defaultprimaryMotorMixer)
1092 const char *format = "mmix %d %s %s %s %s";
1093 char buf0[FTOA_BUFFER_SIZE];
1094 char buf1[FTOA_BUFFER_SIZE];
1095 char buf2[FTOA_BUFFER_SIZE];
1096 char buf3[FTOA_BUFFER_SIZE];
1097 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1098 if (primaryMotorMixer[i].throttle == 0.0f)
1099 break;
1100 const float thr = primaryMotorMixer[i].throttle;
1101 const float roll = primaryMotorMixer[i].roll;
1102 const float pitch = primaryMotorMixer[i].pitch;
1103 const float yaw = primaryMotorMixer[i].yaw;
1104 bool equalsDefault = false;
1105 if (defaultprimaryMotorMixer) {
1106 const float thrDefault = defaultprimaryMotorMixer[i].throttle;
1107 const float rollDefault = defaultprimaryMotorMixer[i].roll;
1108 const float pitchDefault = defaultprimaryMotorMixer[i].pitch;
1109 const float yawDefault = defaultprimaryMotorMixer[i].yaw;
1110 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1112 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1114 ftoa(thrDefault, buf0),
1115 ftoa(rollDefault, buf1),
1116 ftoa(pitchDefault, buf2),
1117 ftoa(yawDefault, buf3));
1119 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1121 ftoa(thr, buf0),
1122 ftoa(roll, buf1),
1123 ftoa(pitch, buf2),
1124 ftoa(yaw, buf3));
1128 static void cliMotorMix(char *cmdline)
1130 int check = 0;
1131 const char *ptr;
1133 if (isEmpty(cmdline)) {
1134 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1135 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1136 // erase custom mixer
1137 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1138 primaryMotorMixerMutable(i)->throttle = 0.0f;
1140 } else {
1141 ptr = cmdline;
1142 uint32_t i = fastA2I(ptr); // get motor number
1143 if (i < MAX_SUPPORTED_MOTORS) {
1144 ptr = nextArg(ptr);
1145 if (ptr) {
1146 primaryMotorMixerMutable(i)->throttle = fastA2F(ptr);
1147 check++;
1149 ptr = nextArg(ptr);
1150 if (ptr) {
1151 primaryMotorMixerMutable(i)->roll = fastA2F(ptr);
1152 check++;
1154 ptr = nextArg(ptr);
1155 if (ptr) {
1156 primaryMotorMixerMutable(i)->pitch = fastA2F(ptr);
1157 check++;
1159 ptr = nextArg(ptr);
1160 if (ptr) {
1161 primaryMotorMixerMutable(i)->yaw = fastA2F(ptr);
1162 check++;
1164 if (check != 4) {
1165 cliShowParseError();
1166 } else {
1167 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1169 } else {
1170 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1175 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1177 const char *format = "rxrange %u %u %u";
1178 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1179 bool equalsDefault = false;
1180 if (defaultChannelRangeConfigs) {
1181 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1182 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1183 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1185 defaultChannelRangeConfigs[i].min,
1186 defaultChannelRangeConfigs[i].max
1189 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1191 channelRangeConfigs[i].min,
1192 channelRangeConfigs[i].max
1197 static void cliRxRange(char *cmdline)
1199 int i, validArgumentCount = 0;
1200 const char *ptr;
1202 if (isEmpty(cmdline)) {
1203 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1204 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1205 resetAllRxChannelRangeConfigurations();
1206 } else {
1207 ptr = cmdline;
1208 i = fastA2I(ptr);
1209 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1210 int rangeMin = 0, rangeMax = 0;
1212 ptr = nextArg(ptr);
1213 if (ptr) {
1214 rangeMin = fastA2I(ptr);
1215 validArgumentCount++;
1218 ptr = nextArg(ptr);
1219 if (ptr) {
1220 rangeMax = fastA2I(ptr);
1221 validArgumentCount++;
1224 if (validArgumentCount != 2) {
1225 cliShowParseError();
1226 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1227 cliShowParseError();
1228 } else {
1229 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1230 channelRangeConfig->min = rangeMin;
1231 channelRangeConfig->max = rangeMax;
1233 } else {
1234 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1239 #ifdef USE_TEMPERATURE_SENSOR
1240 static void printTempSensor(uint8_t dumpMask, const tempSensorConfig_t *tempSensorConfigs, const tempSensorConfig_t *defaultTempSensorConfigs)
1242 const char *format = "temp_sensor %u %u %s %d %d %u %s";
1243 for (uint8_t i = 0; i < MAX_TEMP_SENSORS; i++) {
1244 bool equalsDefault = false;
1245 char label[5], hex_address[17];
1246 strncpy(label, tempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN);
1247 label[4] = '\0';
1248 tempSensorAddressToString(tempSensorConfigs[i].address, hex_address);
1249 if (defaultTempSensorConfigs) {
1250 equalsDefault = tempSensorConfigs[i].type == defaultTempSensorConfigs[i].type
1251 && tempSensorConfigs[i].address == defaultTempSensorConfigs[i].address
1252 && tempSensorConfigs[i].osdSymbol == defaultTempSensorConfigs[i].osdSymbol
1253 && !memcmp(tempSensorConfigs[i].label, defaultTempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN)
1254 && tempSensorConfigs[i].alarm_min == defaultTempSensorConfigs[i].alarm_min
1255 && tempSensorConfigs[i].alarm_max == defaultTempSensorConfigs[i].alarm_max;
1256 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1258 defaultTempSensorConfigs[i].type,
1259 "0",
1260 defaultTempSensorConfigs[i].alarm_min,
1261 defaultTempSensorConfigs[i].alarm_max,
1266 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1268 tempSensorConfigs[i].type,
1269 hex_address,
1270 tempSensorConfigs[i].alarm_min,
1271 tempSensorConfigs[i].alarm_max,
1272 tempSensorConfigs[i].osdSymbol,
1273 label
1278 static void cliTempSensor(char *cmdline)
1280 if (isEmpty(cmdline)) {
1281 printTempSensor(DUMP_MASTER, tempSensorConfig(0), NULL);
1282 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1283 resetTempSensorConfig();
1284 } else {
1285 int16_t i;
1286 const char *ptr = cmdline, *label;
1287 int16_t type=0, alarm_min=0, alarm_max=0;
1288 bool addressValid = false;
1289 uint64_t address;
1290 int8_t osdSymbol=0;
1291 uint8_t validArgumentCount = 0;
1292 i = fastA2I(ptr);
1293 if (i >= 0 && i < MAX_TEMP_SENSORS) {
1295 ptr = nextArg(ptr);
1296 if (ptr) {
1297 type = fastA2I(ptr);
1298 validArgumentCount++;
1301 ptr = nextArg(ptr);
1302 if (ptr) {
1303 addressValid = tempSensorStringToAddress(ptr, &address);
1304 validArgumentCount++;
1307 ptr = nextArg(ptr);
1308 if (ptr) {
1309 alarm_min = fastA2I(ptr);
1310 validArgumentCount++;
1313 ptr = nextArg(ptr);
1314 if (ptr) {
1315 alarm_max = fastA2I(ptr);
1316 validArgumentCount++;
1319 ptr = nextArg(ptr);
1320 if (ptr) {
1321 osdSymbol = fastA2I(ptr);
1322 validArgumentCount++;
1325 label = nextArg(ptr);
1326 if (label)
1327 ++validArgumentCount;
1328 else
1329 label = "";
1331 if (validArgumentCount < 4) {
1332 cliShowParseError();
1333 } 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) {
1334 cliShowParseError();
1335 } else {
1336 tempSensorConfig_t *sensorConfig = tempSensorConfigMutable(i);
1337 sensorConfig->type = type;
1338 sensorConfig->address = address;
1339 sensorConfig->alarm_min = alarm_min;
1340 sensorConfig->alarm_max = alarm_max;
1341 sensorConfig->osdSymbol = osdSymbol;
1342 for (uint8_t index = 0; index < TEMPERATURE_LABEL_LEN; ++index) {
1343 sensorConfig->label[index] = toupper(label[index]);
1344 if (label[index] == '\0') break;
1347 } else {
1348 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS - 1);
1352 #endif
1354 #ifdef USE_FW_AUTOLAND
1355 static void printFwAutolandApproach(uint8_t dumpMask, const navFwAutolandApproach_t *navFwAutolandApproach, const navFwAutolandApproach_t *defaultFwAutolandApproach)
1357 const char *format = "fwapproach %u %d %d %u %d %d %u";
1358 for (uint8_t i = 0; i < MAX_FW_LAND_APPOACH_SETTINGS; i++) {
1359 bool equalsDefault = false;
1360 if (defaultFwAutolandApproach) {
1361 equalsDefault = navFwAutolandApproach[i].approachDirection == defaultFwAutolandApproach[i].approachDirection
1362 && navFwAutolandApproach[i].approachAlt == defaultFwAutolandApproach[i].approachAlt
1363 && navFwAutolandApproach[i].landAlt == defaultFwAutolandApproach[i].landAlt
1364 && navFwAutolandApproach[i].landApproachHeading1 == defaultFwAutolandApproach[i].landApproachHeading1
1365 && navFwAutolandApproach[i].landApproachHeading2 == defaultFwAutolandApproach[i].landApproachHeading2
1366 && navFwAutolandApproach[i].isSeaLevelRef == defaultFwAutolandApproach[i].isSeaLevelRef;
1367 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,
1368 defaultFwAutolandApproach[i].approachAlt, defaultFwAutolandApproach[i].landAlt, defaultFwAutolandApproach[i].approachDirection, defaultFwAutolandApproach[i].landApproachHeading1, defaultFwAutolandApproach[i].landApproachHeading2, defaultFwAutolandApproach[i].isSeaLevelRef);
1370 cliDumpPrintLinef(dumpMask, equalsDefault, format, i,
1371 navFwAutolandApproach[i].approachAlt, navFwAutolandApproach[i].landAlt, navFwAutolandApproach[i].approachDirection, navFwAutolandApproach[i].landApproachHeading1, navFwAutolandApproach[i].landApproachHeading2, navFwAutolandApproach[i].isSeaLevelRef);
1375 static void cliFwAutolandApproach(char * cmdline)
1377 if (isEmpty(cmdline)) {
1378 printFwAutolandApproach(DUMP_MASTER, fwAutolandApproachConfig(0), NULL);
1379 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1380 resetFwAutolandApproach(-1);
1381 } else {
1382 int32_t approachAlt = 0, heading1 = 0, heading2 = 0, landDirection = 0, landAlt = 0;
1383 bool isSeaLevelRef = false;
1384 uint8_t validArgumentCount = 0;
1385 const char *ptr = cmdline;
1386 int8_t i = fastA2I(ptr);
1387 if (i < 0 || i >= MAX_FW_LAND_APPOACH_SETTINGS) {
1388 cliShowArgumentRangeError("fwapproach index", 0, MAX_FW_LAND_APPOACH_SETTINGS - 1);
1389 } else {
1390 if ((ptr = nextArg(ptr))) {
1391 approachAlt = fastA2I(ptr);
1392 validArgumentCount++;
1395 if ((ptr = nextArg(ptr))) {
1396 landAlt = fastA2I(ptr);
1397 validArgumentCount++;
1400 if ((ptr = nextArg(ptr))) {
1401 landDirection = fastA2I(ptr);
1403 if (landDirection != 0 && landDirection != 1) {
1404 cliShowParseError();
1405 return;
1408 validArgumentCount++;
1411 if ((ptr = nextArg(ptr))) {
1412 heading1 = fastA2I(ptr);
1414 if (heading1 < -360 || heading1 > 360) {
1415 cliShowParseError();
1416 return;
1419 validArgumentCount++;
1422 if ((ptr = nextArg(ptr))) {
1423 heading2 = fastA2I(ptr);
1425 if (heading2 < -360 || heading2 > 360) {
1426 cliShowParseError();
1427 return;
1430 validArgumentCount++;
1433 if ((ptr = nextArg(ptr))) {
1434 isSeaLevelRef = fastA2I(ptr);
1435 validArgumentCount++;
1438 if ((ptr = nextArg(ptr))) {
1439 // check for too many arguments
1440 validArgumentCount++;
1443 if (validArgumentCount != 6) {
1444 cliShowParseError();
1445 } else {
1446 fwAutolandApproachConfigMutable(i)->approachAlt = approachAlt;
1447 fwAutolandApproachConfigMutable(i)->landAlt = landAlt;
1448 fwAutolandApproachConfigMutable(i)->approachDirection = (fwAutolandApproachDirection_e)landDirection;
1449 fwAutolandApproachConfigMutable(i)->landApproachHeading1 = (int16_t)heading1;
1450 fwAutolandApproachConfigMutable(i)->landApproachHeading2 = (int16_t)heading2;
1451 fwAutolandApproachConfigMutable(i)->isSeaLevelRef = isSeaLevelRef;
1456 #endif
1458 #if defined(USE_SAFE_HOME)
1459 static void printSafeHomes(uint8_t dumpMask, const navSafeHome_t *navSafeHome, const navSafeHome_t *defaultSafeHome)
1461 const char *format = "safehome %u %u %d %d"; // uint8_t enabled, int32_t lat; int32_t lon
1462 for (uint8_t i = 0; i < MAX_SAFE_HOMES; i++) {
1463 bool equalsDefault = false;
1464 if (defaultSafeHome) {
1465 equalsDefault = navSafeHome[i].enabled == defaultSafeHome[i].enabled
1466 && navSafeHome[i].lat == defaultSafeHome[i].lat
1467 && navSafeHome[i].lon == defaultSafeHome[i].lon;
1468 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,
1469 defaultSafeHome[i].enabled, defaultSafeHome[i].lat, defaultSafeHome[i].lon);
1471 cliDumpPrintLinef(dumpMask, equalsDefault, format, i,
1472 navSafeHome[i].enabled, navSafeHome[i].lat, navSafeHome[i].lon);
1476 static void cliSafeHomes(char *cmdline)
1478 if (isEmpty(cmdline)) {
1479 printSafeHomes(DUMP_MASTER, safeHomeConfig(0), NULL);
1480 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1481 resetSafeHomes();
1482 } else {
1483 int32_t lat=0, lon=0;
1484 bool enabled=false;
1485 uint8_t validArgumentCount = 0;
1486 const char *ptr = cmdline;
1487 int8_t i = fastA2I(ptr);
1488 if (i < 0 || i >= MAX_SAFE_HOMES) {
1489 cliShowArgumentRangeError("safehome index", 0, MAX_SAFE_HOMES - 1);
1490 } else {
1491 if ((ptr = nextArg(ptr))) {
1492 enabled = fastA2I(ptr);
1493 validArgumentCount++;
1495 if ((ptr = nextArg(ptr))) {
1496 lat = fastA2I(ptr);
1497 validArgumentCount++;
1499 if ((ptr = nextArg(ptr))) {
1500 lon = fastA2I(ptr);
1501 validArgumentCount++;
1503 if ((ptr = nextArg(ptr))) {
1504 // check for too many arguments
1505 validArgumentCount++;
1507 if (validArgumentCount != 3) {
1508 cliShowParseError();
1509 } else {
1510 safeHomeConfigMutable(i)->enabled = enabled;
1511 safeHomeConfigMutable(i)->lat = lat;
1512 safeHomeConfigMutable(i)->lon = lon;
1518 #endif
1519 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1520 static void printWaypoints(uint8_t dumpMask, const navWaypoint_t *navWaypoint, const navWaypoint_t *defaultNavWaypoint)
1522 cliPrintLinef("#wp %d %svalid", posControl.waypointCount, posControl.waypointListValid ? "" : "in"); //int8_t bool
1523 const char *format = "wp %u %u %d %d %d %d %d %d %u"; //uint8_t action; int32_t lat; int32_t lon; int32_t alt; int16_t p1 int16_t p2 int16_t p3; uint8_t flag
1524 for (uint8_t i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1525 bool equalsDefault = false;
1526 if (defaultNavWaypoint) {
1527 equalsDefault = navWaypoint[i].action == defaultNavWaypoint[i].action
1528 && navWaypoint[i].lat == defaultNavWaypoint[i].lat
1529 && navWaypoint[i].lon == defaultNavWaypoint[i].lon
1530 && navWaypoint[i].alt == defaultNavWaypoint[i].alt
1531 && navWaypoint[i].p1 == defaultNavWaypoint[i].p1
1532 && navWaypoint[i].p2 == defaultNavWaypoint[i].p2
1533 && navWaypoint[i].p3 == defaultNavWaypoint[i].p3
1534 && navWaypoint[i].flag == defaultNavWaypoint[i].flag;
1535 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1537 defaultNavWaypoint[i].action,
1538 defaultNavWaypoint[i].lat,
1539 defaultNavWaypoint[i].lon,
1540 defaultNavWaypoint[i].alt,
1541 defaultNavWaypoint[i].p1,
1542 defaultNavWaypoint[i].p2,
1543 defaultNavWaypoint[i].p3,
1544 defaultNavWaypoint[i].flag
1547 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1549 navWaypoint[i].action,
1550 navWaypoint[i].lat,
1551 navWaypoint[i].lon,
1552 navWaypoint[i].alt,
1553 navWaypoint[i].p1,
1554 navWaypoint[i].p2,
1555 navWaypoint[i].p3,
1556 navWaypoint[i].flag
1561 static void cliWaypoints(char *cmdline)
1563 #ifdef USE_MULTI_MISSION
1564 static int8_t multiMissionWPCounter = 0;
1565 #endif
1566 if (isEmpty(cmdline)) {
1567 printWaypoints(DUMP_MASTER, posControl.waypointList, NULL);
1568 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1569 resetWaypointList();
1570 } else if (sl_strcasecmp(cmdline, "load") == 0) {
1571 loadNonVolatileWaypointList(true);
1572 } else if (sl_strcasecmp(cmdline, "save") == 0) {
1573 posControl.waypointListValid = false;
1574 for (int i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1575 if (!(posControl.waypointList[i].action == NAV_WP_ACTION_WAYPOINT || posControl.waypointList[i].action == NAV_WP_ACTION_JUMP || posControl.waypointList[i].action == NAV_WP_ACTION_RTH || posControl.waypointList[i].action == NAV_WP_ACTION_HOLD_TIME || posControl.waypointList[i].action == NAV_WP_ACTION_LAND || posControl.waypointList[i].action == NAV_WP_ACTION_SET_POI || posControl.waypointList[i].action == NAV_WP_ACTION_SET_HEAD)) break;
1576 if (posControl.waypointList[i].flag == NAV_WP_FLAG_LAST) {
1577 #ifdef USE_MULTI_MISSION
1578 if (posControl.multiMissionCount == 1) {
1579 posControl.waypointCount = i + 1;
1580 posControl.waypointListValid = true;
1581 multiMissionWPCounter = 0;
1582 posControl.multiMissionCount = 0;
1583 break;
1584 } else {
1585 posControl.multiMissionCount -= 1;
1587 #else
1588 posControl.waypointCount = i + 1;
1589 posControl.waypointListValid = true;
1590 break;
1591 #endif
1594 if (posControl.waypointListValid) {
1595 saveNonVolatileWaypointList();
1596 } else {
1597 cliShowParseError();
1599 } else {
1600 int16_t i, p1=0,p2=0,p3=0,tmp=0;
1601 uint8_t action=0, flag=0;
1602 int32_t lat=0, lon=0, alt=0;
1603 uint8_t validArgumentCount = 0;
1604 const char *ptr = cmdline;
1605 i = fastA2I(ptr);
1606 #ifdef USE_MULTI_MISSION
1607 if (i + multiMissionWPCounter >= 0 && i + multiMissionWPCounter < NAV_MAX_WAYPOINTS) {
1608 #else
1609 if (i >= 0 && i < NAV_MAX_WAYPOINTS) {
1610 #endif
1611 ptr = nextArg(ptr);
1612 if (ptr) {
1613 action = fastA2I(ptr);
1614 validArgumentCount++;
1616 ptr = nextArg(ptr);
1617 if (ptr) {
1618 lat = fastA2I(ptr);
1619 validArgumentCount++;
1621 ptr = nextArg(ptr);
1622 if (ptr) {
1623 lon = fastA2I(ptr);
1624 validArgumentCount++;
1626 ptr = nextArg(ptr);
1627 if (ptr) {
1628 alt = fastA2I(ptr);
1629 validArgumentCount++;
1631 ptr = nextArg(ptr);
1632 if (ptr) {
1633 p1 = fastA2I(ptr);
1634 validArgumentCount++;
1636 ptr = nextArg(ptr);
1637 if (ptr) {
1638 tmp = fastA2I(ptr);
1639 validArgumentCount++;
1641 /* We support pre-2.5 6 values (... p1,flags) or
1642 * 2.5 and later, 8 values (... p1,p2,p3,flags)
1644 ptr = nextArg(ptr);
1645 if (ptr) {
1646 p2 = tmp;
1647 p3 = fastA2I(ptr);
1648 validArgumentCount++;
1649 ptr = nextArg(ptr);
1650 if (ptr) {
1651 flag = fastA2I(ptr);
1652 validArgumentCount++;
1654 } else {
1655 flag = tmp;
1658 if (!(validArgumentCount == 6 || validArgumentCount == 8)) {
1659 cliShowParseError();
1660 } else if (!(action == 0 || action == NAV_WP_ACTION_WAYPOINT || action == NAV_WP_ACTION_RTH || action == NAV_WP_ACTION_JUMP || action == NAV_WP_ACTION_HOLD_TIME || action == NAV_WP_ACTION_LAND || action == NAV_WP_ACTION_SET_POI || action == NAV_WP_ACTION_SET_HEAD) || !(flag == 0 || flag == NAV_WP_FLAG_LAST || flag == NAV_WP_FLAG_HOME)) {
1661 cliShowParseError();
1662 } else {
1663 #ifdef USE_MULTI_MISSION
1664 if (i + multiMissionWPCounter == 0) {
1665 posControl.multiMissionCount = 0;
1668 posControl.waypointList[i + multiMissionWPCounter].action = action;
1669 posControl.waypointList[i + multiMissionWPCounter].lat = lat;
1670 posControl.waypointList[i + multiMissionWPCounter].lon = lon;
1671 posControl.waypointList[i + multiMissionWPCounter].alt = alt;
1672 posControl.waypointList[i + multiMissionWPCounter].p1 = p1;
1673 posControl.waypointList[i + multiMissionWPCounter].p2 = p2;
1674 posControl.waypointList[i + multiMissionWPCounter].p3 = p3;
1675 posControl.waypointList[i + multiMissionWPCounter].flag = flag;
1677 // Process WP entries made up of multiple successive WP missions (multiple NAV_WP_FLAG_LAST entries)
1678 // Individial missions loaded at runtime, mission selected nav_waypoint_multi_mission_index
1679 if (flag == NAV_WP_FLAG_LAST) {
1680 multiMissionWPCounter += i + 1;
1681 posControl.multiMissionCount += 1;
1683 #else
1684 posControl.waypointList[i].action = action;
1685 posControl.waypointList[i].lat = lat;
1686 posControl.waypointList[i].lon = lon;
1687 posControl.waypointList[i].alt = alt;
1688 posControl.waypointList[i].p1 = p1;
1689 posControl.waypointList[i].p2 = p2;
1690 posControl.waypointList[i].p3 = p3;
1691 posControl.waypointList[i].flag = flag;
1692 #endif
1694 } else {
1695 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS - 1);
1700 #endif
1702 #ifdef USE_LED_STRIP
1703 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1705 const char *format = "led %u %s";
1706 char ledConfigBuffer[20];
1707 char ledConfigDefaultBuffer[20];
1708 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1709 ledConfig_t ledConfig = ledConfigs[i];
1710 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1711 bool equalsDefault = false;
1712 if (defaultLedConfigs) {
1713 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1714 equalsDefault = !memcmp(&ledConfig, &ledConfigDefault, sizeof(ledConfig_t));
1715 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1716 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1718 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1722 static void cliLed(char *cmdline)
1724 int i;
1725 const char *ptr;
1727 if (isEmpty(cmdline)) {
1728 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1729 } else {
1730 ptr = cmdline;
1731 i = fastA2I(ptr);
1732 if (i < LED_MAX_STRIP_LENGTH) {
1733 ptr = nextArg(cmdline);
1734 if (!parseLedStripConfig(i, ptr)) {
1735 cliShowParseError();
1737 } else {
1738 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1743 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1745 const char *format = "color %u %d,%u,%u";
1746 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1747 const hsvColor_t *color = &colors[i];
1748 bool equalsDefault = false;
1749 if (defaultColors) {
1750 const hsvColor_t *colorDefault = &defaultColors[i];
1751 equalsDefault = color->h == colorDefault->h
1752 && color->s == colorDefault->s
1753 && color->v == colorDefault->v;
1754 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1756 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1760 static void cliColor(char *cmdline)
1762 if (isEmpty(cmdline)) {
1763 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1764 } else {
1765 const char *ptr = cmdline;
1766 const int i = fastA2I(ptr);
1767 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1768 ptr = nextArg(cmdline);
1769 if (!parseColor(i, ptr)) {
1770 cliShowParseError();
1772 } else {
1773 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1778 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1780 const char *format = "mode_color %u %u %u";
1781 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1782 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1783 int colorIndex = ledStripConfig->modeColors[i].color[j];
1784 bool equalsDefault = false;
1785 if (defaultLedStripConfig) {
1786 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1787 equalsDefault = colorIndex == colorIndexDefault;
1788 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1790 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1794 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1795 const int colorIndex = ledStripConfig->specialColors.color[j];
1796 bool equalsDefault = false;
1797 if (defaultLedStripConfig) {
1798 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1799 equalsDefault = colorIndex == colorIndexDefault;
1800 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1802 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1806 static void cliModeColor(char *cmdline)
1808 char * saveptr;
1810 if (isEmpty(cmdline)) {
1811 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1812 } else {
1813 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1814 int args[ARGS_COUNT];
1815 int argNo = 0;
1816 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1817 while (ptr && argNo < ARGS_COUNT) {
1818 args[argNo++] = fastA2I(ptr);
1819 ptr = strtok_r(NULL, " ", &saveptr);
1822 if (ptr != NULL || argNo != ARGS_COUNT) {
1823 cliShowParseError();
1824 return;
1827 int modeIdx = args[MODE];
1828 int funIdx = args[FUNCTION];
1829 int color = args[COLOR];
1830 if (!setModeColor(modeIdx, funIdx, color)) {
1831 cliShowParseError();
1832 return;
1834 // values are validated
1835 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1839 static void cliLedPinPWM(char *cmdline)
1841 int i;
1843 if (isEmpty(cmdline)) {
1844 ledPinStopPWM();
1845 cliPrintLine("PWM stopped");
1846 } else {
1847 i = fastA2I(cmdline);
1848 ledPinStartPWM(i);
1849 cliPrintLinef("PWM started: %d%%",i);
1852 #endif
1854 static void cliDelay(char* cmdLine) {
1855 int ms = 0;
1856 if (isEmpty(cmdLine)) {
1857 cliDelayMs = 0;
1858 cliPrintLine("CLI delay deactivated");
1859 return;
1862 ms = fastA2I(cmdLine);
1863 if (ms) {
1864 cliDelayMs = ms;
1865 cliPrintLinef("CLI delay set to %d ms", ms);
1867 } else {
1868 cliShowParseError();
1873 static void printServo(uint8_t dumpMask, const servoParam_t *servoParam, const servoParam_t *defaultServoParam)
1875 // print out servo settings
1876 const char *format = "servo %u %d %d %d %d";
1877 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1878 const servoParam_t *servoConf = &servoParam[i];
1879 bool equalsDefault = false;
1880 if (defaultServoParam) {
1881 const servoParam_t *servoConfDefault = &defaultServoParam[i];
1882 equalsDefault = servoConf->min == servoConfDefault->min
1883 && servoConf->max == servoConfDefault->max
1884 && servoConf->middle == servoConfDefault->middle
1885 && servoConf->rate == servoConfDefault->rate;
1886 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1888 servoConfDefault->min,
1889 servoConfDefault->max,
1890 servoConfDefault->middle,
1891 servoConfDefault->rate
1894 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1896 servoConf->min,
1897 servoConf->max,
1898 servoConf->middle,
1899 servoConf->rate
1904 static void cliServo(char *cmdline)
1906 enum { SERVO_ARGUMENT_COUNT = 5 };
1907 int16_t arguments[SERVO_ARGUMENT_COUNT];
1909 servoParam_t *servo;
1911 int i;
1912 const char *ptr;
1914 if (isEmpty(cmdline)) {
1915 printServo(DUMP_MASTER, servoParams(0), NULL);
1916 } else {
1917 int validArgumentCount = 0;
1919 ptr = cmdline;
1921 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1923 // If command line doesn't fit the format, don't modify the config
1924 while (*ptr) {
1925 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1926 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1927 cliShowParseError();
1928 return;
1931 arguments[validArgumentCount++] = fastA2I(ptr);
1933 do {
1934 ptr++;
1935 } while (*ptr >= '0' && *ptr <= '9');
1936 } else if (*ptr == ' ') {
1937 ptr++;
1938 } else {
1939 cliShowParseError();
1940 return;
1944 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE};
1946 i = arguments[INDEX];
1948 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1949 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1950 cliShowParseError();
1951 return;
1954 servo = servoParamsMutable(i);
1956 if (
1957 arguments[MIN] < SERVO_OUTPUT_MIN || arguments[MIN] > SERVO_OUTPUT_MAX ||
1958 arguments[MAX] < SERVO_OUTPUT_MIN || arguments[MAX] > SERVO_OUTPUT_MAX ||
1959 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1960 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1961 arguments[RATE] < -125 || arguments[RATE] > 125
1963 cliShowParseError();
1964 return;
1967 servo->min = arguments[MIN];
1968 servo->max = arguments[MAX];
1969 servo->middle = arguments[MIDDLE];
1970 servo->rate = arguments[RATE];
1974 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1976 const char *format = "smix %d %d %d %d %d %d";
1977 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1978 const servoMixer_t customServoMixer = customServoMixers[i];
1979 if (customServoMixer.rate == 0) {
1980 break;
1983 bool equalsDefault = false;
1984 if (defaultCustomServoMixers) {
1985 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1986 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1987 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1988 && customServoMixer.rate == customServoMixerDefault.rate
1989 && customServoMixer.speed == customServoMixerDefault.speed
1990 #ifdef USE_PROGRAMMING_FRAMEWORK
1991 && customServoMixer.conditionId == customServoMixerDefault.conditionId
1992 #endif
1995 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1997 customServoMixerDefault.targetChannel,
1998 customServoMixerDefault.inputSource,
1999 customServoMixerDefault.rate,
2000 customServoMixerDefault.speed,
2001 #ifdef USE_PROGRAMMING_FRAMEWORK
2002 customServoMixer.conditionId
2003 #else
2005 #endif
2008 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2010 customServoMixer.targetChannel,
2011 customServoMixer.inputSource,
2012 customServoMixer.rate,
2013 customServoMixer.speed,
2014 #ifdef USE_PROGRAMMING_FRAMEWORK
2015 customServoMixer.conditionId
2016 #else
2018 #endif
2023 static void cliServoMix(char *cmdline)
2025 char * saveptr;
2026 int args[6], check = 0;
2027 uint8_t len = strlen(cmdline);
2029 if (len == 0) {
2030 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
2031 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2032 // erase custom mixer
2033 Reset_servoMixers(customServoMixersMutable(0));
2034 } else {
2035 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, CONDITION, ARGS_COUNT};
2036 char *ptr = strtok_r(cmdline, " ", &saveptr);
2037 args[CONDITION] = -1;
2038 while (ptr != NULL && check < ARGS_COUNT) {
2039 args[check++] = fastA2I(ptr);
2040 ptr = strtok_r(NULL, " ", &saveptr);
2043 if (ptr != NULL || (check < ARGS_COUNT - 1)) {
2044 cliShowParseError();
2045 return;
2048 int32_t i = args[RULE];
2049 if (
2050 i >= 0 && i < MAX_SERVO_RULES &&
2051 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2052 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2053 args[RATE] >= -1000 && args[RATE] <= 1000 &&
2054 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2055 args[CONDITION] >= -1 && args[CONDITION] < MAX_LOGIC_CONDITIONS
2057 customServoMixersMutable(i)->targetChannel = args[TARGET];
2058 customServoMixersMutable(i)->inputSource = args[INPUT];
2059 customServoMixersMutable(i)->rate = args[RATE];
2060 customServoMixersMutable(i)->speed = args[SPEED];
2061 #ifdef USE_PROGRAMMING_FRAMEWORK
2062 customServoMixersMutable(i)->conditionId = args[CONDITION];
2063 #endif
2064 cliServoMix("");
2065 } else {
2066 cliShowParseError();
2071 #ifdef USE_PROGRAMMING_FRAMEWORK
2073 static void printLogic(uint8_t dumpMask, const logicCondition_t *logicConditions, const logicCondition_t *defaultLogicConditions, int16_t showLC)
2075 const char *format = "logic %d %d %d %d %d %d %d %d %d";
2076 for (uint8_t i = 0; i < MAX_LOGIC_CONDITIONS; i++) {
2077 if (showLC == -1 || showLC == i) {
2078 const logicCondition_t logic = logicConditions[i];
2080 bool equalsDefault = false;
2081 if (defaultLogicConditions) {
2082 logicCondition_t defaultValue = defaultLogicConditions[i];
2083 equalsDefault =
2084 logic.enabled == defaultValue.enabled &&
2085 logic.activatorId == defaultValue.activatorId &&
2086 logic.operation == defaultValue.operation &&
2087 logic.operandA.type == defaultValue.operandA.type &&
2088 logic.operandA.value == defaultValue.operandA.value &&
2089 logic.operandB.type == defaultValue.operandB.type &&
2090 logic.operandB.value == defaultValue.operandB.value &&
2091 logic.flags == defaultValue.flags;
2093 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2095 logic.enabled,
2096 logic.activatorId,
2097 logic.operation,
2098 logic.operandA.type,
2099 logic.operandA.value,
2100 logic.operandB.type,
2101 logic.operandB.value,
2102 logic.flags
2105 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2107 logic.enabled,
2108 logic.activatorId,
2109 logic.operation,
2110 logic.operandA.type,
2111 logic.operandA.value,
2112 logic.operandB.type,
2113 logic.operandB.value,
2114 logic.flags
2120 static void processCliLogic(char *cmdline, int16_t lcIndex) {
2121 char * saveptr;
2122 int args[9], check = 0;
2123 uint8_t len = strlen(cmdline);
2125 if (len == 0) {
2126 if (!commandBatchActive) {
2127 printLogic(DUMP_MASTER, logicConditions(0), NULL, -1);
2128 } else if (lcIndex >= 0) {
2129 printLogic(DUMP_MASTER, logicConditions(0), NULL, lcIndex);
2131 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2132 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS);
2133 } else {
2134 enum {
2135 INDEX = 0,
2136 ENABLED,
2137 ACTIVATOR_ID,
2138 OPERATION,
2139 OPERAND_A_TYPE,
2140 OPERAND_A_VALUE,
2141 OPERAND_B_TYPE,
2142 OPERAND_B_VALUE,
2143 FLAGS,
2144 ARGS_COUNT
2146 char *ptr = strtok_r(cmdline, " ", &saveptr);
2147 while (ptr != NULL && check < ARGS_COUNT) {
2148 args[check++] = fastA2I(ptr);
2149 ptr = strtok_r(NULL, " ", &saveptr);
2152 if (ptr != NULL || check != ARGS_COUNT) {
2153 cliShowParseError();
2154 return;
2157 int32_t i = args[INDEX];
2158 if (
2159 i >= 0 && i < MAX_LOGIC_CONDITIONS &&
2160 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
2161 args[ACTIVATOR_ID] >= -1 && args[ACTIVATOR_ID] < MAX_LOGIC_CONDITIONS &&
2162 args[OPERATION] >= 0 && args[OPERATION] < LOGIC_CONDITION_LAST &&
2163 args[OPERAND_A_TYPE] >= 0 && args[OPERAND_A_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2164 args[OPERAND_A_VALUE] >= -1000000 && args[OPERAND_A_VALUE] <= 1000000 &&
2165 args[OPERAND_B_TYPE] >= 0 && args[OPERAND_B_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2166 args[OPERAND_B_VALUE] >= -1000000 && args[OPERAND_B_VALUE] <= 1000000 &&
2167 args[FLAGS] >= 0 && args[FLAGS] <= 255
2170 logicConditionsMutable(i)->enabled = args[ENABLED];
2171 logicConditionsMutable(i)->activatorId = args[ACTIVATOR_ID];
2172 logicConditionsMutable(i)->operation = args[OPERATION];
2173 logicConditionsMutable(i)->operandA.type = args[OPERAND_A_TYPE];
2174 logicConditionsMutable(i)->operandA.value = args[OPERAND_A_VALUE];
2175 logicConditionsMutable(i)->operandB.type = args[OPERAND_B_TYPE];
2176 logicConditionsMutable(i)->operandB.value = args[OPERAND_B_VALUE];
2177 logicConditionsMutable(i)->flags = args[FLAGS];
2179 processCliLogic("", i);
2180 } else {
2181 cliShowParseError();
2186 static void cliLogic(char *cmdline) {
2187 processCliLogic(cmdline, -1);
2190 static void printGvar(uint8_t dumpMask, const globalVariableConfig_t *gvars, const globalVariableConfig_t *defaultGvars)
2192 const char *format = "gvar %d %d %d %d";
2193 for (uint32_t i = 0; i < MAX_GLOBAL_VARIABLES; i++) {
2194 const globalVariableConfig_t gvar = gvars[i];
2196 bool equalsDefault = false;
2197 if (defaultGvars) {
2198 globalVariableConfig_t defaultValue = defaultGvars[i];
2199 equalsDefault =
2200 gvar.defaultValue == defaultValue.defaultValue &&
2201 gvar.min == defaultValue.min &&
2202 gvar.max == defaultValue.max;
2204 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2206 gvar.defaultValue,
2207 gvar.min,
2208 gvar.max
2211 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2213 gvar.defaultValue,
2214 gvar.min,
2215 gvar.max
2220 static void cliGvar(char *cmdline) {
2221 char * saveptr;
2222 int args[4], check = 0;
2223 uint8_t len = strlen(cmdline);
2225 if (len == 0) {
2226 printGvar(DUMP_MASTER, globalVariableConfigs(0), NULL);
2227 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2228 pgResetCopy(globalVariableConfigsMutable(0), PG_GLOBAL_VARIABLE_CONFIG);
2229 } else {
2230 enum {
2231 INDEX = 0,
2232 DEFAULT,
2233 MIN,
2234 MAX,
2235 ARGS_COUNT
2237 char *ptr = strtok_r(cmdline, " ", &saveptr);
2238 while (ptr != NULL && check < ARGS_COUNT) {
2239 args[check++] = fastA2I(ptr);
2240 ptr = strtok_r(NULL, " ", &saveptr);
2243 if (ptr != NULL || check != ARGS_COUNT) {
2244 cliShowParseError();
2245 return;
2248 int32_t i = args[INDEX];
2249 if (
2250 i >= 0 && i < MAX_GLOBAL_VARIABLES &&
2251 args[DEFAULT] >= INT32_MIN && args[DEFAULT] <= INT32_MAX &&
2252 args[MIN] >= INT32_MIN && args[MIN] <= INT32_MAX &&
2253 args[MAX] >= INT32_MIN && args[MAX] <= INT32_MAX
2255 globalVariableConfigsMutable(i)->defaultValue = args[DEFAULT];
2256 globalVariableConfigsMutable(i)->min = args[MIN];
2257 globalVariableConfigsMutable(i)->max = args[MAX];
2259 cliGvar("");
2260 } else {
2261 cliShowParseError();
2266 static void printPid(uint8_t dumpMask, const programmingPid_t *programmingPids, const programmingPid_t *defaultProgrammingPids)
2268 const char *format = "pid %d %d %d %d %d %d %d %d %d %d";
2269 for (uint32_t i = 0; i < MAX_PROGRAMMING_PID_COUNT; i++) {
2270 const programmingPid_t pid = programmingPids[i];
2272 bool equalsDefault = false;
2273 if (defaultProgrammingPids) {
2274 programmingPid_t defaultValue = defaultProgrammingPids[i];
2275 equalsDefault =
2276 pid.enabled == defaultValue.enabled &&
2277 pid.setpoint.type == defaultValue.setpoint.type &&
2278 pid.setpoint.value == defaultValue.setpoint.value &&
2279 pid.measurement.type == defaultValue.measurement.type &&
2280 pid.measurement.value == defaultValue.measurement.value &&
2281 pid.gains.P == defaultValue.gains.P &&
2282 pid.gains.I == defaultValue.gains.I &&
2283 pid.gains.D == defaultValue.gains.D &&
2284 pid.gains.FF == defaultValue.gains.FF;
2286 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2288 pid.enabled,
2289 pid.setpoint.type,
2290 pid.setpoint.value,
2291 pid.measurement.type,
2292 pid.measurement.value,
2293 pid.gains.P,
2294 pid.gains.I,
2295 pid.gains.D,
2296 pid.gains.FF
2299 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2301 pid.enabled,
2302 pid.setpoint.type,
2303 pid.setpoint.value,
2304 pid.measurement.type,
2305 pid.measurement.value,
2306 pid.gains.P,
2307 pid.gains.I,
2308 pid.gains.D,
2309 pid.gains.FF
2314 static void cliPid(char *cmdline) {
2315 char * saveptr;
2316 int args[10], check = 0;
2317 uint8_t len = strlen(cmdline);
2319 if (len == 0) {
2320 printPid(DUMP_MASTER, programmingPids(0), NULL);
2321 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2322 pgResetCopy(programmingPidsMutable(0), PG_LOGIC_CONDITIONS);
2323 } else {
2324 enum {
2325 INDEX = 0,
2326 ENABLED,
2327 SETPOINT_TYPE,
2328 SETPOINT_VALUE,
2329 MEASUREMENT_TYPE,
2330 MEASUREMENT_VALUE,
2331 P_GAIN,
2332 I_GAIN,
2333 D_GAIN,
2334 FF_GAIN,
2335 ARGS_COUNT
2337 char *ptr = strtok_r(cmdline, " ", &saveptr);
2338 while (ptr != NULL && check < ARGS_COUNT) {
2339 args[check++] = fastA2I(ptr);
2340 ptr = strtok_r(NULL, " ", &saveptr);
2343 if (ptr != NULL || check != ARGS_COUNT) {
2344 cliShowParseError();
2345 return;
2348 int32_t i = args[INDEX];
2349 if (
2350 i >= 0 && i < MAX_PROGRAMMING_PID_COUNT &&
2351 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
2352 args[SETPOINT_TYPE] >= 0 && args[SETPOINT_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2353 args[SETPOINT_VALUE] >= -1000000 && args[SETPOINT_VALUE] <= 1000000 &&
2354 args[MEASUREMENT_TYPE] >= 0 && args[MEASUREMENT_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2355 args[MEASUREMENT_VALUE] >= -1000000 && args[MEASUREMENT_VALUE] <= 1000000 &&
2356 args[P_GAIN] >= 0 && args[P_GAIN] <= INT16_MAX &&
2357 args[I_GAIN] >= 0 && args[I_GAIN] <= INT16_MAX &&
2358 args[D_GAIN] >= 0 && args[D_GAIN] <= INT16_MAX &&
2359 args[FF_GAIN] >= 0 && args[FF_GAIN] <= INT16_MAX
2361 programmingPidsMutable(i)->enabled = args[ENABLED];
2362 programmingPidsMutable(i)->setpoint.type = args[SETPOINT_TYPE];
2363 programmingPidsMutable(i)->setpoint.value = args[SETPOINT_VALUE];
2364 programmingPidsMutable(i)->measurement.type = args[MEASUREMENT_TYPE];
2365 programmingPidsMutable(i)->measurement.value = args[MEASUREMENT_VALUE];
2366 programmingPidsMutable(i)->gains.P = args[P_GAIN];
2367 programmingPidsMutable(i)->gains.I = args[I_GAIN];
2368 programmingPidsMutable(i)->gains.D = args[D_GAIN];
2369 programmingPidsMutable(i)->gains.FF = args[FF_GAIN];
2371 cliPid("");
2372 } else {
2373 cliShowParseError();
2378 static void printOsdCustomElements(uint8_t dumpMask, const osdCustomElement_t *osdCustomElements, const osdCustomElement_t *defaultosdCustomElements)
2380 const char *format = "osd_custom_elements %d %d %d %d %d %d %d %d %d \"%s\"";
2382 if(CUSTOM_ELEMENTS_PARTS != 3)
2384 cliPrintHashLine("Incompatible count of elements for custom OSD elements");
2387 for (uint8_t i = 0; i < MAX_CUSTOM_ELEMENTS; i++) {
2388 bool equalsDefault = false;
2390 const osdCustomElement_t osdCustomElement = osdCustomElements[i];
2391 if(defaultosdCustomElements){
2392 const osdCustomElement_t defaultValue = defaultosdCustomElements[i];
2393 equalsDefault =
2394 osdCustomElement.part[0].type == defaultValue.part[0].type &&
2395 osdCustomElement.part[0].value == defaultValue.part[0].value &&
2396 osdCustomElement.part[1].type == defaultValue.part[1].type &&
2397 osdCustomElement.part[1].value == defaultValue.part[1].value &&
2398 osdCustomElement.part[2].type == defaultValue.part[2].type &&
2399 osdCustomElement.part[2].value == defaultValue.part[2].value &&
2400 osdCustomElement.visibility.type == defaultValue.visibility.type &&
2401 osdCustomElement.visibility.value == defaultValue.visibility.value &&
2402 strcmp(osdCustomElement.osdCustomElementText, defaultValue.osdCustomElementText) == 0;
2404 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2406 osdCustomElement.part[0].type,
2407 osdCustomElement.part[0].value,
2408 osdCustomElement.part[1].type,
2409 osdCustomElement.part[1].value,
2410 osdCustomElement.part[2].type,
2411 osdCustomElement.part[2].value,
2412 osdCustomElement.visibility.type,
2413 osdCustomElement.visibility.value,
2414 osdCustomElement.osdCustomElementText
2418 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2420 osdCustomElement.part[0].type,
2421 osdCustomElement.part[0].value,
2422 osdCustomElement.part[1].type,
2423 osdCustomElement.part[1].value,
2424 osdCustomElement.part[2].type,
2425 osdCustomElement.part[2].value,
2426 osdCustomElement.visibility.type,
2427 osdCustomElement.visibility.value,
2428 osdCustomElement.osdCustomElementText
2433 static void osdCustom(char *cmdline){
2434 char * saveptrMain;
2435 char * saveptrParams;
2436 int args[10], check = 0;
2437 char text[OSD_CUSTOM_ELEMENT_TEXT_SIZE];
2438 uint8_t len = strlen(cmdline);
2440 if (len == 0) {
2441 printOsdCustomElements(DUMP_MASTER, osdCustomElements(0), NULL);
2442 } else {
2443 //split by ", first are params second is text
2444 char *ptrMain = strtok_r(cmdline, "\"", &saveptrMain);
2445 enum {
2446 INDEX = 0,
2447 PART0_TYPE,
2448 PART0_VALUE,
2449 PART1_TYPE,
2450 PART1_VALUE,
2451 PART2_TYPE,
2452 PART2_VALUE,
2453 VISIBILITY_TYPE,
2454 VISIBILITY_VALUE,
2455 ARGS_COUNT
2457 char *ptrParams = strtok_r(ptrMain, " ", &saveptrParams);
2458 while (ptrParams != NULL && check < ARGS_COUNT) {
2459 args[check++] = fastA2I(ptrParams);
2460 ptrParams = strtok_r(NULL, " ", &saveptrParams);
2463 if (check != ARGS_COUNT) {
2464 cliShowParseError();
2465 return;
2468 //text
2469 char *ptrText = strtok_r(NULL, "\"", &saveptrMain);
2470 size_t copySize = 0;
2471 if(ptrText != NULL){
2472 copySize = MIN(strlen(ptrText), (size_t)(sizeof(text) - 1));
2473 if(copySize > 0){
2474 memcpy(text, ptrText, copySize);
2477 text[copySize] = '\0';
2479 int32_t i = args[INDEX];
2480 if (
2481 i >= 0 && i < MAX_CUSTOM_ELEMENTS &&
2482 args[PART0_TYPE] >= 0 && args[PART0_TYPE] <= 7 &&
2483 args[PART0_VALUE] >= 0 && args[PART0_VALUE] <= UINT8_MAX &&
2484 args[PART1_TYPE] >= 0 && args[PART1_TYPE] <= 7 &&
2485 args[PART1_VALUE] >= 0 && args[PART1_VALUE] <= UINT8_MAX &&
2486 args[PART2_TYPE] >= 0 && args[PART2_TYPE] <= 7 &&
2487 args[PART2_VALUE] >= 0 && args[PART2_VALUE] <= UINT8_MAX &&
2488 args[VISIBILITY_TYPE] >= 0 && args[VISIBILITY_TYPE] <= 2 &&
2489 args[VISIBILITY_VALUE] >= 0 && args[VISIBILITY_VALUE] <= UINT8_MAX
2491 osdCustomElementsMutable(i)->part[0].type = args[PART0_TYPE];
2492 osdCustomElementsMutable(i)->part[0].value = args[PART0_VALUE];
2493 osdCustomElementsMutable(i)->part[1].type = args[PART1_TYPE];
2494 osdCustomElementsMutable(i)->part[1].value = args[PART1_VALUE];
2495 osdCustomElementsMutable(i)->part[2].type = args[PART2_TYPE];
2496 osdCustomElementsMutable(i)->part[2].value = args[PART2_VALUE];
2497 osdCustomElementsMutable(i)->visibility.type = args[VISIBILITY_TYPE];
2498 osdCustomElementsMutable(i)->visibility.value = args[VISIBILITY_VALUE];
2499 memcpy(osdCustomElementsMutable(i)->osdCustomElementText, text, OSD_CUSTOM_ELEMENT_TEXT_SIZE);
2501 osdCustom("");
2502 } else {
2503 cliShowParseError();
2509 #endif
2511 #ifdef USE_SDCARD
2513 static void cliWriteBytes(const uint8_t *buffer, int count)
2515 while (count > 0) {
2516 cliWrite(*buffer);
2517 buffer++;
2518 count--;
2522 static void cliSdInfo(char *cmdline)
2524 UNUSED(cmdline);
2526 cliPrint("SD card: ");
2528 if (!sdcard_isInserted()) {
2529 cliPrintLine("None inserted");
2530 return;
2533 if (!sdcard_isInitialized()) {
2534 cliPrintLine("Startup failed");
2535 return;
2538 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2540 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2541 metadata->manufacturerID,
2542 metadata->numBlocks / 2, /* One block is half a kB */
2543 metadata->productionMonth,
2544 metadata->productionYear,
2545 metadata->productRevisionMajor,
2546 metadata->productRevisionMinor
2549 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2551 cliPrint("'\r\n" "Filesystem: ");
2553 switch (afatfs_getFilesystemState()) {
2554 case AFATFS_FILESYSTEM_STATE_READY:
2555 cliPrint("Ready");
2556 break;
2557 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2558 cliPrint("Initializing");
2559 break;
2560 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2561 case AFATFS_FILESYSTEM_STATE_FATAL:
2562 cliPrint("Fatal");
2564 switch (afatfs_getLastError()) {
2565 case AFATFS_ERROR_BAD_MBR:
2566 cliPrint(" - no FAT MBR partitions");
2567 break;
2568 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2569 cliPrint(" - bad FAT header");
2570 break;
2571 case AFATFS_ERROR_GENERIC:
2572 case AFATFS_ERROR_NONE:
2573 ; // Nothing more detailed to print
2574 break;
2576 break;
2578 cliPrintLinefeed();
2581 #endif
2583 #ifdef USE_FLASHFS
2585 static void cliFlashInfo(char *cmdline)
2587 UNUSED(cmdline);
2589 const flashGeometry_t *layout = flashGetGeometry();
2591 if (layout->totalSize == 0) {
2592 cliPrintLine("Flash not available");
2593 return;
2596 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2597 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2599 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2600 const flashPartition_t *partition;
2601 if (index == 0) {
2602 cliPrintLine("Paritions:");
2604 partition = flashPartitionFindByIndex(index);
2605 if (!partition) {
2606 break;
2608 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2610 #ifdef USE_FLASHFS
2611 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2613 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2614 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2615 flashfsGetOffset()
2617 #endif
2620 static void cliFlashErase(char *cmdline)
2622 UNUSED(cmdline);
2624 const flashGeometry_t *layout = flashGetGeometry();
2626 if (layout->totalSize == 0) {
2627 cliPrintLine("Flash not available");
2628 return;
2631 cliPrintLine("Erasing...");
2632 flashfsEraseCompletely();
2634 while (!flashIsReady()) {
2635 delay(100);
2638 cliPrintLine("Done.");
2641 #ifdef USE_FLASH_TOOLS
2643 static void cliFlashWrite(char *cmdline)
2645 const uint32_t address = fastA2I(cmdline);
2646 const char *text = strchr(cmdline, ' ');
2648 if (!text) {
2649 cliShowParseError();
2650 } else {
2651 flashfsSeekAbs(address);
2652 flashfsWrite((uint8_t*)text, strlen(text), true);
2653 flashfsFlushSync();
2655 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2659 static void cliFlashRead(char *cmdline)
2661 uint32_t address = fastA2I(cmdline);
2663 const char *nextArg = strchr(cmdline, ' ');
2665 if (!nextArg) {
2666 cliShowParseError();
2667 } else {
2668 uint32_t length = fastA2I(nextArg);
2670 cliPrintLinef("Reading %u bytes at %u:", length, address);
2672 uint8_t buffer[32];
2673 while (length > 0) {
2674 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2676 for (int i = 0; i < bytesRead; i++) {
2677 cliWrite(buffer[i]);
2680 length -= bytesRead;
2681 address += bytesRead;
2683 if (bytesRead == 0) {
2684 //Assume we reached the end of the volume or something fatal happened
2685 break;
2688 cliPrintLinefeed();
2692 #endif
2693 #endif
2695 #ifdef USE_OSD
2696 static void printOsdLayout(uint8_t dumpMask, const osdLayoutsConfig_t *config, const osdLayoutsConfig_t *configDefault, int layout, int item)
2698 // "<layout> <item> <col> <row> <visible>"
2699 const char *format = "osd_layout %d %d %d %d %c";
2700 for (int ii = 0; ii < OSD_LAYOUT_COUNT; ii++) {
2701 if (layout >= 0 && layout != ii) {
2702 continue;
2704 const uint16_t *layoutItems = config->item_pos[ii];
2705 const uint16_t *defaultLayoutItems = configDefault->item_pos[ii];
2706 for (int jj = 0; jj < OSD_ITEM_COUNT; jj++) {
2707 if (item >= 0 && item != jj) {
2708 continue;
2710 bool equalsDefault = layoutItems[jj] == defaultLayoutItems[jj];
2711 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2712 ii, jj,
2713 OSD_X(defaultLayoutItems[jj]),
2714 OSD_Y(defaultLayoutItems[jj]),
2715 OSD_VISIBLE(defaultLayoutItems[jj]) ? 'V' : 'H');
2717 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2718 ii, jj,
2719 OSD_X(layoutItems[jj]),
2720 OSD_Y(layoutItems[jj]),
2721 OSD_VISIBLE(layoutItems[jj]) ? 'V' : 'H');
2726 static void cliOsdLayout(char *cmdline)
2728 char * saveptr;
2730 int layout = -1;
2731 int item = -1;
2732 int col = 0;
2733 int row = 0;
2734 bool visible = false;
2735 char *tok = strtok_r(cmdline, " ", &saveptr);
2737 int ii;
2739 for (ii = 0; tok != NULL; ii++, tok = strtok_r(NULL, " ", &saveptr)) {
2740 switch (ii) {
2741 case 0:
2742 layout = fastA2I(tok);
2743 if (layout < 0 || layout >= OSD_LAYOUT_COUNT) {
2744 cliShowParseError();
2745 return;
2747 break;
2748 case 1:
2749 item = fastA2I(tok);
2750 if (item < 0 || item >= OSD_ITEM_COUNT) {
2751 cliShowParseError();
2752 return;
2754 break;
2755 case 2:
2756 col = fastA2I(tok);
2757 if (col < 0 || col > OSD_X(OSD_POS_MAX)) {
2758 cliShowParseError();
2759 return;
2761 break;
2762 case 3:
2763 row = fastA2I(tok);
2764 if (row < 0 || row > OSD_Y(OSD_POS_MAX)) {
2765 cliShowParseError();
2766 return;
2768 break;
2769 case 4:
2770 switch (*tok) {
2771 case 'H':
2772 visible = false;
2773 break;
2774 case 'V':
2775 visible = true;
2776 break;
2777 default:
2778 cliShowParseError();
2779 return;
2781 break;
2782 default:
2783 cliShowParseError();
2784 return;
2788 switch (ii) {
2789 case 0:
2790 FALLTHROUGH;
2791 case 1:
2792 FALLTHROUGH;
2793 case 2:
2794 // No args, or just layout or layout and item. If any of them not provided,
2795 // it will be the -1 that we used during initialization, so printOsdLayout()
2796 // won't use them for filtering.
2797 printOsdLayout(DUMP_MASTER, osdLayoutsConfig(), osdLayoutsConfig(), layout, item);
2798 break;
2799 case 4:
2800 // No visibility provided. Keep the previous one.
2801 visible = OSD_VISIBLE(osdLayoutsConfig()->item_pos[layout][item]);
2802 FALLTHROUGH;
2803 case 5:
2804 // Layout, item, pos and visibility. Set the item.
2805 osdLayoutsConfigMutable()->item_pos[layout][item] = OSD_POS(col, row) | (visible ? OSD_VISIBLE_FLAG : 0);
2806 break;
2807 default:
2808 // Unhandled
2809 cliShowParseError();
2810 return;
2814 #endif
2816 static void printTimerOutputModes(dumpFlags_e dumpFlags, const timerOverride_t* to, const timerOverride_t* defaultTimerOverride, int timer)
2818 const char *format = "timer_output_mode %d %s";
2820 for (int i = 0; i < HARDWARE_TIMER_DEFINITION_COUNT; ++i) {
2821 if (timer < 0 || timer == i) {
2822 outputMode_e mode = to[i].outputMode;
2823 bool equalsDefault = false;
2824 if(defaultTimerOverride) {
2825 outputMode_e defaultMode = defaultTimerOverride[i].outputMode;
2826 equalsDefault = mode == defaultMode;
2827 cliDefaultPrintLinef(dumpFlags, equalsDefault, format, i, outputModeNames[defaultMode]);
2829 cliDumpPrintLinef(dumpFlags, equalsDefault, format, i, outputModeNames[mode]);
2834 static void cliTimerOutputMode(char *cmdline)
2836 char * saveptr;
2838 int timer = -1;
2839 uint8_t mode;
2840 char *tok = strtok_r(cmdline, " ", &saveptr);
2842 int ii;
2844 for (ii = 0; tok != NULL; ii++, tok = strtok_r(NULL, " ", &saveptr)) {
2845 switch (ii) {
2846 case 0:
2847 timer = fastA2I(tok);
2848 if (timer < 0 || timer >= HARDWARE_TIMER_DEFINITION_COUNT) {
2849 cliShowParseError();
2850 return;
2852 break;
2853 case 1:
2854 if(!sl_strcasecmp("AUTO", tok)) {
2855 mode = OUTPUT_MODE_AUTO;
2856 } else if(!sl_strcasecmp("MOTORS", tok)) {
2857 mode = OUTPUT_MODE_MOTORS;
2858 } else if(!sl_strcasecmp("SERVOS", tok)) {
2859 mode = OUTPUT_MODE_SERVOS;
2860 } else if(!sl_strcasecmp("LED", tok)) {
2861 mode = OUTPUT_MODE_LED;
2862 } else {
2863 cliShowParseError();
2864 return;
2866 break;
2867 default:
2868 cliShowParseError();
2869 return;
2873 switch (ii) {
2874 case 0:
2875 FALLTHROUGH;
2876 case 1:
2877 // No args, or just timer. If any of them not provided,
2878 // it will be the -1 that we used during initialization, so printOsdLayout()
2879 // won't use them for filtering.
2880 printTimerOutputModes(DUMP_MASTER, timerOverrides(0), NULL, timer);
2881 break;
2882 case 2:
2883 timerOverridesMutable(timer)->outputMode = mode;
2884 printTimerOutputModes(DUMP_MASTER, timerOverrides(0), NULL, timer);
2885 break;
2886 default:
2887 // Unhandled
2888 cliShowParseError();
2889 return;
2894 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2896 uint32_t mask = featureConfig->enabledFeatures;
2897 uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2898 for (uint32_t i = 0; ; i++) { // disable all feature first
2899 if (featureNames[i] == NULL)
2900 break;
2901 if (featureNames[i][0] == '\0')
2902 continue;
2903 const char *format = "feature -%s";
2904 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2905 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2907 for (uint32_t i = 0; ; i++) { // reenable what we want.
2908 if (featureNames[i] == NULL)
2909 break;
2910 if (featureNames[i][0] == '\0')
2911 continue;
2912 const char *format = "feature %s";
2913 if (defaultMask & (1 << i)) {
2914 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2916 if (mask & (1 << i)) {
2917 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2922 static void cliFeature(char *cmdline)
2924 uint32_t len = strlen(cmdline);
2925 uint32_t mask = featureMask();
2927 if (len == 0) {
2928 cliPrint("Enabled: ");
2929 for (uint32_t i = 0; ; i++) {
2930 if (featureNames[i] == NULL)
2931 break;
2932 if (featureNames[i][0] == '\0')
2933 continue;
2934 if (mask & (1 << i))
2935 cliPrintf("%s ", featureNames[i]);
2937 cliPrintLinefeed();
2938 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
2939 cliPrint("Available: ");
2940 for (uint32_t i = 0; ; i++) {
2941 if (featureNames[i] == NULL)
2942 break;
2943 if (featureNames[i][0] == '\0')
2944 continue;
2945 cliPrintf("%s ", featureNames[i]);
2947 cliPrintLinefeed();
2948 return;
2949 } else {
2950 bool remove = false;
2951 if (cmdline[0] == '-') {
2952 // remove feature
2953 remove = true;
2954 cmdline++; // skip over -
2955 len--;
2958 for (uint32_t i = 0; ; i++) {
2959 if (featureNames[i] == NULL) {
2960 cliPrintErrorLine("Invalid name");
2961 break;
2964 if (sl_strncasecmp(cmdline, featureNames[i], len) == 0) {
2966 mask = 1 << i;
2967 #ifndef USE_GPS
2968 if (mask & FEATURE_GPS) {
2969 cliPrintErrorLine("unavailable");
2970 break;
2972 #endif
2973 if (remove) {
2974 featureClear(mask);
2975 cliPrint("Disabled");
2976 } else {
2977 featureSet(mask);
2978 cliPrint("Enabled");
2980 cliPrintLinef(" %s", featureNames[i]);
2981 break;
2987 #ifdef USE_BLACKBOX
2988 static void printBlackbox(uint8_t dumpMask, const blackboxConfig_t *config, const blackboxConfig_t *configDefault)
2991 UNUSED(configDefault);
2993 uint32_t mask = config->includeFlags;
2995 for (uint8_t i = 0; ; i++) { // reenable what we want.
2996 if (blackboxIncludeFlagNames[i] == NULL) {
2997 break;
3000 const char *formatOn = "blackbox %s";
3001 const char *formatOff = "blackbox -%s";
3003 if (mask & (1 << i)) {
3004 cliDumpPrintLinef(dumpMask, false, formatOn, blackboxIncludeFlagNames[i]);
3005 cliDefaultPrintLinef(dumpMask, false, formatOn, blackboxIncludeFlagNames[i]);
3006 } else {
3007 cliDumpPrintLinef(dumpMask, false, formatOff, blackboxIncludeFlagNames[i]);
3008 cliDefaultPrintLinef(dumpMask, false, formatOff, blackboxIncludeFlagNames[i]);
3014 static void cliBlackbox(char *cmdline)
3016 uint32_t len = strlen(cmdline);
3017 uint32_t mask = blackboxConfig()->includeFlags;
3019 if (len == 0) {
3020 cliPrint("Enabled: ");
3021 for (uint8_t i = 0; ; i++) {
3022 if (blackboxIncludeFlagNames[i] == NULL) {
3023 break;
3026 if (mask & (1 << i))
3027 cliPrintf("%s ", blackboxIncludeFlagNames[i]);
3029 cliPrintLinefeed();
3030 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
3031 cliPrint("Available: ");
3032 for (uint32_t i = 0; ; i++) {
3033 if (blackboxIncludeFlagNames[i] == NULL) {
3034 break;
3037 cliPrintf("%s ", blackboxIncludeFlagNames[i]);
3039 cliPrintLinefeed();
3040 return;
3041 } else {
3042 bool remove = false;
3043 if (cmdline[0] == '-') {
3044 // remove feature
3045 remove = true;
3046 cmdline++; // skip over -
3047 len--;
3050 for (uint32_t i = 0; ; i++) {
3051 if (blackboxIncludeFlagNames[i] == NULL) {
3052 cliPrintErrorLine("Invalid name");
3053 break;
3056 if (sl_strncasecmp(cmdline, blackboxIncludeFlagNames[i], len) == 0) {
3058 mask = 1 << i;
3060 if (remove) {
3061 blackboxIncludeFlagClear(mask);
3062 cliPrint("Disabled");
3063 } else {
3064 blackboxIncludeFlagSet(mask);
3065 cliPrint("Enabled");
3067 cliPrintLinef(" %s", blackboxIncludeFlagNames[i]);
3068 break;
3073 #endif
3075 #if defined(BEEPER) || defined(USE_DSHOT)
3076 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
3078 const uint8_t beeperCount = beeperTableEntryCount();
3079 const uint32_t mask = beeperConfig->beeper_off_flags;
3080 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
3081 for (int i = 0; i < beeperCount - 2; i++) {
3082 const char *formatOff = "beeper -%s";
3083 const char *formatOn = "beeper %s";
3084 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOn : formatOff, beeperNameForTableIndex(i));
3085 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOff : formatOn, beeperNameForTableIndex(i));
3089 static void cliBeeper(char *cmdline)
3091 uint32_t len = strlen(cmdline);
3092 uint8_t beeperCount = beeperTableEntryCount();
3093 uint32_t mask = getBeeperOffMask();
3095 if (len == 0) {
3096 cliPrintf("Disabled:");
3097 for (int32_t i = 0; ; i++) {
3098 if (i == beeperCount - 2){
3099 if (mask == 0)
3100 cliPrint(" none");
3101 break;
3103 if (mask & (1 << (beeperModeForTableIndex(i) - 1)))
3104 cliPrintf(" %s", beeperNameForTableIndex(i));
3106 cliPrintLinefeed();
3107 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
3108 cliPrint("Available:");
3109 for (uint32_t i = 0; i < beeperCount; i++)
3110 cliPrintf(" %s", beeperNameForTableIndex(i));
3111 cliPrintLinefeed();
3112 return;
3113 } else {
3114 bool remove = false;
3115 if (cmdline[0] == '-') {
3116 remove = true; // this is for beeper OFF condition
3117 cmdline++;
3118 len--;
3121 for (uint32_t i = 0; ; i++) {
3122 if (i == beeperCount) {
3123 cliPrintErrorLine("Invalid name");
3124 break;
3126 if (sl_strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
3127 if (remove) { // beeper off
3128 if (i == BEEPER_ALL-1)
3129 beeperOffSetAll(beeperCount-2);
3130 else
3131 if (i == BEEPER_PREFERENCE-1)
3132 setBeeperOffMask(getPreferredBeeperOffMask());
3133 else {
3134 mask = 1 << (beeperModeForTableIndex(i) - 1);
3135 beeperOffSet(mask);
3137 cliPrint("Disabled");
3139 else { // beeper on
3140 if (i == BEEPER_ALL-1)
3141 beeperOffClearAll();
3142 else
3143 if (i == BEEPER_PREFERENCE-1)
3144 setPreferredBeeperOffMask(getBeeperOffMask());
3145 else {
3146 mask = 1 << (beeperModeForTableIndex(i) - 1);
3147 beeperOffClear(mask);
3149 cliPrint("Enabled");
3151 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3152 break;
3157 #endif
3159 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
3161 bool equalsDefault = true;
3162 char buf[16];
3163 char bufDefault[16];
3164 uint32_t i;
3166 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3167 buf[i] = bufDefault[i] = 0;
3170 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3171 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3172 if (defaultRxConfig) {
3173 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3174 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3177 buf[i] = '\0';
3179 const char *formatMap = "map %s";
3180 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3181 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3184 static void cliMap(char *cmdline)
3186 uint32_t len;
3187 char out[MAX_MAPPABLE_RX_INPUTS + 1];
3189 len = strlen(cmdline);
3191 if (len == MAX_MAPPABLE_RX_INPUTS) {
3192 // uppercase it
3193 for (uint32_t i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3194 cmdline[i] = sl_toupper((unsigned char)cmdline[i]);
3196 for (uint32_t i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3197 if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i])) {
3198 continue;
3200 cliShowParseError();
3201 return;
3203 parseRcChannels(cmdline);
3204 } else if (len != 0) {
3205 cliShowParseError();
3207 cliPrint("Map: ");
3208 uint32_t i;
3209 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++){
3210 out[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3212 out[i] = '\0';
3213 cliPrintLinef("%s", out);
3216 static const char *checkCommand(const char *cmdLine, const char *command)
3218 if (!sl_strncasecmp(cmdLine, command, strlen(command)) // command names match
3219 && !sl_isalnum((unsigned)cmdLine[strlen(command)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
3220 return cmdLine + strlen(command) + 1;
3221 } else {
3222 return 0;
3226 static void cliRebootEx(bool bootLoader)
3228 cliPrint("\r\nRebooting");
3229 bufWriterFlush(cliWriter);
3230 waitForSerialPortToFinishTransmitting(cliPort);
3232 fcReboot(bootLoader);
3235 static void cliReboot(void)
3237 cliRebootEx(false);
3240 static void cliDfu(char *cmdline)
3242 UNUSED(cmdline);
3243 #ifndef CLI_MINIMAL_VERBOSITY
3244 cliPrint("\r\nRestarting in DFU mode");
3245 #endif
3246 cliRebootEx(true);
3249 #if defined (USE_SERIALRX_SRXL2)
3250 void cliRxBind(char *cmdline){
3251 UNUSED(cmdline);
3252 if (rxConfig()->receiverType == RX_TYPE_SERIAL) {
3253 switch (rxConfig()->serialrx_provider) {
3254 default:
3255 cliPrint("Not supported.");
3256 break;
3257 #if defined(USE_SERIALRX_SRXL2)
3258 case SERIALRX_SRXL2:
3259 srxl2Bind();
3260 cliPrint("Binding SRXL2 receiver...");
3261 break;
3262 #endif
3263 #if defined(USE_SERIALRX_CRSF)
3264 case SERIALRX_CRSF:
3265 crsfBind();
3266 cliPrint("Binding CRSF receiver...");
3267 break;
3268 #endif
3272 #endif
3274 static void cliExit(char *cmdline)
3276 UNUSED(cmdline);
3278 #ifndef CLI_MINIMAL_VERBOSITY
3279 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
3280 #endif
3281 bufWriterFlush(cliWriter);
3283 *cliBuffer = '\0';
3284 bufferIndex = 0;
3285 cliMode = false;
3286 // incase a motor was left running during motortest, clear it here
3287 mixerResetDisarmedMotors();
3288 cliReboot();
3290 cliWriter = NULL;
3293 #ifdef USE_GPS
3294 static void cliGpsPassthrough(char *cmdline)
3296 UNUSED(cmdline);
3298 gpsEnablePassthrough(cliPort);
3300 #endif
3302 static void cliMotor(char *cmdline)
3304 int motor_index = 0;
3305 int motor_value = 0;
3306 int index = 0;
3307 char *pch = NULL;
3308 char *saveptr;
3310 if (isEmpty(cmdline)) {
3311 cliShowParseError();
3313 return;
3316 pch = strtok_r(cmdline, " ", &saveptr);
3317 while (pch != NULL) {
3318 switch (index) {
3319 case 0:
3320 motor_index = fastA2I(pch);
3321 break;
3322 case 1:
3323 motor_value = fastA2I(pch);
3324 break;
3326 index++;
3327 pch = strtok_r(NULL, " ", &saveptr);
3330 if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
3331 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
3332 return;
3335 if (index == 2) {
3336 if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
3337 cliShowArgumentRangeError("value", 1000, 2000);
3338 return;
3339 } else {
3340 motor_disarmed[motor_index] = motor_value;
3344 cliPrintLinef("motor %d: %d", motor_index, motor_disarmed[motor_index]);
3347 static void cliPlaySound(char *cmdline)
3349 int i;
3350 const char *name;
3351 static int lastSoundIdx = -1;
3353 if (isEmpty(cmdline)) {
3354 i = lastSoundIdx + 1; //next sound index
3355 if ((name=beeperNameForTableIndex(i)) == NULL) {
3356 while (true) { //no name for index; try next one
3357 if (++i >= beeperTableEntryCount())
3358 i = 0; //if end then wrap around to first entry
3359 if ((name=beeperNameForTableIndex(i)) != NULL)
3360 break; //if name OK then play sound below
3361 if (i == lastSoundIdx + 1) { //prevent infinite loop
3362 cliPrintLine("Error playing sound");
3363 return;
3367 } else { //index value was given
3368 i = fastA2I(cmdline);
3369 if ((name=beeperNameForTableIndex(i)) == NULL) {
3370 cliPrintLinef("No sound for index %d", i);
3371 return;
3374 lastSoundIdx = i;
3375 beeperSilence();
3376 cliPrintLinef("Playing sound %d: %s", i, name);
3377 beeper(beeperModeForTableIndex(i));
3380 static void cliControlProfile(char *cmdline)
3382 // CLI profile index is 1-based
3383 if (isEmpty(cmdline)) {
3384 cliPrintLinef("control_profile %d", getConfigProfile() + 1);
3385 return;
3386 } else {
3387 const int i = fastA2I(cmdline) - 1;
3388 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3389 setConfigProfileAndWriteEEPROM(i);
3390 cliControlProfile("");
3395 static void cliDumpControlProfile(uint8_t profileIndex, uint8_t dumpMask)
3397 if (profileIndex >= MAX_PROFILE_COUNT) {
3398 // Faulty values
3399 return;
3401 setConfigProfile(profileIndex);
3402 cliPrintHashLine("control_profile");
3403 cliPrintLinef("control_profile %d\r\n", getConfigProfile() + 1);
3404 dumpAllValues(PROFILE_VALUE, dumpMask);
3405 dumpAllValues(CONTROL_RATE_VALUE, dumpMask);
3406 dumpAllValues(EZ_TUNE_VALUE, dumpMask);
3409 static void cliBatteryProfile(char *cmdline)
3411 // CLI profile index is 1-based
3412 if (isEmpty(cmdline)) {
3413 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
3414 return;
3415 } else {
3416 const int i = fastA2I(cmdline) - 1;
3417 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3418 setConfigBatteryProfileAndWriteEEPROM(i);
3419 cliBatteryProfile("");
3424 static void cliDumpBatteryProfile(uint8_t profileIndex, uint8_t dumpMask)
3426 if (profileIndex >= MAX_BATTERY_PROFILE_COUNT) {
3427 // Faulty values
3428 return;
3430 setConfigBatteryProfile(profileIndex);
3431 cliPrintHashLine("battery_profile");
3432 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
3433 dumpAllValues(BATTERY_CONFIG_VALUE, dumpMask);
3436 static void cliMixerProfile(char *cmdline)
3438 // CLI profile index is 1-based
3439 if (isEmpty(cmdline)) {
3440 cliPrintLinef("mixer_profile %d", getConfigMixerProfile() + 1);
3441 return;
3442 } else {
3443 const int i = fastA2I(cmdline) - 1;
3444 if (i >= 0 && i < MAX_MIXER_PROFILE_COUNT) {
3445 setConfigMixerProfileAndWriteEEPROM(i);
3446 cliMixerProfile("");
3451 static void cliDumpMixerProfile(uint8_t profileIndex, uint8_t dumpMask)
3453 if (profileIndex >= MAX_MIXER_PROFILE_COUNT) {
3454 // Faulty values
3455 return;
3457 setConfigMixerProfile(profileIndex);
3458 cliPrintHashLine("mixer_profile");
3459 cliPrintLinef("mixer_profile %d\r\n", getConfigMixerProfile() + 1);
3460 dumpAllValues(MIXER_CONFIG_VALUE, dumpMask);
3461 cliPrintHashLine("Mixer: motor mixer");
3462 cliDumpPrintLinef(dumpMask, primaryMotorMixer_CopyArray()[0].throttle == 0.0f, "\r\nmmix reset\r\n");
3463 printMotorMix(dumpMask, primaryMotorMixer_CopyArray(), primaryMotorMixer(0));
3464 cliPrintHashLine("Mixer: servo mixer");
3465 cliDumpPrintLinef(dumpMask, customServoMixers_CopyArray()[0].rate == 0, "smix reset\r\n");
3466 printServoMix(dumpMask, customServoMixers_CopyArray(), customServoMixers(0));
3469 #ifdef USE_CLI_BATCH
3470 static void cliPrintCommandBatchWarning(const char *warning)
3472 char errorBuf[59];
3473 tfp_sprintf(errorBuf, "%d ERRORS WERE DETECTED - Please review and fix before continuing!", commandBatchErrorCount);
3475 cliPrintErrorLinef(errorBuf);
3476 if (warning) {
3477 cliPrintErrorLinef(warning);
3481 static void resetCommandBatch(void)
3483 commandBatchActive = false;
3484 commandBatchError = false;
3485 commandBatchErrorCount = 0;
3488 static void cliBatch(char *cmdline)
3490 if (strncasecmp(cmdline, "start", 5) == 0) {
3491 if (!commandBatchActive) {
3492 commandBatchActive = true;
3493 commandBatchError = false;
3494 commandBatchErrorCount = 0;
3496 cliPrintLine("Command batch started");
3497 } else if (strncasecmp(cmdline, "end", 3) == 0) {
3498 if (commandBatchActive && commandBatchError) {
3499 cliPrintCommandBatchWarning(NULL);
3500 } else {
3501 cliPrintLine("Command batch ended");
3503 resetCommandBatch();
3504 } else {
3505 cliPrintErrorLinef("Invalid option");
3508 #endif
3510 static void cliSave(char *cmdline)
3512 UNUSED(cmdline);
3514 #ifdef USE_CLI_BATCH
3515 if (commandBatchActive && commandBatchError) {
3516 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3517 resetCommandBatch();
3518 return;
3520 #endif
3522 cliPrint("Saving");
3523 //copyCurrentProfileToProfileSlot(getConfigProfile();
3524 suspendRxSignal();
3525 writeEEPROM();
3526 resumeRxSignal();
3527 cliReboot();
3530 static void cliDefaults(char *cmdline)
3532 UNUSED(cmdline);
3534 cliPrint("Resetting to defaults");
3535 resetEEPROM();
3536 suspendRxSignal();
3537 writeEEPROM();
3538 resumeRxSignal();
3540 #ifdef USE_CLI_BATCH
3541 commandBatchError = false;
3542 #endif
3544 if (!checkCommand(cmdline, "noreboot"))
3545 cliReboot();
3548 static void cliGet(char *cmdline)
3550 const setting_t *val;
3551 int matchedCommands = 0;
3552 char name[SETTING_MAX_NAME_LENGTH];
3554 while(*cmdline == ' ') ++cmdline; // ignore spaces
3556 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3557 val = settingGet(i);
3558 if (settingNameContains(val, name, cmdline)) {
3559 cliPrintf("%s = ", name);
3560 if (strcmp(name, "name") == 0) {
3561 // if the craftname has a leading space, then enclose the name in quotes
3562 const char * v = (const char *)settingGetValuePointer(val);
3563 cliPrintf(v[0] == ' ' ? "\"%s\"" : "%s", v);
3564 } else {
3565 cliPrintVar(val, 0);
3567 cliPrintLinefeed();
3568 cliPrintVarRange(val);
3569 cliPrintLinefeed();
3571 matchedCommands++;
3576 if (matchedCommands) {
3577 return;
3580 cliPrintErrorLine("Invalid name");
3583 static void cliSet(char *cmdline)
3585 uint32_t len;
3586 const setting_t *val;
3587 char *eqptr = NULL;
3588 char name[SETTING_MAX_NAME_LENGTH];
3590 while(*cmdline == ' ') ++cmdline; // ignore spaces
3592 len = strlen(cmdline);
3594 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
3595 cliPrintLine("Current settings:");
3596 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3597 val = settingGet(i);
3598 settingGetName(val, name);
3599 cliPrintf("%s = ", name);
3600 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3601 cliPrintLinefeed();
3603 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
3604 // has equals
3606 char *lastNonSpaceCharacter = eqptr;
3607 while (*(lastNonSpaceCharacter - 1) == ' ') {
3608 lastNonSpaceCharacter--;
3610 uint8_t variableNameLength = lastNonSpaceCharacter - cmdline;
3612 // skip the '=' and any ' ' characters
3613 eqptr++;
3614 while (*(eqptr) == ' ') {
3615 eqptr++;
3618 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3619 val = settingGet(i);
3620 // ensure exact match when setting to prevent setting variables with shorter names
3621 if (settingNameIsExactMatch(val, name, cmdline, variableNameLength)) {
3622 const setting_type_e type = SETTING_TYPE(val);
3623 if (type == VAR_STRING) {
3624 // Convert strings to uppercase. Lower case is not supported by the OSD.
3625 sl_toupperptr(eqptr);
3626 // if setting the craftname, remove any quotes around the name. This allows leading spaces in the name
3627 if ((strcmp(name, "name") == 0 || strcmp(name, "pilot_name") == 0) && (eqptr[0] == '"' && eqptr[strlen(eqptr)-1] == '"')) {
3628 settingSetString(val, eqptr + 1, strlen(eqptr)-2);
3629 } else {
3630 settingSetString(val, eqptr, strlen(eqptr));
3632 return;
3634 const setting_mode_e mode = SETTING_MODE(val);
3635 bool changeValue = false;
3636 int_float_value_t tmp = {0};
3637 switch (mode) {
3638 case MODE_DIRECT: {
3639 if (*eqptr != 0 && strspn(eqptr, "0123456789.+-") == strlen(eqptr)) {
3640 float valuef = fastA2F(eqptr);
3641 // note: compare float values
3642 if (valuef >= (float)settingGetMin(val) && valuef <= (float)settingGetMax(val)) {
3644 if (type == VAR_FLOAT)
3645 tmp.float_value = valuef;
3646 else if (type == VAR_UINT32)
3647 tmp.uint_value = fastA2UL(eqptr);
3648 else
3649 tmp.int_value = fastA2I(eqptr);
3651 changeValue = true;
3655 break;
3656 case MODE_LOOKUP: {
3657 const lookupTableEntry_t *tableEntry = settingLookupTable(val);
3658 bool matched = false;
3659 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
3660 matched = sl_strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
3662 if (matched) {
3663 tmp.int_value = tableValueIndex;
3664 changeValue = true;
3668 break;
3671 if (changeValue) {
3672 cliSetIntFloatVar(val, tmp);
3674 cliPrintf("%s set to ", name);
3675 cliPrintVar(val, 0);
3676 } else {
3677 cliPrintError("Invalid value. ");
3678 cliPrintVarRange(val);
3679 cliPrintLinefeed();
3682 return;
3685 cliPrintErrorLine("Invalid name");
3686 } else {
3687 // no equals, check for matching variables.
3688 cliGet(cmdline);
3692 static const char * getBatteryStateString(void)
3694 static const char * const batteryStateStrings[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
3696 return batteryStateStrings[getBatteryState()];
3699 static void cliStatus(char *cmdline)
3701 UNUSED(cmdline);
3703 char buf[MAX(FORMATTED_DATE_TIME_BUFSIZE, SETTING_MAX_NAME_LENGTH)];
3704 dateTime_t dt;
3706 cliPrintLinef("%s/%s %s %s / %s (%s) %s",
3707 FC_FIRMWARE_NAME,
3708 targetName,
3709 FC_VERSION_STRING,
3710 buildDate,
3711 buildTime,
3712 shortGitRevision,
3713 FC_VERSION_TYPE
3715 cliPrintLinef("GCC-%s",
3716 compilerVersion
3718 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3719 rtcGetDateTime(&dt);
3720 dateTimeFormatLocal(buf, &dt);
3721 cliPrintLinef("Current Time: %s", buf);
3722 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
3723 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3725 const uint32_t detectedSensorsMask = sensorsMask();
3727 for (int i = 0; i < SENSOR_INDEX_COUNT; i++) {
3729 const uint32_t mask = (1 << i);
3730 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3731 const int sensorHardwareIndex = detectedSensors[i];
3732 if (sensorHardwareNames[i]) {
3733 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3734 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3738 cliPrintLinefeed();
3739 #if !defined(SITL_BUILD)
3740 #if defined(AT32F43x)
3741 cliPrintLine("AT32 system clocks:");
3742 crm_clocks_freq_type clocks;
3743 crm_clocks_freq_get(&clocks);
3745 cliPrintLinef(" SYSCLK = %d MHz", clocks.sclk_freq / 1000000);
3746 cliPrintLinef(" ABH = %d MHz", clocks.ahb_freq / 1000000);
3747 cliPrintLinef(" ABP1 = %d MHz", clocks.apb1_freq / 1000000);
3748 cliPrintLinef(" ABP2 = %d MHz", clocks.apb2_freq / 1000000);
3749 #else
3750 cliPrintLine("STM32 system clocks:");
3751 #if defined(USE_HAL_DRIVER)
3752 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
3753 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
3754 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
3755 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
3756 #else
3757 RCC_ClocksTypeDef clocks;
3758 RCC_GetClocksFreq(&clocks);
3759 cliPrintLinef(" SYSCLK = %d MHz", clocks.SYSCLK_Frequency / 1000000);
3760 cliPrintLinef(" HCLK = %d MHz", clocks.HCLK_Frequency / 1000000);
3761 cliPrintLinef(" PCLK1 = %d MHz", clocks.PCLK1_Frequency / 1000000);
3762 cliPrintLinef(" PCLK2 = %d MHz", clocks.PCLK2_Frequency / 1000000);
3763 #endif
3764 #endif // for if at32
3765 #endif // for SITL
3767 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
3768 hardwareSensorStatusNames[getHwGyroStatus()],
3769 hardwareSensorStatusNames[getHwAccelerometerStatus()],
3770 hardwareSensorStatusNames[getHwCompassStatus()],
3771 hardwareSensorStatusNames[getHwBarometerStatus()],
3772 hardwareSensorStatusNames[getHwRangefinderStatus()],
3773 hardwareSensorStatusNames[getHwOpticalFlowStatus()],
3774 hardwareSensorStatusNames[getHwGPSStatus()]
3777 #ifdef USE_ESC_SENSOR
3778 uint8_t motorCount = getMotorCount();
3779 if (STATE(ESC_SENSOR_ENABLED) && motorCount > 0) {
3780 cliPrintLinef("ESC Temperature(s): Motor Count = %d", motorCount);
3781 for (uint8_t i = 0; i < motorCount; i++) {
3782 const escSensorData_t *escState = getEscTelemetry(i); //Get ESC telemetry
3783 cliPrintf("ESC %d: %d\260C, ", i, escState->temperature);
3785 cliPrintLinefeed();
3787 #endif
3789 #ifdef USE_SDCARD
3790 cliSdInfo(NULL);
3791 #endif
3792 #ifdef USE_I2C
3793 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3794 #elif !defined(SITL_BUILD)
3795 const uint16_t i2cErrorCounter = 0;
3796 #endif
3798 #ifdef STACK_CHECK
3799 cliPrintf("Stack used: %d, ", stackUsedSize());
3800 #endif
3801 #if !defined(SITL_BUILD)
3802 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
3804 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
3805 #endif
3806 #if defined(USE_ADC) && !defined(SITL_BUILD)
3807 static char * adcFunctions[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
3808 cliPrintLine("ADC channel usage:");
3809 for (int i = 0; i < ADC_FUNCTION_COUNT; i++) {
3810 cliPrintf(" %8s :", adcFunctions[i]);
3812 cliPrint(" configured = ");
3813 if (adcChannelConfig()->adcFunctionChannel[i] == ADC_CHN_NONE) {
3814 cliPrint("none");
3816 else {
3817 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel[i]);
3820 cliPrint(", used = ");
3821 if (adcGetFunctionChannelAllocation(i) == ADC_CHN_NONE) {
3822 cliPrintLine("none");
3824 else {
3825 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i));
3828 #endif
3830 cliPrintf("System load: %d", averageSystemLoadPercent);
3831 const timeDelta_t pidTaskDeltaTime = getTaskDeltaTime(TASK_PID);
3832 const int pidRate = pidTaskDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)pidTaskDeltaTime));
3833 const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
3834 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3835 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime, pidRate, rxRate, systemRate);
3836 #if !defined(CLI_MINIMAL_VERBOSITY)
3837 cliPrint("Arming disabled flags:");
3838 uint32_t flags = armingFlags & ARMING_DISABLED_ALL_FLAGS;
3839 while (flags) {
3840 int bitpos = ffs(flags) - 1;
3841 flags &= ~(1 << bitpos);
3842 if (bitpos > 6) cliPrintf(" %s", armingDisableFlagNames[bitpos - 7]);
3844 cliPrintLinefeed();
3845 if (armingFlags & ARMING_DISABLED_INVALID_SETTING) {
3846 unsigned invalidIndex;
3847 if (!settingsValidate(&invalidIndex)) {
3848 settingGetName(settingGet(invalidIndex), buf);
3849 cliPrintErrorLinef("Invalid setting: %s", buf);
3852 #else
3853 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags & ARMING_DISABLED_ALL_FLAGS);
3854 #endif
3856 #if !defined(CLI_MINIMAL_VERBOSITY)
3857 cliPrint("OSD: ");
3858 #if defined(USE_OSD)
3859 displayPort_t *osdDisplayPort = osdGetDisplayPort();
3860 if (osdDisplayPort != NULL) {
3861 cliPrintf("%s [%u x %u]", osdDisplayPort->displayPortType, osdDisplayPort->cols, osdDisplayPort->rows);
3862 } else {
3863 cliPrint("not enabled");
3865 #else
3866 cliPrint("not used");
3867 #endif
3868 cliPrintLinefeed();
3870 cliPrint("VTX: ");
3871 #if defined(USE_VTX_CONTROL)
3872 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
3873 vtxDeviceOsdInfo_t osdInfo;
3874 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo);
3875 cliPrintf("band: %c, chan: %s, power: %c", osdInfo.bandLetter, osdInfo.channelName, osdInfo.powerIndexLetter);
3877 if (osdInfo.powerMilliwatt) {
3878 cliPrintf(" (%d mW)", osdInfo.powerMilliwatt);
3881 if (osdInfo.frequency) {
3882 cliPrintf(", freq: %d MHz", osdInfo.frequency);
3885 else {
3886 cliPrint("not detected");
3888 #else
3889 cliPrint("no VTX control");
3890 #endif
3892 cliPrintLinefeed();
3893 #endif
3895 if (featureConfigured(FEATURE_GPS) && isGpsUblox()) {
3896 cliPrint("GPS: ");
3897 cliPrintf("HW Version: %s Proto: %d.%02d Baud: %d", getGpsHwVersion(), getGpsProtoMajorVersion(), getGpsProtoMinorVersion(), getGpsBaudrate());
3898 if(ubloxVersionLT(15, 0)) {
3899 cliPrintf(" (UBLOX Proto >= 15.0 required)");
3901 cliPrintLinefeed();
3902 cliPrintLinef(" SATS: %i", gpsSol.numSat);
3903 cliPrintLinef(" HDOP: %f", (double)(gpsSol.hdop / (float)HDOP_SCALE));
3904 cliPrintLinef(" EPH : %f m", (double)(gpsSol.eph / 100.0f));
3905 cliPrintLinef(" EPV : %f m", (double)(gpsSol.epv / 100.0f));
3906 //cliPrintLinef(" GNSS Capabilities: %d", gpsUbloxCapLastUpdate());
3907 cliPrintLinef(" GNSS Capabilities:");
3908 cliPrintLine(" GNSS Provider active/default");
3909 cliPrintLine(" GPS 1/1");
3910 if(gpsUbloxHasGalileo())
3911 cliPrintLinef(" Galileo %d/%d", gpsUbloxGalileoEnabled(), gpsUbloxGalileoDefault());
3912 if(gpsUbloxHasBeidou())
3913 cliPrintLinef(" BeiDou %d/%d", gpsUbloxBeidouEnabled(), gpsUbloxBeidouDefault());
3914 if(gpsUbloxHasGlonass())
3915 cliPrintLinef(" Glonass %d/%d", gpsUbloxGlonassEnabled(), gpsUbloxGlonassDefault());
3916 cliPrintLinef(" Max concurrent: %d", gpsUbloxMaxGnss());
3919 // If we are blocked by PWM init - provide more information
3920 if (getPwmInitError() != PWM_INIT_ERROR_NONE) {
3921 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3925 static void cliTasks(char *cmdline)
3927 UNUSED(cmdline);
3928 int maxLoadSum = 0;
3929 int averageLoadSum = 0;
3930 cfCheckFuncInfo_t checkFuncInfo;
3932 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3933 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3934 cfTaskInfo_t taskInfo;
3935 getTaskInfo(taskId, &taskInfo);
3936 if (taskInfo.isEnabled) {
3937 const int taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3938 const int maxLoad = (taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3939 const int averageLoad = (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3940 if (taskId != TASK_SERIAL) {
3941 maxLoadSum += maxLoad;
3942 averageLoadSum += averageLoad;
3944 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
3945 taskId, taskInfo.taskName, taskFrequency, (uint32_t)taskInfo.maxExecutionTime, (uint32_t)taskInfo.averageExecutionTime,
3946 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, (uint32_t)taskInfo.totalExecutionTime / 1000);
3949 getCheckFuncInfo(&checkFuncInfo);
3950 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo.maxExecutionTime, (uint32_t)checkFuncInfo.averageExecutionTime, (uint32_t)checkFuncInfo.totalExecutionTime / 1000);
3951 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3954 static void cliVersion(char *cmdline)
3956 UNUSED(cmdline);
3958 cliPrintLinef("# %s/%s %s %s / %s (%s) %s",
3959 FC_FIRMWARE_NAME,
3960 targetName,
3961 FC_VERSION_STRING,
3962 buildDate,
3963 buildTime,
3964 shortGitRevision,
3965 FC_VERSION_TYPE
3967 cliPrintLinef("# GCC-%s",
3968 compilerVersion
3972 static void cliMemory(char *cmdline)
3974 UNUSED(cmdline);
3975 cliPrintLinef("Dynamic memory usage:");
3976 for (unsigned i = 0; i < OWNER_TOTAL_COUNT; i++) {
3977 const char * owner = ownerNames[i];
3978 const uint32_t memUsed = memGetUsedBytesByOwner(i);
3980 if (memUsed) {
3981 cliPrintLinef("%s : %d bytes", owner, memUsed);
3986 static void cliResource(char *cmdline)
3988 UNUSED(cmdline);
3989 cliPrintLinef("IO:\r\n----------------------");
3990 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3991 const char* owner;
3992 owner = ownerNames[ioRecs[i].owner];
3994 const char* resource;
3995 resource = resourceNames[ioRecs[i].resource];
3997 if (ioRecs[i].index > 0) {
3998 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, ioRecs[i].index, resource);
3999 } else {
4000 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, resource);
4005 static void backupConfigs(void)
4007 // make copies of configs to do differencing
4008 PG_FOREACH(pg) {
4009 if (pgIsProfile(pg)) {
4010 memcpy(pg->copy, pg->address, pgSize(pg) * MAX_PROFILE_COUNT);
4011 } else {
4012 memcpy(pg->copy, pg->address, pgSize(pg));
4017 static void restoreConfigs(void)
4019 PG_FOREACH(pg) {
4020 if (pgIsProfile(pg)) {
4021 memcpy(pg->address, pg->copy, pgSize(pg) * MAX_PROFILE_COUNT);
4022 } else {
4023 memcpy(pg->address, pg->copy, pgSize(pg));
4028 static void printConfig(const char *cmdline, bool doDiff)
4030 uint8_t dumpMask = DUMP_MASTER;
4031 const char *options;
4032 if ((options = checkCommand(cmdline, "master"))) {
4033 dumpMask = DUMP_MASTER; // only
4034 } else if ((options = checkCommand(cmdline, "control_profile"))) {
4035 dumpMask = DUMP_CONTROL_PROFILE; // only
4036 } else if ((options = checkCommand(cmdline, "mixer_profile"))) {
4037 dumpMask = DUMP_MIXER_PROFILE; // only
4038 } else if ((options = checkCommand(cmdline, "battery_profile"))) {
4039 dumpMask = DUMP_BATTERY_PROFILE; // only
4040 } else if ((options = checkCommand(cmdline, "all"))) {
4041 dumpMask = DUMP_ALL; // all profiles and rates
4042 } else {
4043 options = cmdline;
4046 if (doDiff) {
4047 dumpMask = dumpMask | DO_DIFF;
4050 const int currentControlProfileIndexSave = getConfigProfile();
4051 const int currentMixerProfileIndexSave = getConfigMixerProfile();
4052 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
4053 backupConfigs();
4054 // reset all configs to defaults to do differencing
4055 resetConfigs();
4056 // restore the profile indices, since they should not be reset for proper comparison
4057 setConfigProfile(currentControlProfileIndexSave);
4058 setConfigMixerProfile(currentMixerProfileIndexSave);
4059 setConfigBatteryProfile(currentBatteryProfileIndexSave);
4061 if (checkCommand(options, "showdefaults")) {
4062 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
4065 #ifdef USE_CLI_BATCH
4066 bool batchModeEnabled = false;
4067 #endif
4069 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
4070 cliPrintHashLine("version");
4071 cliVersion(NULL);
4073 #ifdef USE_CLI_BATCH
4074 cliPrintHashLine("start the command batch");
4075 cliPrintLine("batch start");
4076 batchModeEnabled = true;
4077 #endif
4079 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
4080 #ifndef CLI_MINIMAL_VERBOSITY
4081 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
4082 #else
4083 cliPrintLinef("defaults noreboot");
4084 #endif
4087 cliPrintHashLine("resources");
4088 //printResource(dumpMask, &defaultConfig);
4090 cliPrintHashLine("Timer overrides");
4091 printTimerOutputModes(dumpMask, timerOverrides_CopyArray, timerOverrides(0), -1);
4093 // print servo parameters
4094 cliPrintHashLine("Outputs [servo]");
4095 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
4097 #if defined(USE_SAFE_HOME)
4098 cliPrintHashLine("safehome");
4099 printSafeHomes(dumpMask, safeHomeConfig_CopyArray, safeHomeConfig(0));
4100 #endif
4102 #ifdef USE_FW_AUTOLAND
4103 cliPrintHashLine("Fixed Wing Approach");
4104 printFwAutolandApproach(dumpMask, fwAutolandApproachConfig_CopyArray, fwAutolandApproachConfig(0));
4105 #endif
4107 cliPrintHashLine("features");
4108 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
4110 #if defined(BEEPER) || defined(USE_DSHOT)
4111 cliPrintHashLine("beeper");
4112 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
4113 #endif
4115 #ifdef USE_BLACKBOX
4116 cliPrintHashLine("blackbox");
4117 printBlackbox(dumpMask, &blackboxConfig_Copy, blackboxConfig());
4118 #endif
4120 cliPrintHashLine("Receiver: Channel map");
4121 printMap(dumpMask, &rxConfig_Copy, rxConfig());
4123 cliPrintHashLine("Ports");
4124 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
4126 #ifdef USE_LED_STRIP
4127 cliPrintHashLine("LEDs");
4128 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
4130 cliPrintHashLine("LED color");
4131 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
4133 cliPrintHashLine("LED mode_color");
4134 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
4135 #endif
4137 cliPrintHashLine("Modes [aux]");
4138 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
4140 cliPrintHashLine("Adjustments [adjrange]");
4141 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
4143 cliPrintHashLine("Receiver rxrange");
4144 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
4146 #ifdef USE_TEMPERATURE_SENSOR
4147 cliPrintHashLine("temp_sensor");
4148 printTempSensor(dumpMask, tempSensorConfig_CopyArray, tempSensorConfig(0));
4149 #endif
4151 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4152 cliPrintHashLine("Mission Control Waypoints [wp]");
4153 printWaypoints(dumpMask, posControl.waypointList, nonVolatileWaypointList(0));
4154 #endif
4156 #ifdef USE_OSD
4157 cliPrintHashLine("OSD [osd_layout]");
4158 printOsdLayout(dumpMask, &osdLayoutsConfig_Copy, osdLayoutsConfig(), -1, -1);
4159 #endif
4161 #ifdef USE_PROGRAMMING_FRAMEWORK
4162 cliPrintHashLine("Programming: logic");
4163 printLogic(dumpMask, logicConditions_CopyArray, logicConditions(0), -1);
4165 cliPrintHashLine("Programming: global variables");
4166 printGvar(dumpMask, globalVariableConfigs_CopyArray, globalVariableConfigs(0));
4168 cliPrintHashLine("Programming: PID controllers");
4169 printPid(dumpMask, programmingPids_CopyArray, programmingPids(0));
4170 #endif
4171 #ifdef USE_PROGRAMMING_FRAMEWORK
4172 cliPrintHashLine("OSD: custom elements");
4173 printOsdCustomElements(dumpMask, osdCustomElements_CopyArray, osdCustomElements(0));
4174 #endif
4176 cliPrintHashLine("master");
4177 dumpAllValues(MASTER_VALUE, dumpMask);
4179 if (dumpMask & DUMP_ALL) {
4180 // dump all profiles
4181 const int currentControlProfileIndexSave = getConfigProfile();
4182 const int currentMixerProfileIndexSave = getConfigMixerProfile();
4183 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
4184 for (int ii = 0; ii < MAX_PROFILE_COUNT; ++ii) {
4185 cliDumpControlProfile(ii, dumpMask);
4187 for (int ii = 0; ii < MAX_MIXER_PROFILE_COUNT; ++ii) {
4188 cliDumpMixerProfile(ii, dumpMask);
4190 for (int ii = 0; ii < MAX_BATTERY_PROFILE_COUNT; ++ii) {
4191 cliDumpBatteryProfile(ii, dumpMask);
4193 setConfigProfile(currentControlProfileIndexSave);
4194 setConfigMixerProfile(currentMixerProfileIndexSave);
4195 setConfigBatteryProfile(currentBatteryProfileIndexSave);
4197 cliPrintHashLine("restore original profile selection");
4198 cliPrintLinef("control_profile %d", currentControlProfileIndexSave + 1);
4199 cliPrintLinef("mixer_profile %d", currentMixerProfileIndexSave + 1);
4200 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave + 1);
4202 #ifdef USE_CLI_BATCH
4203 batchModeEnabled = false;
4204 #endif
4205 } else {
4206 // dump just the current profiles
4207 cliDumpControlProfile(getConfigProfile(), dumpMask);
4208 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask);
4209 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
4213 if (dumpMask & DUMP_CONTROL_PROFILE) {
4214 cliDumpControlProfile(getConfigProfile(), dumpMask);
4217 if (dumpMask & DUMP_MIXER_PROFILE) {
4218 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask);
4221 if (dumpMask & DUMP_BATTERY_PROFILE) {
4222 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
4225 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
4226 cliPrintHashLine("save configuration\r\nsave");
4229 #ifdef USE_CLI_BATCH
4230 if (batchModeEnabled) {
4231 cliPrintHashLine("end the command batch");
4232 cliPrintLine("batch end");
4234 #endif
4236 // restore configs from copies
4237 restoreConfigs();
4240 static void cliDump(char *cmdline)
4242 printConfig(cmdline, false);
4245 static void cliDiff(char *cmdline)
4247 printConfig(cmdline, true);
4250 #ifdef USE_USB_MSC
4251 static void cliMsc(char *cmdline)
4253 UNUSED(cmdline);
4255 if (false
4256 #ifdef USE_SDCARD
4257 || sdcard_isFunctional()
4258 #endif
4259 #ifdef USE_FLASHFS
4260 || flashfsGetSize() > 0
4261 #endif
4263 cliPrintHashLine("restarting in mass storage mode");
4264 cliPrint("\r\nRebooting");
4265 bufWriterFlush(cliWriter);
4266 delay(1000);
4267 waitForSerialPortToFinishTransmitting(cliPort);
4268 stopPwmAllMotors();
4269 systemResetRequest(RESET_MSC_REQUEST);
4270 } else {
4271 cliPrint("\r\nStorage not present or failed to initialize!");
4272 bufWriterFlush(cliWriter);
4275 #endif
4278 typedef struct {
4279 const char *name;
4280 #ifndef SKIP_CLI_COMMAND_HELP
4281 const char *description;
4282 const char *args;
4283 #endif
4284 void (*func)(char *cmdline);
4285 } clicmd_t;
4287 #ifndef SKIP_CLI_COMMAND_HELP
4288 #define CLI_COMMAND_DEF(name, description, args, method) \
4290 name , \
4291 description , \
4292 args , \
4293 method \
4295 #else
4296 #define CLI_COMMAND_DEF(name, description, args, method) \
4298 name, \
4299 method \
4301 #endif
4303 static void cliCmdDebug(char *arg)
4305 UNUSED(arg);
4306 if (debugMode != DEBUG_NONE) {
4307 cliPrintLinef("Debug fields: [%s (%i)]", debugMode < DEBUG_COUNT ? debugModeNames[debugMode] : "unknown", debugMode);
4308 for (int i = 0; i < DEBUG32_VALUE_COUNT; i++) {
4309 cliPrintLinef("debug[%d] = %d", i, debug[i]);
4311 } else {
4312 cliPrintLine("Debug mode is disabled");
4317 #if defined(USE_GPS) && defined(USE_GPS_PROTO_UBLOX)
4319 static const char* _ubloxGetSigId(uint8_t gnssId, uint8_t sigId)
4321 if(gnssId == 0) {
4322 switch(sigId) {
4323 case 0: return "GPS L1C/A";
4324 case 3: return "GPS L2 CL";
4325 case 4: return "GPS L2 CM";
4326 case 6: return "GPS L5 I";
4327 case 7: return "GPS L5 Q";
4328 default: return "GPS Unknown";
4330 } else if(gnssId == 1) {
4331 switch(sigId) {
4332 case 0: return "SBAS L1C/A";
4333 default: return "SBAS Unknown";
4335 } else if(gnssId == 2) {
4336 switch(sigId) {
4337 case 0: return "Galileo E1 C";
4338 case 1: return "Galileo E1 B";
4339 case 3: return "Galileo E5 al";
4340 case 4: return "Galileo E5 aQ";
4341 case 5: return "Galileo E5 bl";
4342 case 6: return "Galileo E5 bQ";
4343 default: return "Galileo Unknown";
4345 } else if(gnssId == 3) {
4346 switch(sigId) {
4347 case 0: return "BeiDou B1I D1";
4348 case 1: return "BeiDou B1I D2";
4349 case 2: return "BeiDou B2I D1";
4350 case 3: return "BeiDou B2I D2";
4351 case 5: return "BeiDou B1C";
4352 case 7: return "BeiDou B2a";
4353 default: return "BeiDou Unknown";
4355 } else if(gnssId == 5) {
4356 switch(sigId) {
4357 case 0: return "QZSS L1C/A";
4358 case 1: return "QZSS L1S";
4359 case 4: return "QZSS L2 CM";
4360 case 5: return "QZSS L2 CL";
4361 case 8: return "QZSS L5 I";
4362 case 9: return "QZSS L5 Q";
4363 default: return "QZSS Unknown";
4365 } else if(gnssId == 6) {
4366 switch(sigId) {
4367 case 0: return "GLONASS L1 OF";
4368 case 2: return "GLONASS L2 OF";
4369 default: return "GLONASS Unknown";
4373 return "Unknown GNSS/SigId";
4376 static const char *_ubloxGetQuality(uint8_t quality)
4378 switch(quality) {
4379 case UBLOX_SIG_QUALITY_NOSIGNAL: return "No signal";
4380 case UBLOX_SIG_QUALITY_SEARCHING: return "Searching signal...";
4381 case UBLOX_SIG_QUALITY_ACQUIRED: return "Signal acquired";
4382 case UBLOX_SIG_QUALITY_UNUSABLE: return "Signal detected but unusable";
4383 case UBLOX_SIG_QUALITY_CODE_LOCK_TIME_SYNC: return "Code locked and time sync";
4384 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC:
4385 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC2:
4386 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC3:
4387 return "Code and carrier locked and time sync";
4388 default: return "Unknown";
4392 static void cliUbloxPrintSatelites(char *arg)
4394 UNUSED(arg);
4395 if(!isGpsUblox() /*|| !(gpsState.flags.sig || gpsState.flags.sat)*/) {
4396 cliPrint("GPS is not UBLOX or does not report satelites.");
4397 return;
4400 cliPrintLine("UBLOX Satelites");
4402 for(int i = 0; i < UBLOX_MAX_SIGNALS; ++i)
4404 const ubx_nav_sig_info *sat = gpsGetUbloxSatelite(i);
4405 if(sat == NULL) {
4406 continue;
4409 cliPrintLinef("satelite[%d]: %d:%d", i+1, sat->gnssId, sat->svId);
4410 cliPrintLinef("sigId: %d (%s)", sat->sigId, _ubloxGetSigId(sat->gnssId, sat->sigId));
4411 cliPrintLinef("signal strength: %i dbHz", sat->cno);
4412 cliPrintLinef("quality: %i (%s)", sat->quality, _ubloxGetQuality(sat->quality));
4413 //cliPrintLinef("Correlation: %i", sat->corrSource);
4414 //cliPrintLinef("Iono model: %i", sat->ionoModel);
4415 cliPrintLinef("signal flags: 0x%02X", sat->sigFlags);
4416 switch(sat->sigFlags & UBLOX_SIG_HEALTH_MASK) {
4417 case UBLOX_SIG_HEALTH_HEALTHY:
4418 cliPrintLine("signal: Healthy");
4419 break;
4420 case UBLOX_SIG_HEALTH_UNHEALTHY:
4421 cliPrintLine("signal: Unhealthy");
4422 break;
4423 case UBLOX_SIG_HEALTH_UNKNOWN:
4424 default:
4425 cliPrintLinef("signal: Unknown (0x%X)", sat->sigFlags & UBLOX_SIG_HEALTH_MASK);
4426 break;
4428 cliPrintLinefeed();
4431 #endif
4433 static void cliHelp(char *cmdline);
4435 // should be sorted a..z for bsearch()
4436 const clicmd_t cmdTable[] = {
4437 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
4438 #if defined(USE_ASSERT)
4439 CLI_COMMAND_DEF("assert", "", NULL, cliAssert),
4440 #endif
4441 CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
4442 #ifdef USE_CLI_BATCH
4443 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
4444 #endif
4445 #if defined(BEEPER) || defined(USE_DSHOT)
4446 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
4447 "\t<+|->[name]", cliBeeper),
4448 #endif
4449 #if defined (USE_SERIALRX_SRXL2)
4450 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
4451 #endif
4452 #if defined(USE_BOOTLOG)
4453 CLI_COMMAND_DEF("bootlog", "show boot events", NULL, cliBootlog),
4454 #endif
4455 #ifdef USE_LED_STRIP
4456 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
4457 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
4458 #endif
4459 CLI_COMMAND_DEF("cli_delay", "CLI Delay", "Delay in ms", cliDelay),
4460 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
4461 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL, cliDfu),
4462 CLI_COMMAND_DEF("diff", "list configuration changes from default",
4463 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDiff),
4464 CLI_COMMAND_DEF("dump", "dump configuration",
4465 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDump),
4466 #ifdef USE_RX_ELERES
4467 CLI_COMMAND_DEF("eleres_bind", NULL, NULL, cliEleresBind),
4468 #endif // USE_RX_ELERES
4469 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
4470 CLI_COMMAND_DEF("feature", "configure features",
4471 "list\r\n"
4472 "\t<+|->[name]", cliFeature),
4473 #ifdef USE_BLACKBOX
4474 CLI_COMMAND_DEF("blackbox", "configure blackbox fields",
4475 "list\r\n"
4476 "\t<+|->[name]", cliBlackbox),
4477 #endif
4478 #ifdef USE_FLASHFS
4479 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
4480 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
4481 #ifdef USE_FLASH_TOOLS
4482 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
4483 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
4484 #endif
4485 #endif
4486 #ifdef USE_FW_AUTOLAND
4487 CLI_COMMAND_DEF("fwapproach", "Fixed Wing Approach Settings", NULL, cliFwAutolandApproach),
4488 #endif
4489 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
4490 #ifdef USE_GPS
4491 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
4492 CLI_COMMAND_DEF("gpssats", "show GPS satellites", NULL, cliUbloxPrintSatelites),
4493 #endif
4494 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
4495 #ifdef USE_LED_STRIP
4496 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
4497 CLI_COMMAND_DEF("ledpinpwm", "start/stop PWM on LED pin, 0..100 duty ratio", "[<value>]\r\n", cliLedPinPWM),
4498 #endif
4499 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
4500 CLI_COMMAND_DEF("memory", "view memory usage", NULL, cliMemory),
4501 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
4502 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
4503 #ifdef USE_USB_MSC
4504 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
4505 #endif
4506 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]\r\n", cliPlaySound),
4507 CLI_COMMAND_DEF("control_profile", "change control profile", "[<index>]", cliControlProfile),
4508 CLI_COMMAND_DEF("mixer_profile", "change mixer profile", "[<index>]", cliMixerProfile),
4509 CLI_COMMAND_DEF("battery_profile", "change battery profile", "[<index>]", cliBatteryProfile),
4510 CLI_COMMAND_DEF("resource", "view currently used resources", NULL, cliResource),
4511 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
4512 #if defined(USE_SAFE_HOME)
4513 CLI_COMMAND_DEF("safehome", "safe home list", NULL, cliSafeHomes),
4514 #endif
4515 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
4516 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
4517 #ifdef USE_SERIAL_PASSTHROUGH
4518 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough),
4519 #endif
4520 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
4521 #ifdef USE_PROGRAMMING_FRAMEWORK
4522 CLI_COMMAND_DEF("logic", "configure logic conditions",
4523 "<rule> <enabled> <activatorId> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
4524 "\treset\r\n", cliLogic),
4526 CLI_COMMAND_DEF("gvar", "configure global variables",
4527 "<gvar> <default> <min> <max>\r\n"
4528 "\treset\r\n", cliGvar),
4530 CLI_COMMAND_DEF("pid", "configurable PID controllers",
4531 "<#> <enabled> <setpoint type> <setpoint value> <measurement type> <measurement value> <P gain> <I gain> <D gain> <FF gain>\r\n"
4532 "\treset\r\n", cliPid),
4534 CLI_COMMAND_DEF("osd_custom_elements", "configurable OSD custom elements",
4535 "<#> <part0 type> <part0 value> <part1 type> <part1 value> <part2 type> <part2 value> <visibility type> <visibility value> <text>\r\n"
4536 , osdCustom),
4537 #endif
4538 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
4539 CLI_COMMAND_DEF("smix", "servo mixer",
4540 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
4541 "\treset\r\n", cliServoMix),
4542 #ifdef USE_SDCARD
4543 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
4544 #endif
4545 CLI_COMMAND_DEF("showdebug", "Show debug fields.", NULL, cliCmdDebug),
4546 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
4547 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
4548 #ifdef USE_TEMPERATURE_SENSOR
4549 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL, cliTempSensor),
4550 #endif
4551 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
4552 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4553 CLI_COMMAND_DEF("wp", "waypoint list", NULL, cliWaypoints),
4554 #endif
4555 #ifdef USE_OSD
4556 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout),
4557 #endif
4558 CLI_COMMAND_DEF("timer_output_mode", "get or set the outputmode for a given timer.", "[<timer> [<AUTO|MOTORS|SERVOS>]]", cliTimerOutputMode),
4561 static void cliHelp(char *cmdline)
4563 UNUSED(cmdline);
4565 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
4566 cliPrint(cmdTable[i].name);
4567 #ifndef SKIP_CLI_COMMAND_HELP
4568 if (cmdTable[i].description) {
4569 cliPrintf(" - %s", cmdTable[i].description);
4571 if (cmdTable[i].args) {
4572 cliPrintf("\r\n\t%s", cmdTable[i].args);
4574 #endif
4575 cliPrintLinefeed();
4579 void cliProcess(void)
4581 if (!cliWriter) {
4582 return;
4585 // Be a little bit tricky. Flush the last inputs buffer, if any.
4586 bufWriterFlush(cliWriter);
4588 while (serialRxBytesWaiting(cliPort)) {
4589 uint8_t c = serialRead(cliPort);
4590 if (c == '\t' || c == '?') {
4591 // do tab completion
4592 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
4593 uint32_t i = bufferIndex;
4594 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4595 if (bufferIndex && (sl_strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
4596 continue;
4597 if (!pstart)
4598 pstart = cmd;
4599 pend = cmd;
4601 if (pstart) { /* Buffer matches one or more commands */
4602 for (; ; bufferIndex++) {
4603 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
4604 break;
4605 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
4606 /* Unambiguous -- append a space */
4607 cliBuffer[bufferIndex++] = ' ';
4608 cliBuffer[bufferIndex] = '\0';
4609 break;
4611 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
4614 if (!bufferIndex || pstart != pend) {
4615 /* Print list of ambiguous matches */
4616 cliPrint("\r\033[K");
4617 for (cmd = pstart; cmd <= pend; cmd++) {
4618 cliPrint(cmd->name);
4619 cliWrite('\t');
4621 cliPrompt();
4622 i = 0; /* Redraw prompt */
4624 for (; i < bufferIndex; i++)
4625 cliWrite(cliBuffer[i]);
4626 } else if (!bufferIndex && c == 4) { // CTRL-D
4627 cliExit(cliBuffer);
4628 return;
4629 } else if (c == 12) { // NewPage / CTRL-L
4630 // clear screen
4631 cliPrint("\033[2J\033[1;1H");
4632 cliPrompt();
4633 } else if (bufferIndex && (c == '\n' || c == '\r')) {
4634 // enter pressed
4635 cliPrintLinefeed();
4637 // Strip comment starting with # from line
4638 char *p = cliBuffer;
4639 p = strchr(p, '#');
4640 if (NULL != p) {
4641 bufferIndex = (uint32_t)(p - cliBuffer);
4644 // Strip trailing whitespace
4645 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
4646 bufferIndex--;
4649 // Process non-empty lines
4650 if (bufferIndex > 0) {
4651 cliBuffer[bufferIndex] = 0; // null terminate
4653 const clicmd_t *cmd;
4654 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4655 if (!sl_strncasecmp(cliBuffer, cmd->name, strlen(cmd->name)) // command names match
4656 && !sl_isalnum((unsigned)cliBuffer[strlen(cmd->name)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
4657 break;
4659 if (cmd < cmdTable + ARRAYLEN(cmdTable))
4660 cmd->func(cliBuffer + strlen(cmd->name) + 1);
4661 else
4662 cliPrintError("Unknown command, try 'help'");
4663 bufferIndex = 0;
4666 ZERO_FARRAY(cliBuffer);
4668 // 'exit' will reset this flag, so we don't need to print prompt again
4669 if (!cliMode)
4670 return;
4672 cliPrompt();
4673 } else if (c == 127) {
4674 // backspace
4675 if (bufferIndex) {
4676 cliBuffer[--bufferIndex] = 0;
4677 cliPrint("\010 \010");
4679 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
4680 if (!bufferIndex && c == ' ')
4681 continue; // Ignore leading spaces
4682 cliBuffer[bufferIndex++] = c;
4683 cliWrite(c);
4688 void cliEnter(serialPort_t *serialPort)
4690 if (cliMode) {
4691 return;
4694 cliMode = true;
4695 cliPort = serialPort;
4696 setPrintfSerialPort(cliPort);
4697 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
4699 #ifndef CLI_MINIMAL_VERBOSITY
4700 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4701 #else
4702 cliPrintLine("\r\nCLI");
4703 #endif
4704 cliPrompt();
4706 #ifdef USE_CLI_BATCH
4707 resetCommandBatch();
4708 #endif
4710 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI);
4713 void cliInit(const serialConfig_t *serialConfig)
4715 UNUSED(serialConfig);