Merge pull request #10271 from MUSTARDTIGERFPV/serialpassthrough-modes
[inav.git] / src / main / fc / cli.c
blob7a3e1f1b78f24e0051cab88701b29c884b37e512
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
918 portOptions_t constructPortOptions(char *options) {
919 if (strlen(options) != 3 || options[0] != '8') {
920 // Invalid format
921 return -1;
924 portOptions_t result = 0;
926 switch (options[1]) {
927 case 'N':
928 result |= SERIAL_PARITY_NO;
929 break;
930 case 'E':
931 result |= SERIAL_PARITY_EVEN;
932 break;
933 default:
934 // Invalid format
935 return -1;
938 switch (options[2]) {
939 case '1':
940 result |= SERIAL_STOPBITS_1;
941 break;
942 case '2':
943 result |= SERIAL_STOPBITS_2;
944 break;
945 default:
946 // Invalid format
947 return -1;
950 return result;
953 static void cliSerialPassthrough(char *cmdline)
955 char * saveptr;
957 if (isEmpty(cmdline)) {
958 cliShowParseError();
959 return;
962 int id = -1;
963 uint32_t baud = 0;
964 unsigned mode = 0;
965 portOptions_t options = SERIAL_NOT_INVERTED;
966 char* tok = strtok_r(cmdline, " ", &saveptr);
967 int index = 0;
969 while (tok != NULL) {
970 switch (index) {
971 case 0:
972 id = fastA2I(tok);
973 break;
974 case 1:
975 baud = fastA2I(tok);
976 break;
977 case 2:
978 if (strstr(tok, "rx") || strstr(tok, "RX"))
979 mode |= MODE_RX;
980 if (strstr(tok, "tx") || strstr(tok, "TX"))
981 mode |= MODE_TX;
982 break;
983 case 3:
984 options |= constructPortOptions(tok);
985 break;
987 index++;
988 tok = strtok_r(NULL, " ", &saveptr);
991 serialPort_t *passThroughPort;
992 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
993 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
994 if (!baud) {
995 tfp_printf("Port %d is closed, must specify baud.\r\n", id);
996 return;
998 if (!mode)
999 mode = MODE_RXTX;
1001 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
1002 baud, mode,
1003 options);
1004 if (!passThroughPort) {
1005 tfp_printf("Port %d could not be opened.\r\n", id);
1006 return;
1008 tfp_printf("Port %d opened, baud = %u.\r\n", id, (unsigned)baud);
1009 } else {
1010 passThroughPort = passThroughPortUsage->serialPort;
1011 // If the user supplied a mode, override the port's mode, otherwise
1012 // leave the mode unchanged. serialPassthrough() handles one-way ports.
1013 tfp_printf("Port %d already open.\r\n", id);
1014 if (mode && passThroughPort->mode != mode) {
1015 tfp_printf("Adjusting mode from %d to %d.\r\n",
1016 passThroughPort->mode, mode);
1017 serialSetMode(passThroughPort, mode);
1019 if (options && passThroughPort->options != options) {
1020 tfp_printf("Adjusting options from %d to %d.\r\n",
1021 passThroughPort->options, options);
1022 serialSetOptions(passThroughPort, options);
1024 // If this port has a rx callback associated we need to remove it now.
1025 // Otherwise no data will be pushed in the serial port buffer!
1026 if (passThroughPort->rxCallback) {
1027 tfp_printf("Removing rxCallback\r\n");
1028 passThroughPort->rxCallback = 0;
1032 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id);
1034 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
1036 #endif
1038 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
1040 const char *format = "adjrange %u %u %u %u %u %u %u";
1041 // print out adjustment ranges channel settings
1042 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1043 const adjustmentRange_t *ar = &adjustmentRanges[i];
1044 bool equalsDefault = false;
1045 if (defaultAdjustmentRanges) {
1046 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
1047 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
1048 && ar->range.startStep == arDefault->range.startStep
1049 && ar->range.endStep == arDefault->range.endStep
1050 && ar->adjustmentFunction == arDefault->adjustmentFunction
1051 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
1052 && ar->adjustmentIndex == arDefault->adjustmentIndex;
1053 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1055 arDefault->adjustmentIndex,
1056 arDefault->auxChannelIndex,
1057 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1058 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1059 arDefault->adjustmentFunction,
1060 arDefault->auxSwitchChannelIndex
1063 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1065 ar->adjustmentIndex,
1066 ar->auxChannelIndex,
1067 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1068 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1069 ar->adjustmentFunction,
1070 ar->auxSwitchChannelIndex
1075 static void cliAdjustmentRange(char *cmdline)
1077 int i, val = 0;
1078 const char *ptr;
1080 if (isEmpty(cmdline)) {
1081 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
1082 } else {
1083 ptr = cmdline;
1084 i = fastA2I(ptr++);
1085 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1086 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1087 uint8_t validArgumentCount = 0;
1089 ptr = nextArg(ptr);
1090 if (ptr) {
1091 val = fastA2I(ptr);
1092 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1093 ar->adjustmentIndex = val;
1094 validArgumentCount++;
1097 ptr = nextArg(ptr);
1098 if (ptr) {
1099 val = fastA2I(ptr);
1100 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1101 ar->auxChannelIndex = val;
1102 validArgumentCount++;
1106 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1108 ptr = nextArg(ptr);
1109 if (ptr) {
1110 val = fastA2I(ptr);
1111 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1112 ar->adjustmentFunction = val;
1113 validArgumentCount++;
1116 ptr = nextArg(ptr);
1117 if (ptr) {
1118 val = fastA2I(ptr);
1119 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1120 ar->auxSwitchChannelIndex = val;
1121 validArgumentCount++;
1125 if (validArgumentCount != 6) {
1126 memset(ar, 0, sizeof(adjustmentRange_t));
1127 cliShowParseError();
1129 } else {
1130 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1135 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *primaryMotorMixer, const motorMixer_t *defaultprimaryMotorMixer)
1137 const char *format = "mmix %d %s %s %s %s";
1138 char buf0[FTOA_BUFFER_SIZE];
1139 char buf1[FTOA_BUFFER_SIZE];
1140 char buf2[FTOA_BUFFER_SIZE];
1141 char buf3[FTOA_BUFFER_SIZE];
1142 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1143 if (primaryMotorMixer[i].throttle == 0.0f)
1144 break;
1145 const float thr = primaryMotorMixer[i].throttle;
1146 const float roll = primaryMotorMixer[i].roll;
1147 const float pitch = primaryMotorMixer[i].pitch;
1148 const float yaw = primaryMotorMixer[i].yaw;
1149 bool equalsDefault = false;
1150 if (defaultprimaryMotorMixer) {
1151 const float thrDefault = defaultprimaryMotorMixer[i].throttle;
1152 const float rollDefault = defaultprimaryMotorMixer[i].roll;
1153 const float pitchDefault = defaultprimaryMotorMixer[i].pitch;
1154 const float yawDefault = defaultprimaryMotorMixer[i].yaw;
1155 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1157 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1159 ftoa(thrDefault, buf0),
1160 ftoa(rollDefault, buf1),
1161 ftoa(pitchDefault, buf2),
1162 ftoa(yawDefault, buf3));
1164 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1166 ftoa(thr, buf0),
1167 ftoa(roll, buf1),
1168 ftoa(pitch, buf2),
1169 ftoa(yaw, buf3));
1173 static void cliMotorMix(char *cmdline)
1175 int check = 0;
1176 const char *ptr;
1178 if (isEmpty(cmdline)) {
1179 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1180 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1181 // erase custom mixer
1182 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1183 primaryMotorMixerMutable(i)->throttle = 0.0f;
1185 } else {
1186 ptr = cmdline;
1187 uint32_t i = fastA2I(ptr); // get motor number
1188 if (i < MAX_SUPPORTED_MOTORS) {
1189 ptr = nextArg(ptr);
1190 if (ptr) {
1191 primaryMotorMixerMutable(i)->throttle = fastA2F(ptr);
1192 check++;
1194 ptr = nextArg(ptr);
1195 if (ptr) {
1196 primaryMotorMixerMutable(i)->roll = fastA2F(ptr);
1197 check++;
1199 ptr = nextArg(ptr);
1200 if (ptr) {
1201 primaryMotorMixerMutable(i)->pitch = fastA2F(ptr);
1202 check++;
1204 ptr = nextArg(ptr);
1205 if (ptr) {
1206 primaryMotorMixerMutable(i)->yaw = fastA2F(ptr);
1207 check++;
1209 if (check != 4) {
1210 cliShowParseError();
1211 } else {
1212 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1214 } else {
1215 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1220 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1222 const char *format = "rxrange %u %u %u";
1223 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1224 bool equalsDefault = false;
1225 if (defaultChannelRangeConfigs) {
1226 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1227 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1228 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1230 defaultChannelRangeConfigs[i].min,
1231 defaultChannelRangeConfigs[i].max
1234 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1236 channelRangeConfigs[i].min,
1237 channelRangeConfigs[i].max
1242 static void cliRxRange(char *cmdline)
1244 int i, validArgumentCount = 0;
1245 const char *ptr;
1247 if (isEmpty(cmdline)) {
1248 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1249 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1250 resetAllRxChannelRangeConfigurations();
1251 } else {
1252 ptr = cmdline;
1253 i = fastA2I(ptr);
1254 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1255 int rangeMin = 0, rangeMax = 0;
1257 ptr = nextArg(ptr);
1258 if (ptr) {
1259 rangeMin = fastA2I(ptr);
1260 validArgumentCount++;
1263 ptr = nextArg(ptr);
1264 if (ptr) {
1265 rangeMax = fastA2I(ptr);
1266 validArgumentCount++;
1269 if (validArgumentCount != 2) {
1270 cliShowParseError();
1271 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1272 cliShowParseError();
1273 } else {
1274 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1275 channelRangeConfig->min = rangeMin;
1276 channelRangeConfig->max = rangeMax;
1278 } else {
1279 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1284 #ifdef USE_TEMPERATURE_SENSOR
1285 static void printTempSensor(uint8_t dumpMask, const tempSensorConfig_t *tempSensorConfigs, const tempSensorConfig_t *defaultTempSensorConfigs)
1287 const char *format = "temp_sensor %u %u %s %d %d %u %s";
1288 for (uint8_t i = 0; i < MAX_TEMP_SENSORS; i++) {
1289 bool equalsDefault = false;
1290 char label[5], hex_address[17];
1291 strncpy(label, tempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN);
1292 label[4] = '\0';
1293 tempSensorAddressToString(tempSensorConfigs[i].address, hex_address);
1294 if (defaultTempSensorConfigs) {
1295 equalsDefault = tempSensorConfigs[i].type == defaultTempSensorConfigs[i].type
1296 && tempSensorConfigs[i].address == defaultTempSensorConfigs[i].address
1297 && tempSensorConfigs[i].osdSymbol == defaultTempSensorConfigs[i].osdSymbol
1298 && !memcmp(tempSensorConfigs[i].label, defaultTempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN)
1299 && tempSensorConfigs[i].alarm_min == defaultTempSensorConfigs[i].alarm_min
1300 && tempSensorConfigs[i].alarm_max == defaultTempSensorConfigs[i].alarm_max;
1301 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1303 defaultTempSensorConfigs[i].type,
1304 "0",
1305 defaultTempSensorConfigs[i].alarm_min,
1306 defaultTempSensorConfigs[i].alarm_max,
1311 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1313 tempSensorConfigs[i].type,
1314 hex_address,
1315 tempSensorConfigs[i].alarm_min,
1316 tempSensorConfigs[i].alarm_max,
1317 tempSensorConfigs[i].osdSymbol,
1318 label
1323 static void cliTempSensor(char *cmdline)
1325 if (isEmpty(cmdline)) {
1326 printTempSensor(DUMP_MASTER, tempSensorConfig(0), NULL);
1327 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1328 resetTempSensorConfig();
1329 } else {
1330 int16_t i;
1331 const char *ptr = cmdline, *label;
1332 int16_t type=0, alarm_min=0, alarm_max=0;
1333 bool addressValid = false;
1334 uint64_t address;
1335 int8_t osdSymbol=0;
1336 uint8_t validArgumentCount = 0;
1337 i = fastA2I(ptr);
1338 if (i >= 0 && i < MAX_TEMP_SENSORS) {
1340 ptr = nextArg(ptr);
1341 if (ptr) {
1342 type = fastA2I(ptr);
1343 validArgumentCount++;
1346 ptr = nextArg(ptr);
1347 if (ptr) {
1348 addressValid = tempSensorStringToAddress(ptr, &address);
1349 validArgumentCount++;
1352 ptr = nextArg(ptr);
1353 if (ptr) {
1354 alarm_min = fastA2I(ptr);
1355 validArgumentCount++;
1358 ptr = nextArg(ptr);
1359 if (ptr) {
1360 alarm_max = fastA2I(ptr);
1361 validArgumentCount++;
1364 ptr = nextArg(ptr);
1365 if (ptr) {
1366 osdSymbol = fastA2I(ptr);
1367 validArgumentCount++;
1370 label = nextArg(ptr);
1371 if (label)
1372 ++validArgumentCount;
1373 else
1374 label = "";
1376 if (validArgumentCount < 4) {
1377 cliShowParseError();
1378 } 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) {
1379 cliShowParseError();
1380 } else {
1381 tempSensorConfig_t *sensorConfig = tempSensorConfigMutable(i);
1382 sensorConfig->type = type;
1383 sensorConfig->address = address;
1384 sensorConfig->alarm_min = alarm_min;
1385 sensorConfig->alarm_max = alarm_max;
1386 sensorConfig->osdSymbol = osdSymbol;
1387 for (uint8_t index = 0; index < TEMPERATURE_LABEL_LEN; ++index) {
1388 sensorConfig->label[index] = toupper(label[index]);
1389 if (label[index] == '\0') break;
1392 } else {
1393 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS - 1);
1397 #endif
1399 #ifdef USE_FW_AUTOLAND
1400 static void printFwAutolandApproach(uint8_t dumpMask, const navFwAutolandApproach_t *navFwAutolandApproach, const navFwAutolandApproach_t *defaultFwAutolandApproach)
1402 const char *format = "fwapproach %u %d %d %u %d %d %u";
1403 for (uint8_t i = 0; i < MAX_FW_LAND_APPOACH_SETTINGS; i++) {
1404 bool equalsDefault = false;
1405 if (defaultFwAutolandApproach) {
1406 equalsDefault = navFwAutolandApproach[i].approachDirection == defaultFwAutolandApproach[i].approachDirection
1407 && navFwAutolandApproach[i].approachAlt == defaultFwAutolandApproach[i].approachAlt
1408 && navFwAutolandApproach[i].landAlt == defaultFwAutolandApproach[i].landAlt
1409 && navFwAutolandApproach[i].landApproachHeading1 == defaultFwAutolandApproach[i].landApproachHeading1
1410 && navFwAutolandApproach[i].landApproachHeading2 == defaultFwAutolandApproach[i].landApproachHeading2
1411 && navFwAutolandApproach[i].isSeaLevelRef == defaultFwAutolandApproach[i].isSeaLevelRef;
1412 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,
1413 defaultFwAutolandApproach[i].approachAlt, defaultFwAutolandApproach[i].landAlt, defaultFwAutolandApproach[i].approachDirection, defaultFwAutolandApproach[i].landApproachHeading1, defaultFwAutolandApproach[i].landApproachHeading2, defaultFwAutolandApproach[i].isSeaLevelRef);
1415 cliDumpPrintLinef(dumpMask, equalsDefault, format, i,
1416 navFwAutolandApproach[i].approachAlt, navFwAutolandApproach[i].landAlt, navFwAutolandApproach[i].approachDirection, navFwAutolandApproach[i].landApproachHeading1, navFwAutolandApproach[i].landApproachHeading2, navFwAutolandApproach[i].isSeaLevelRef);
1420 static void cliFwAutolandApproach(char * cmdline)
1422 if (isEmpty(cmdline)) {
1423 printFwAutolandApproach(DUMP_MASTER, fwAutolandApproachConfig(0), NULL);
1424 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1425 resetFwAutolandApproach(-1);
1426 } else {
1427 int32_t approachAlt = 0, heading1 = 0, heading2 = 0, landDirection = 0, landAlt = 0;
1428 bool isSeaLevelRef = false;
1429 uint8_t validArgumentCount = 0;
1430 const char *ptr = cmdline;
1431 int8_t i = fastA2I(ptr);
1432 if (i < 0 || i >= MAX_FW_LAND_APPOACH_SETTINGS) {
1433 cliShowArgumentRangeError("fwapproach index", 0, MAX_FW_LAND_APPOACH_SETTINGS - 1);
1434 } else {
1435 if ((ptr = nextArg(ptr))) {
1436 approachAlt = fastA2I(ptr);
1437 validArgumentCount++;
1440 if ((ptr = nextArg(ptr))) {
1441 landAlt = fastA2I(ptr);
1442 validArgumentCount++;
1445 if ((ptr = nextArg(ptr))) {
1446 landDirection = fastA2I(ptr);
1448 if (landDirection != 0 && landDirection != 1) {
1449 cliShowParseError();
1450 return;
1453 validArgumentCount++;
1456 if ((ptr = nextArg(ptr))) {
1457 heading1 = fastA2I(ptr);
1459 if (heading1 < -360 || heading1 > 360) {
1460 cliShowParseError();
1461 return;
1464 validArgumentCount++;
1467 if ((ptr = nextArg(ptr))) {
1468 heading2 = fastA2I(ptr);
1470 if (heading2 < -360 || heading2 > 360) {
1471 cliShowParseError();
1472 return;
1475 validArgumentCount++;
1478 if ((ptr = nextArg(ptr))) {
1479 isSeaLevelRef = fastA2I(ptr);
1480 validArgumentCount++;
1483 if ((ptr = nextArg(ptr))) {
1484 // check for too many arguments
1485 validArgumentCount++;
1488 if (validArgumentCount != 6) {
1489 cliShowParseError();
1490 } else {
1491 fwAutolandApproachConfigMutable(i)->approachAlt = approachAlt;
1492 fwAutolandApproachConfigMutable(i)->landAlt = landAlt;
1493 fwAutolandApproachConfigMutable(i)->approachDirection = (fwAutolandApproachDirection_e)landDirection;
1494 fwAutolandApproachConfigMutable(i)->landApproachHeading1 = (int16_t)heading1;
1495 fwAutolandApproachConfigMutable(i)->landApproachHeading2 = (int16_t)heading2;
1496 fwAutolandApproachConfigMutable(i)->isSeaLevelRef = isSeaLevelRef;
1501 #endif
1503 #if defined(USE_SAFE_HOME)
1504 static void printSafeHomes(uint8_t dumpMask, const navSafeHome_t *navSafeHome, const navSafeHome_t *defaultSafeHome)
1506 const char *format = "safehome %u %u %d %d"; // uint8_t enabled, int32_t lat; int32_t lon
1507 for (uint8_t i = 0; i < MAX_SAFE_HOMES; i++) {
1508 bool equalsDefault = false;
1509 if (defaultSafeHome) {
1510 equalsDefault = navSafeHome[i].enabled == defaultSafeHome[i].enabled
1511 && navSafeHome[i].lat == defaultSafeHome[i].lat
1512 && navSafeHome[i].lon == defaultSafeHome[i].lon;
1513 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,
1514 defaultSafeHome[i].enabled, defaultSafeHome[i].lat, defaultSafeHome[i].lon);
1516 cliDumpPrintLinef(dumpMask, equalsDefault, format, i,
1517 navSafeHome[i].enabled, navSafeHome[i].lat, navSafeHome[i].lon);
1521 static void cliSafeHomes(char *cmdline)
1523 if (isEmpty(cmdline)) {
1524 printSafeHomes(DUMP_MASTER, safeHomeConfig(0), NULL);
1525 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1526 resetSafeHomes();
1527 } else {
1528 int32_t lat=0, lon=0;
1529 bool enabled=false;
1530 uint8_t validArgumentCount = 0;
1531 const char *ptr = cmdline;
1532 int8_t i = fastA2I(ptr);
1533 if (i < 0 || i >= MAX_SAFE_HOMES) {
1534 cliShowArgumentRangeError("safehome index", 0, MAX_SAFE_HOMES - 1);
1535 } else {
1536 if ((ptr = nextArg(ptr))) {
1537 enabled = fastA2I(ptr);
1538 validArgumentCount++;
1540 if ((ptr = nextArg(ptr))) {
1541 lat = fastA2I(ptr);
1542 validArgumentCount++;
1544 if ((ptr = nextArg(ptr))) {
1545 lon = fastA2I(ptr);
1546 validArgumentCount++;
1548 if ((ptr = nextArg(ptr))) {
1549 // check for too many arguments
1550 validArgumentCount++;
1552 if (validArgumentCount != 3) {
1553 cliShowParseError();
1554 } else {
1555 safeHomeConfigMutable(i)->enabled = enabled;
1556 safeHomeConfigMutable(i)->lat = lat;
1557 safeHomeConfigMutable(i)->lon = lon;
1563 #endif
1564 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1565 static void printWaypoints(uint8_t dumpMask, const navWaypoint_t *navWaypoint, const navWaypoint_t *defaultNavWaypoint)
1567 cliPrintLinef("#wp %d %svalid", posControl.waypointCount, posControl.waypointListValid ? "" : "in"); //int8_t bool
1568 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
1569 for (uint8_t i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1570 bool equalsDefault = false;
1571 if (defaultNavWaypoint) {
1572 equalsDefault = navWaypoint[i].action == defaultNavWaypoint[i].action
1573 && navWaypoint[i].lat == defaultNavWaypoint[i].lat
1574 && navWaypoint[i].lon == defaultNavWaypoint[i].lon
1575 && navWaypoint[i].alt == defaultNavWaypoint[i].alt
1576 && navWaypoint[i].p1 == defaultNavWaypoint[i].p1
1577 && navWaypoint[i].p2 == defaultNavWaypoint[i].p2
1578 && navWaypoint[i].p3 == defaultNavWaypoint[i].p3
1579 && navWaypoint[i].flag == defaultNavWaypoint[i].flag;
1580 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1582 defaultNavWaypoint[i].action,
1583 defaultNavWaypoint[i].lat,
1584 defaultNavWaypoint[i].lon,
1585 defaultNavWaypoint[i].alt,
1586 defaultNavWaypoint[i].p1,
1587 defaultNavWaypoint[i].p2,
1588 defaultNavWaypoint[i].p3,
1589 defaultNavWaypoint[i].flag
1592 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1594 navWaypoint[i].action,
1595 navWaypoint[i].lat,
1596 navWaypoint[i].lon,
1597 navWaypoint[i].alt,
1598 navWaypoint[i].p1,
1599 navWaypoint[i].p2,
1600 navWaypoint[i].p3,
1601 navWaypoint[i].flag
1606 static void cliWaypoints(char *cmdline)
1608 #ifdef USE_MULTI_MISSION
1609 static int8_t multiMissionWPCounter = 0;
1610 #endif
1611 if (isEmpty(cmdline)) {
1612 printWaypoints(DUMP_MASTER, posControl.waypointList, NULL);
1613 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1614 resetWaypointList();
1615 } else if (sl_strcasecmp(cmdline, "load") == 0) {
1616 loadNonVolatileWaypointList(true);
1617 } else if (sl_strcasecmp(cmdline, "save") == 0) {
1618 posControl.waypointListValid = false;
1619 for (int i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1620 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;
1621 if (posControl.waypointList[i].flag == NAV_WP_FLAG_LAST) {
1622 #ifdef USE_MULTI_MISSION
1623 if (posControl.multiMissionCount == 1) {
1624 posControl.waypointCount = i + 1;
1625 posControl.waypointListValid = true;
1626 multiMissionWPCounter = 0;
1627 posControl.multiMissionCount = 0;
1628 break;
1629 } else {
1630 posControl.multiMissionCount -= 1;
1632 #else
1633 posControl.waypointCount = i + 1;
1634 posControl.waypointListValid = true;
1635 break;
1636 #endif
1639 if (posControl.waypointListValid) {
1640 saveNonVolatileWaypointList();
1641 } else {
1642 cliShowParseError();
1644 } else {
1645 int16_t i, p1=0,p2=0,p3=0,tmp=0;
1646 uint8_t action=0, flag=0;
1647 int32_t lat=0, lon=0, alt=0;
1648 uint8_t validArgumentCount = 0;
1649 const char *ptr = cmdline;
1650 i = fastA2I(ptr);
1651 #ifdef USE_MULTI_MISSION
1652 if (i + multiMissionWPCounter >= 0 && i + multiMissionWPCounter < NAV_MAX_WAYPOINTS) {
1653 #else
1654 if (i >= 0 && i < NAV_MAX_WAYPOINTS) {
1655 #endif
1656 ptr = nextArg(ptr);
1657 if (ptr) {
1658 action = fastA2I(ptr);
1659 validArgumentCount++;
1661 ptr = nextArg(ptr);
1662 if (ptr) {
1663 lat = fastA2I(ptr);
1664 validArgumentCount++;
1666 ptr = nextArg(ptr);
1667 if (ptr) {
1668 lon = fastA2I(ptr);
1669 validArgumentCount++;
1671 ptr = nextArg(ptr);
1672 if (ptr) {
1673 alt = fastA2I(ptr);
1674 validArgumentCount++;
1676 ptr = nextArg(ptr);
1677 if (ptr) {
1678 p1 = fastA2I(ptr);
1679 validArgumentCount++;
1681 ptr = nextArg(ptr);
1682 if (ptr) {
1683 tmp = fastA2I(ptr);
1684 validArgumentCount++;
1686 /* We support pre-2.5 6 values (... p1,flags) or
1687 * 2.5 and later, 8 values (... p1,p2,p3,flags)
1689 ptr = nextArg(ptr);
1690 if (ptr) {
1691 p2 = tmp;
1692 p3 = fastA2I(ptr);
1693 validArgumentCount++;
1694 ptr = nextArg(ptr);
1695 if (ptr) {
1696 flag = fastA2I(ptr);
1697 validArgumentCount++;
1699 } else {
1700 flag = tmp;
1703 if (!(validArgumentCount == 6 || validArgumentCount == 8)) {
1704 cliShowParseError();
1705 } 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)) {
1706 cliShowParseError();
1707 } else {
1708 #ifdef USE_MULTI_MISSION
1709 if (i + multiMissionWPCounter == 0) {
1710 posControl.multiMissionCount = 0;
1713 posControl.waypointList[i + multiMissionWPCounter].action = action;
1714 posControl.waypointList[i + multiMissionWPCounter].lat = lat;
1715 posControl.waypointList[i + multiMissionWPCounter].lon = lon;
1716 posControl.waypointList[i + multiMissionWPCounter].alt = alt;
1717 posControl.waypointList[i + multiMissionWPCounter].p1 = p1;
1718 posControl.waypointList[i + multiMissionWPCounter].p2 = p2;
1719 posControl.waypointList[i + multiMissionWPCounter].p3 = p3;
1720 posControl.waypointList[i + multiMissionWPCounter].flag = flag;
1722 // Process WP entries made up of multiple successive WP missions (multiple NAV_WP_FLAG_LAST entries)
1723 // Individial missions loaded at runtime, mission selected nav_waypoint_multi_mission_index
1724 if (flag == NAV_WP_FLAG_LAST) {
1725 multiMissionWPCounter += i + 1;
1726 posControl.multiMissionCount += 1;
1728 #else
1729 posControl.waypointList[i].action = action;
1730 posControl.waypointList[i].lat = lat;
1731 posControl.waypointList[i].lon = lon;
1732 posControl.waypointList[i].alt = alt;
1733 posControl.waypointList[i].p1 = p1;
1734 posControl.waypointList[i].p2 = p2;
1735 posControl.waypointList[i].p3 = p3;
1736 posControl.waypointList[i].flag = flag;
1737 #endif
1739 } else {
1740 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS - 1);
1745 #endif
1747 #ifdef USE_LED_STRIP
1748 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1750 const char *format = "led %u %s";
1751 char ledConfigBuffer[20];
1752 char ledConfigDefaultBuffer[20];
1753 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1754 ledConfig_t ledConfig = ledConfigs[i];
1755 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1756 bool equalsDefault = false;
1757 if (defaultLedConfigs) {
1758 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1759 equalsDefault = !memcmp(&ledConfig, &ledConfigDefault, sizeof(ledConfig_t));
1760 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1761 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1763 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1767 static void cliLed(char *cmdline)
1769 int i;
1770 const char *ptr;
1772 if (isEmpty(cmdline)) {
1773 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1774 } else {
1775 ptr = cmdline;
1776 i = fastA2I(ptr);
1777 if (i < LED_MAX_STRIP_LENGTH) {
1778 ptr = nextArg(cmdline);
1779 if (!parseLedStripConfig(i, ptr)) {
1780 cliShowParseError();
1782 } else {
1783 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1788 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1790 const char *format = "color %u %d,%u,%u";
1791 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1792 const hsvColor_t *color = &colors[i];
1793 bool equalsDefault = false;
1794 if (defaultColors) {
1795 const hsvColor_t *colorDefault = &defaultColors[i];
1796 equalsDefault = color->h == colorDefault->h
1797 && color->s == colorDefault->s
1798 && color->v == colorDefault->v;
1799 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1801 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1805 static void cliColor(char *cmdline)
1807 if (isEmpty(cmdline)) {
1808 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1809 } else {
1810 const char *ptr = cmdline;
1811 const int i = fastA2I(ptr);
1812 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1813 ptr = nextArg(cmdline);
1814 if (!parseColor(i, ptr)) {
1815 cliShowParseError();
1817 } else {
1818 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1823 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1825 const char *format = "mode_color %u %u %u";
1826 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1827 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1828 int colorIndex = ledStripConfig->modeColors[i].color[j];
1829 bool equalsDefault = false;
1830 if (defaultLedStripConfig) {
1831 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1832 equalsDefault = colorIndex == colorIndexDefault;
1833 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1835 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1839 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1840 const int colorIndex = ledStripConfig->specialColors.color[j];
1841 bool equalsDefault = false;
1842 if (defaultLedStripConfig) {
1843 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1844 equalsDefault = colorIndex == colorIndexDefault;
1845 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1847 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1851 static void cliModeColor(char *cmdline)
1853 char * saveptr;
1855 if (isEmpty(cmdline)) {
1856 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1857 } else {
1858 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1859 int args[ARGS_COUNT];
1860 int argNo = 0;
1861 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1862 while (ptr && argNo < ARGS_COUNT) {
1863 args[argNo++] = fastA2I(ptr);
1864 ptr = strtok_r(NULL, " ", &saveptr);
1867 if (ptr != NULL || argNo != ARGS_COUNT) {
1868 cliShowParseError();
1869 return;
1872 int modeIdx = args[MODE];
1873 int funIdx = args[FUNCTION];
1874 int color = args[COLOR];
1875 if (!setModeColor(modeIdx, funIdx, color)) {
1876 cliShowParseError();
1877 return;
1879 // values are validated
1880 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1884 static void cliLedPinPWM(char *cmdline)
1886 int i;
1888 if (isEmpty(cmdline)) {
1889 ledPinStopPWM();
1890 cliPrintLine("PWM stopped");
1891 } else {
1892 i = fastA2I(cmdline);
1893 ledPinStartPWM(i);
1894 cliPrintLinef("PWM started: %d%%",i);
1897 #endif
1899 static void cliDelay(char* cmdLine) {
1900 int ms = 0;
1901 if (isEmpty(cmdLine)) {
1902 cliDelayMs = 0;
1903 cliPrintLine("CLI delay deactivated");
1904 return;
1907 ms = fastA2I(cmdLine);
1908 if (ms) {
1909 cliDelayMs = ms;
1910 cliPrintLinef("CLI delay set to %d ms", ms);
1912 } else {
1913 cliShowParseError();
1918 static void printServo(uint8_t dumpMask, const servoParam_t *servoParam, const servoParam_t *defaultServoParam)
1920 // print out servo settings
1921 const char *format = "servo %u %d %d %d %d";
1922 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1923 const servoParam_t *servoConf = &servoParam[i];
1924 bool equalsDefault = false;
1925 if (defaultServoParam) {
1926 const servoParam_t *servoConfDefault = &defaultServoParam[i];
1927 equalsDefault = servoConf->min == servoConfDefault->min
1928 && servoConf->max == servoConfDefault->max
1929 && servoConf->middle == servoConfDefault->middle
1930 && servoConf->rate == servoConfDefault->rate;
1931 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1933 servoConfDefault->min,
1934 servoConfDefault->max,
1935 servoConfDefault->middle,
1936 servoConfDefault->rate
1939 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1941 servoConf->min,
1942 servoConf->max,
1943 servoConf->middle,
1944 servoConf->rate
1949 static void cliServo(char *cmdline)
1951 enum { SERVO_ARGUMENT_COUNT = 5 };
1952 int16_t arguments[SERVO_ARGUMENT_COUNT];
1954 servoParam_t *servo;
1956 int i;
1957 const char *ptr;
1959 if (isEmpty(cmdline)) {
1960 printServo(DUMP_MASTER, servoParams(0), NULL);
1961 } else {
1962 int validArgumentCount = 0;
1964 ptr = cmdline;
1966 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1968 // If command line doesn't fit the format, don't modify the config
1969 while (*ptr) {
1970 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1971 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1972 cliShowParseError();
1973 return;
1976 arguments[validArgumentCount++] = fastA2I(ptr);
1978 do {
1979 ptr++;
1980 } while (*ptr >= '0' && *ptr <= '9');
1981 } else if (*ptr == ' ') {
1982 ptr++;
1983 } else {
1984 cliShowParseError();
1985 return;
1989 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE};
1991 i = arguments[INDEX];
1993 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1994 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1995 cliShowParseError();
1996 return;
1999 servo = servoParamsMutable(i);
2001 if (
2002 arguments[MIN] < SERVO_OUTPUT_MIN || arguments[MIN] > SERVO_OUTPUT_MAX ||
2003 arguments[MAX] < SERVO_OUTPUT_MIN || arguments[MAX] > SERVO_OUTPUT_MAX ||
2004 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
2005 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
2006 arguments[RATE] < -125 || arguments[RATE] > 125
2008 cliShowParseError();
2009 return;
2012 servo->min = arguments[MIN];
2013 servo->max = arguments[MAX];
2014 servo->middle = arguments[MIDDLE];
2015 servo->rate = arguments[RATE];
2019 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
2021 const char *format = "smix %d %d %d %d %d %d";
2022 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
2023 const servoMixer_t customServoMixer = customServoMixers[i];
2024 if (customServoMixer.rate == 0) {
2025 break;
2028 bool equalsDefault = false;
2029 if (defaultCustomServoMixers) {
2030 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
2031 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
2032 && customServoMixer.inputSource == customServoMixerDefault.inputSource
2033 && customServoMixer.rate == customServoMixerDefault.rate
2034 && customServoMixer.speed == customServoMixerDefault.speed
2035 #ifdef USE_PROGRAMMING_FRAMEWORK
2036 && customServoMixer.conditionId == customServoMixerDefault.conditionId
2037 #endif
2040 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2042 customServoMixerDefault.targetChannel,
2043 customServoMixerDefault.inputSource,
2044 customServoMixerDefault.rate,
2045 customServoMixerDefault.speed,
2046 #ifdef USE_PROGRAMMING_FRAMEWORK
2047 customServoMixer.conditionId
2048 #else
2050 #endif
2053 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2055 customServoMixer.targetChannel,
2056 customServoMixer.inputSource,
2057 customServoMixer.rate,
2058 customServoMixer.speed,
2059 #ifdef USE_PROGRAMMING_FRAMEWORK
2060 customServoMixer.conditionId
2061 #else
2063 #endif
2068 static void cliServoMix(char *cmdline)
2070 char * saveptr;
2071 int args[6], check = 0;
2072 uint8_t len = strlen(cmdline);
2074 if (len == 0) {
2075 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
2076 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2077 // erase custom mixer
2078 Reset_servoMixers(customServoMixersMutable(0));
2079 } else {
2080 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, CONDITION, ARGS_COUNT};
2081 char *ptr = strtok_r(cmdline, " ", &saveptr);
2082 args[CONDITION] = -1;
2083 while (ptr != NULL && check < ARGS_COUNT) {
2084 args[check++] = fastA2I(ptr);
2085 ptr = strtok_r(NULL, " ", &saveptr);
2088 if (ptr != NULL || (check < ARGS_COUNT - 1)) {
2089 cliShowParseError();
2090 return;
2093 int32_t i = args[RULE];
2094 if (
2095 i >= 0 && i < MAX_SERVO_RULES &&
2096 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2097 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2098 args[RATE] >= -1000 && args[RATE] <= 1000 &&
2099 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2100 args[CONDITION] >= -1 && args[CONDITION] < MAX_LOGIC_CONDITIONS
2102 customServoMixersMutable(i)->targetChannel = args[TARGET];
2103 customServoMixersMutable(i)->inputSource = args[INPUT];
2104 customServoMixersMutable(i)->rate = args[RATE];
2105 customServoMixersMutable(i)->speed = args[SPEED];
2106 #ifdef USE_PROGRAMMING_FRAMEWORK
2107 customServoMixersMutable(i)->conditionId = args[CONDITION];
2108 #endif
2109 cliServoMix("");
2110 } else {
2111 cliShowParseError();
2116 #ifdef USE_PROGRAMMING_FRAMEWORK
2118 static void printLogic(uint8_t dumpMask, const logicCondition_t *logicConditions, const logicCondition_t *defaultLogicConditions, int16_t showLC)
2120 const char *format = "logic %d %d %d %d %d %d %d %d %d";
2121 for (uint8_t i = 0; i < MAX_LOGIC_CONDITIONS; i++) {
2122 if (showLC == -1 || showLC == i) {
2123 const logicCondition_t logic = logicConditions[i];
2125 bool equalsDefault = false;
2126 if (defaultLogicConditions) {
2127 logicCondition_t defaultValue = defaultLogicConditions[i];
2128 equalsDefault =
2129 logic.enabled == defaultValue.enabled &&
2130 logic.activatorId == defaultValue.activatorId &&
2131 logic.operation == defaultValue.operation &&
2132 logic.operandA.type == defaultValue.operandA.type &&
2133 logic.operandA.value == defaultValue.operandA.value &&
2134 logic.operandB.type == defaultValue.operandB.type &&
2135 logic.operandB.value == defaultValue.operandB.value &&
2136 logic.flags == defaultValue.flags;
2138 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2140 logic.enabled,
2141 logic.activatorId,
2142 logic.operation,
2143 logic.operandA.type,
2144 logic.operandA.value,
2145 logic.operandB.type,
2146 logic.operandB.value,
2147 logic.flags
2150 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2152 logic.enabled,
2153 logic.activatorId,
2154 logic.operation,
2155 logic.operandA.type,
2156 logic.operandA.value,
2157 logic.operandB.type,
2158 logic.operandB.value,
2159 logic.flags
2165 static void processCliLogic(char *cmdline, int16_t lcIndex) {
2166 char * saveptr;
2167 int args[9], check = 0;
2168 uint8_t len = strlen(cmdline);
2170 if (len == 0) {
2171 if (!commandBatchActive) {
2172 printLogic(DUMP_MASTER, logicConditions(0), NULL, -1);
2173 } else if (lcIndex >= 0) {
2174 printLogic(DUMP_MASTER, logicConditions(0), NULL, lcIndex);
2176 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2177 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS);
2178 } else {
2179 enum {
2180 INDEX = 0,
2181 ENABLED,
2182 ACTIVATOR_ID,
2183 OPERATION,
2184 OPERAND_A_TYPE,
2185 OPERAND_A_VALUE,
2186 OPERAND_B_TYPE,
2187 OPERAND_B_VALUE,
2188 FLAGS,
2189 ARGS_COUNT
2191 char *ptr = strtok_r(cmdline, " ", &saveptr);
2192 while (ptr != NULL && check < ARGS_COUNT) {
2193 args[check++] = fastA2I(ptr);
2194 ptr = strtok_r(NULL, " ", &saveptr);
2197 if (ptr != NULL || check != ARGS_COUNT) {
2198 cliShowParseError();
2199 return;
2202 int32_t i = args[INDEX];
2203 if (
2204 i >= 0 && i < MAX_LOGIC_CONDITIONS &&
2205 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
2206 args[ACTIVATOR_ID] >= -1 && args[ACTIVATOR_ID] < MAX_LOGIC_CONDITIONS &&
2207 args[OPERATION] >= 0 && args[OPERATION] < LOGIC_CONDITION_LAST &&
2208 args[OPERAND_A_TYPE] >= 0 && args[OPERAND_A_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2209 args[OPERAND_A_VALUE] >= -1000000 && args[OPERAND_A_VALUE] <= 1000000 &&
2210 args[OPERAND_B_TYPE] >= 0 && args[OPERAND_B_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2211 args[OPERAND_B_VALUE] >= -1000000 && args[OPERAND_B_VALUE] <= 1000000 &&
2212 args[FLAGS] >= 0 && args[FLAGS] <= 255
2215 logicConditionsMutable(i)->enabled = args[ENABLED];
2216 logicConditionsMutable(i)->activatorId = args[ACTIVATOR_ID];
2217 logicConditionsMutable(i)->operation = args[OPERATION];
2218 logicConditionsMutable(i)->operandA.type = args[OPERAND_A_TYPE];
2219 logicConditionsMutable(i)->operandA.value = args[OPERAND_A_VALUE];
2220 logicConditionsMutable(i)->operandB.type = args[OPERAND_B_TYPE];
2221 logicConditionsMutable(i)->operandB.value = args[OPERAND_B_VALUE];
2222 logicConditionsMutable(i)->flags = args[FLAGS];
2224 processCliLogic("", i);
2225 } else {
2226 cliShowParseError();
2231 static void cliLogic(char *cmdline) {
2232 processCliLogic(cmdline, -1);
2235 static void printGvar(uint8_t dumpMask, const globalVariableConfig_t *gvars, const globalVariableConfig_t *defaultGvars)
2237 const char *format = "gvar %d %d %d %d";
2238 for (uint32_t i = 0; i < MAX_GLOBAL_VARIABLES; i++) {
2239 const globalVariableConfig_t gvar = gvars[i];
2241 bool equalsDefault = false;
2242 if (defaultGvars) {
2243 globalVariableConfig_t defaultValue = defaultGvars[i];
2244 equalsDefault =
2245 gvar.defaultValue == defaultValue.defaultValue &&
2246 gvar.min == defaultValue.min &&
2247 gvar.max == defaultValue.max;
2249 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2251 gvar.defaultValue,
2252 gvar.min,
2253 gvar.max
2256 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2258 gvar.defaultValue,
2259 gvar.min,
2260 gvar.max
2265 static void cliGvar(char *cmdline) {
2266 char * saveptr;
2267 int args[4], check = 0;
2268 uint8_t len = strlen(cmdline);
2270 if (len == 0) {
2271 printGvar(DUMP_MASTER, globalVariableConfigs(0), NULL);
2272 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2273 pgResetCopy(globalVariableConfigsMutable(0), PG_GLOBAL_VARIABLE_CONFIG);
2274 } else {
2275 enum {
2276 INDEX = 0,
2277 DEFAULT,
2278 MIN,
2279 MAX,
2280 ARGS_COUNT
2282 char *ptr = strtok_r(cmdline, " ", &saveptr);
2283 while (ptr != NULL && check < ARGS_COUNT) {
2284 args[check++] = fastA2I(ptr);
2285 ptr = strtok_r(NULL, " ", &saveptr);
2288 if (ptr != NULL || check != ARGS_COUNT) {
2289 cliShowParseError();
2290 return;
2293 int32_t i = args[INDEX];
2294 if (
2295 i >= 0 && i < MAX_GLOBAL_VARIABLES &&
2296 args[DEFAULT] >= INT32_MIN && args[DEFAULT] <= INT32_MAX &&
2297 args[MIN] >= INT32_MIN && args[MIN] <= INT32_MAX &&
2298 args[MAX] >= INT32_MIN && args[MAX] <= INT32_MAX
2300 globalVariableConfigsMutable(i)->defaultValue = args[DEFAULT];
2301 globalVariableConfigsMutable(i)->min = args[MIN];
2302 globalVariableConfigsMutable(i)->max = args[MAX];
2304 cliGvar("");
2305 } else {
2306 cliShowParseError();
2311 static void printPid(uint8_t dumpMask, const programmingPid_t *programmingPids, const programmingPid_t *defaultProgrammingPids)
2313 const char *format = "pid %d %d %d %d %d %d %d %d %d %d";
2314 for (uint32_t i = 0; i < MAX_PROGRAMMING_PID_COUNT; i++) {
2315 const programmingPid_t pid = programmingPids[i];
2317 bool equalsDefault = false;
2318 if (defaultProgrammingPids) {
2319 programmingPid_t defaultValue = defaultProgrammingPids[i];
2320 equalsDefault =
2321 pid.enabled == defaultValue.enabled &&
2322 pid.setpoint.type == defaultValue.setpoint.type &&
2323 pid.setpoint.value == defaultValue.setpoint.value &&
2324 pid.measurement.type == defaultValue.measurement.type &&
2325 pid.measurement.value == defaultValue.measurement.value &&
2326 pid.gains.P == defaultValue.gains.P &&
2327 pid.gains.I == defaultValue.gains.I &&
2328 pid.gains.D == defaultValue.gains.D &&
2329 pid.gains.FF == defaultValue.gains.FF;
2331 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2333 pid.enabled,
2334 pid.setpoint.type,
2335 pid.setpoint.value,
2336 pid.measurement.type,
2337 pid.measurement.value,
2338 pid.gains.P,
2339 pid.gains.I,
2340 pid.gains.D,
2341 pid.gains.FF
2344 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2346 pid.enabled,
2347 pid.setpoint.type,
2348 pid.setpoint.value,
2349 pid.measurement.type,
2350 pid.measurement.value,
2351 pid.gains.P,
2352 pid.gains.I,
2353 pid.gains.D,
2354 pid.gains.FF
2359 static void cliPid(char *cmdline) {
2360 char * saveptr;
2361 int args[10], check = 0;
2362 uint8_t len = strlen(cmdline);
2364 if (len == 0) {
2365 printPid(DUMP_MASTER, programmingPids(0), NULL);
2366 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2367 pgResetCopy(programmingPidsMutable(0), PG_LOGIC_CONDITIONS);
2368 } else {
2369 enum {
2370 INDEX = 0,
2371 ENABLED,
2372 SETPOINT_TYPE,
2373 SETPOINT_VALUE,
2374 MEASUREMENT_TYPE,
2375 MEASUREMENT_VALUE,
2376 P_GAIN,
2377 I_GAIN,
2378 D_GAIN,
2379 FF_GAIN,
2380 ARGS_COUNT
2382 char *ptr = strtok_r(cmdline, " ", &saveptr);
2383 while (ptr != NULL && check < ARGS_COUNT) {
2384 args[check++] = fastA2I(ptr);
2385 ptr = strtok_r(NULL, " ", &saveptr);
2388 if (ptr != NULL || check != ARGS_COUNT) {
2389 cliShowParseError();
2390 return;
2393 int32_t i = args[INDEX];
2394 if (
2395 i >= 0 && i < MAX_PROGRAMMING_PID_COUNT &&
2396 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
2397 args[SETPOINT_TYPE] >= 0 && args[SETPOINT_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2398 args[SETPOINT_VALUE] >= -1000000 && args[SETPOINT_VALUE] <= 1000000 &&
2399 args[MEASUREMENT_TYPE] >= 0 && args[MEASUREMENT_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2400 args[MEASUREMENT_VALUE] >= -1000000 && args[MEASUREMENT_VALUE] <= 1000000 &&
2401 args[P_GAIN] >= 0 && args[P_GAIN] <= INT16_MAX &&
2402 args[I_GAIN] >= 0 && args[I_GAIN] <= INT16_MAX &&
2403 args[D_GAIN] >= 0 && args[D_GAIN] <= INT16_MAX &&
2404 args[FF_GAIN] >= 0 && args[FF_GAIN] <= INT16_MAX
2406 programmingPidsMutable(i)->enabled = args[ENABLED];
2407 programmingPidsMutable(i)->setpoint.type = args[SETPOINT_TYPE];
2408 programmingPidsMutable(i)->setpoint.value = args[SETPOINT_VALUE];
2409 programmingPidsMutable(i)->measurement.type = args[MEASUREMENT_TYPE];
2410 programmingPidsMutable(i)->measurement.value = args[MEASUREMENT_VALUE];
2411 programmingPidsMutable(i)->gains.P = args[P_GAIN];
2412 programmingPidsMutable(i)->gains.I = args[I_GAIN];
2413 programmingPidsMutable(i)->gains.D = args[D_GAIN];
2414 programmingPidsMutable(i)->gains.FF = args[FF_GAIN];
2416 cliPid("");
2417 } else {
2418 cliShowParseError();
2423 static void printOsdCustomElements(uint8_t dumpMask, const osdCustomElement_t *osdCustomElements, const osdCustomElement_t *defaultosdCustomElements)
2425 const char *format = "osd_custom_elements %d %d %d %d %d %d %d %d %d \"%s\"";
2427 if(CUSTOM_ELEMENTS_PARTS != 3)
2429 cliPrintHashLine("Incompatible count of elements for custom OSD elements");
2432 for (uint8_t i = 0; i < MAX_CUSTOM_ELEMENTS; i++) {
2433 bool equalsDefault = false;
2435 const osdCustomElement_t osdCustomElement = osdCustomElements[i];
2436 if(defaultosdCustomElements){
2437 const osdCustomElement_t defaultValue = defaultosdCustomElements[i];
2438 equalsDefault =
2439 osdCustomElement.part[0].type == defaultValue.part[0].type &&
2440 osdCustomElement.part[0].value == defaultValue.part[0].value &&
2441 osdCustomElement.part[1].type == defaultValue.part[1].type &&
2442 osdCustomElement.part[1].value == defaultValue.part[1].value &&
2443 osdCustomElement.part[2].type == defaultValue.part[2].type &&
2444 osdCustomElement.part[2].value == defaultValue.part[2].value &&
2445 osdCustomElement.visibility.type == defaultValue.visibility.type &&
2446 osdCustomElement.visibility.value == defaultValue.visibility.value &&
2447 strcmp(osdCustomElement.osdCustomElementText, defaultValue.osdCustomElementText) == 0;
2449 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2451 osdCustomElement.part[0].type,
2452 osdCustomElement.part[0].value,
2453 osdCustomElement.part[1].type,
2454 osdCustomElement.part[1].value,
2455 osdCustomElement.part[2].type,
2456 osdCustomElement.part[2].value,
2457 osdCustomElement.visibility.type,
2458 osdCustomElement.visibility.value,
2459 osdCustomElement.osdCustomElementText
2463 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2465 osdCustomElement.part[0].type,
2466 osdCustomElement.part[0].value,
2467 osdCustomElement.part[1].type,
2468 osdCustomElement.part[1].value,
2469 osdCustomElement.part[2].type,
2470 osdCustomElement.part[2].value,
2471 osdCustomElement.visibility.type,
2472 osdCustomElement.visibility.value,
2473 osdCustomElement.osdCustomElementText
2478 static void osdCustom(char *cmdline){
2479 char * saveptrMain;
2480 char * saveptrParams;
2481 int args[10], check = 0;
2482 char text[OSD_CUSTOM_ELEMENT_TEXT_SIZE];
2483 uint8_t len = strlen(cmdline);
2485 if (len == 0) {
2486 printOsdCustomElements(DUMP_MASTER, osdCustomElements(0), NULL);
2487 } else {
2488 //split by ", first are params second is text
2489 char *ptrMain = strtok_r(cmdline, "\"", &saveptrMain);
2490 enum {
2491 INDEX = 0,
2492 PART0_TYPE,
2493 PART0_VALUE,
2494 PART1_TYPE,
2495 PART1_VALUE,
2496 PART2_TYPE,
2497 PART2_VALUE,
2498 VISIBILITY_TYPE,
2499 VISIBILITY_VALUE,
2500 ARGS_COUNT
2502 char *ptrParams = strtok_r(ptrMain, " ", &saveptrParams);
2503 while (ptrParams != NULL && check < ARGS_COUNT) {
2504 args[check++] = fastA2I(ptrParams);
2505 ptrParams = strtok_r(NULL, " ", &saveptrParams);
2508 if (check != ARGS_COUNT) {
2509 cliShowParseError();
2510 return;
2513 //text
2514 char *ptrText = strtok_r(NULL, "\"", &saveptrMain);
2515 size_t copySize = 0;
2516 if(ptrText != NULL){
2517 copySize = MIN(strlen(ptrText), (size_t)(sizeof(text) - 1));
2518 if(copySize > 0){
2519 memcpy(text, ptrText, copySize);
2522 text[copySize] = '\0';
2524 int32_t i = args[INDEX];
2525 if (
2526 i >= 0 && i < MAX_CUSTOM_ELEMENTS &&
2527 args[PART0_TYPE] >= 0 && args[PART0_TYPE] <= 7 &&
2528 args[PART0_VALUE] >= 0 && args[PART0_VALUE] <= UINT8_MAX &&
2529 args[PART1_TYPE] >= 0 && args[PART1_TYPE] <= 7 &&
2530 args[PART1_VALUE] >= 0 && args[PART1_VALUE] <= UINT8_MAX &&
2531 args[PART2_TYPE] >= 0 && args[PART2_TYPE] <= 7 &&
2532 args[PART2_VALUE] >= 0 && args[PART2_VALUE] <= UINT8_MAX &&
2533 args[VISIBILITY_TYPE] >= 0 && args[VISIBILITY_TYPE] <= 2 &&
2534 args[VISIBILITY_VALUE] >= 0 && args[VISIBILITY_VALUE] <= UINT8_MAX
2536 osdCustomElementsMutable(i)->part[0].type = args[PART0_TYPE];
2537 osdCustomElementsMutable(i)->part[0].value = args[PART0_VALUE];
2538 osdCustomElementsMutable(i)->part[1].type = args[PART1_TYPE];
2539 osdCustomElementsMutable(i)->part[1].value = args[PART1_VALUE];
2540 osdCustomElementsMutable(i)->part[2].type = args[PART2_TYPE];
2541 osdCustomElementsMutable(i)->part[2].value = args[PART2_VALUE];
2542 osdCustomElementsMutable(i)->visibility.type = args[VISIBILITY_TYPE];
2543 osdCustomElementsMutable(i)->visibility.value = args[VISIBILITY_VALUE];
2544 memcpy(osdCustomElementsMutable(i)->osdCustomElementText, text, OSD_CUSTOM_ELEMENT_TEXT_SIZE);
2546 osdCustom("");
2547 } else {
2548 cliShowParseError();
2554 #endif
2556 #ifdef USE_SDCARD
2558 static void cliWriteBytes(const uint8_t *buffer, int count)
2560 while (count > 0) {
2561 cliWrite(*buffer);
2562 buffer++;
2563 count--;
2567 static void cliSdInfo(char *cmdline)
2569 UNUSED(cmdline);
2571 cliPrint("SD card: ");
2573 if (!sdcard_isInserted()) {
2574 cliPrintLine("None inserted");
2575 return;
2578 if (!sdcard_isInitialized()) {
2579 cliPrintLine("Startup failed");
2580 return;
2583 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2585 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2586 metadata->manufacturerID,
2587 metadata->numBlocks / 2, /* One block is half a kB */
2588 metadata->productionMonth,
2589 metadata->productionYear,
2590 metadata->productRevisionMajor,
2591 metadata->productRevisionMinor
2594 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2596 cliPrint("'\r\n" "Filesystem: ");
2598 switch (afatfs_getFilesystemState()) {
2599 case AFATFS_FILESYSTEM_STATE_READY:
2600 cliPrint("Ready");
2601 break;
2602 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2603 cliPrint("Initializing");
2604 break;
2605 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2606 case AFATFS_FILESYSTEM_STATE_FATAL:
2607 cliPrint("Fatal");
2609 switch (afatfs_getLastError()) {
2610 case AFATFS_ERROR_BAD_MBR:
2611 cliPrint(" - no FAT MBR partitions");
2612 break;
2613 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2614 cliPrint(" - bad FAT header");
2615 break;
2616 case AFATFS_ERROR_GENERIC:
2617 case AFATFS_ERROR_NONE:
2618 ; // Nothing more detailed to print
2619 break;
2621 break;
2623 cliPrintLinefeed();
2626 #endif
2628 #ifdef USE_FLASHFS
2630 static void cliFlashInfo(char *cmdline)
2632 UNUSED(cmdline);
2634 const flashGeometry_t *layout = flashGetGeometry();
2636 if (layout->totalSize == 0) {
2637 cliPrintLine("Flash not available");
2638 return;
2641 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2642 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2644 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2645 const flashPartition_t *partition;
2646 if (index == 0) {
2647 cliPrintLine("Paritions:");
2649 partition = flashPartitionFindByIndex(index);
2650 if (!partition) {
2651 break;
2653 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2655 #ifdef USE_FLASHFS
2656 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2658 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2659 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2660 flashfsGetOffset()
2662 #endif
2665 static void cliFlashErase(char *cmdline)
2667 UNUSED(cmdline);
2669 const flashGeometry_t *layout = flashGetGeometry();
2671 if (layout->totalSize == 0) {
2672 cliPrintLine("Flash not available");
2673 return;
2676 cliPrintLine("Erasing...");
2677 flashfsEraseCompletely();
2679 while (!flashIsReady()) {
2680 delay(100);
2683 cliPrintLine("Done.");
2686 #ifdef USE_FLASH_TOOLS
2688 static void cliFlashWrite(char *cmdline)
2690 const uint32_t address = fastA2I(cmdline);
2691 const char *text = strchr(cmdline, ' ');
2693 if (!text) {
2694 cliShowParseError();
2695 } else {
2696 flashfsSeekAbs(address);
2697 flashfsWrite((uint8_t*)text, strlen(text), true);
2698 flashfsFlushSync();
2700 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2704 static void cliFlashRead(char *cmdline)
2706 uint32_t address = fastA2I(cmdline);
2708 const char *nextArg = strchr(cmdline, ' ');
2710 if (!nextArg) {
2711 cliShowParseError();
2712 } else {
2713 uint32_t length = fastA2I(nextArg);
2715 cliPrintLinef("Reading %u bytes at %u:", length, address);
2717 uint8_t buffer[32];
2718 while (length > 0) {
2719 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2721 for (int i = 0; i < bytesRead; i++) {
2722 cliWrite(buffer[i]);
2725 length -= bytesRead;
2726 address += bytesRead;
2728 if (bytesRead == 0) {
2729 //Assume we reached the end of the volume or something fatal happened
2730 break;
2733 cliPrintLinefeed();
2737 #endif
2738 #endif
2740 #ifdef USE_OSD
2741 static void printOsdLayout(uint8_t dumpMask, const osdLayoutsConfig_t *config, const osdLayoutsConfig_t *configDefault, int layout, int item)
2743 // "<layout> <item> <col> <row> <visible>"
2744 const char *format = "osd_layout %d %d %d %d %c";
2745 for (int ii = 0; ii < OSD_LAYOUT_COUNT; ii++) {
2746 if (layout >= 0 && layout != ii) {
2747 continue;
2749 const uint16_t *layoutItems = config->item_pos[ii];
2750 const uint16_t *defaultLayoutItems = configDefault->item_pos[ii];
2751 for (int jj = 0; jj < OSD_ITEM_COUNT; jj++) {
2752 if (item >= 0 && item != jj) {
2753 continue;
2755 bool equalsDefault = layoutItems[jj] == defaultLayoutItems[jj];
2756 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2757 ii, jj,
2758 OSD_X(defaultLayoutItems[jj]),
2759 OSD_Y(defaultLayoutItems[jj]),
2760 OSD_VISIBLE(defaultLayoutItems[jj]) ? 'V' : 'H');
2762 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2763 ii, jj,
2764 OSD_X(layoutItems[jj]),
2765 OSD_Y(layoutItems[jj]),
2766 OSD_VISIBLE(layoutItems[jj]) ? 'V' : 'H');
2771 static void cliOsdLayout(char *cmdline)
2773 char * saveptr;
2775 int layout = -1;
2776 int item = -1;
2777 int col = 0;
2778 int row = 0;
2779 bool visible = false;
2780 char *tok = strtok_r(cmdline, " ", &saveptr);
2782 int ii;
2784 for (ii = 0; tok != NULL; ii++, tok = strtok_r(NULL, " ", &saveptr)) {
2785 switch (ii) {
2786 case 0:
2787 layout = fastA2I(tok);
2788 if (layout < 0 || layout >= OSD_LAYOUT_COUNT) {
2789 cliShowParseError();
2790 return;
2792 break;
2793 case 1:
2794 item = fastA2I(tok);
2795 if (item < 0 || item >= OSD_ITEM_COUNT) {
2796 cliShowParseError();
2797 return;
2799 break;
2800 case 2:
2801 col = fastA2I(tok);
2802 if (col < 0 || col > OSD_X(OSD_POS_MAX)) {
2803 cliShowParseError();
2804 return;
2806 break;
2807 case 3:
2808 row = fastA2I(tok);
2809 if (row < 0 || row > OSD_Y(OSD_POS_MAX)) {
2810 cliShowParseError();
2811 return;
2813 break;
2814 case 4:
2815 switch (*tok) {
2816 case 'H':
2817 visible = false;
2818 break;
2819 case 'V':
2820 visible = true;
2821 break;
2822 default:
2823 cliShowParseError();
2824 return;
2826 break;
2827 default:
2828 cliShowParseError();
2829 return;
2833 switch (ii) {
2834 case 0:
2835 FALLTHROUGH;
2836 case 1:
2837 FALLTHROUGH;
2838 case 2:
2839 // No args, or just layout or layout and item. If any of them not provided,
2840 // it will be the -1 that we used during initialization, so printOsdLayout()
2841 // won't use them for filtering.
2842 printOsdLayout(DUMP_MASTER, osdLayoutsConfig(), osdLayoutsConfig(), layout, item);
2843 break;
2844 case 4:
2845 // No visibility provided. Keep the previous one.
2846 visible = OSD_VISIBLE(osdLayoutsConfig()->item_pos[layout][item]);
2847 FALLTHROUGH;
2848 case 5:
2849 // Layout, item, pos and visibility. Set the item.
2850 osdLayoutsConfigMutable()->item_pos[layout][item] = OSD_POS(col, row) | (visible ? OSD_VISIBLE_FLAG : 0);
2851 break;
2852 default:
2853 // Unhandled
2854 cliShowParseError();
2855 return;
2859 #endif
2861 static void printTimerOutputModes(dumpFlags_e dumpFlags, const timerOverride_t* to, const timerOverride_t* defaultTimerOverride, int timer)
2863 const char *format = "timer_output_mode %d %s";
2865 for (int i = 0; i < HARDWARE_TIMER_DEFINITION_COUNT; ++i) {
2866 if (timer < 0 || timer == i) {
2867 outputMode_e mode = to[i].outputMode;
2868 bool equalsDefault = false;
2869 if(defaultTimerOverride) {
2870 outputMode_e defaultMode = defaultTimerOverride[i].outputMode;
2871 equalsDefault = mode == defaultMode;
2872 cliDefaultPrintLinef(dumpFlags, equalsDefault, format, i, outputModeNames[defaultMode]);
2874 cliDumpPrintLinef(dumpFlags, equalsDefault, format, i, outputModeNames[mode]);
2879 static void cliTimerOutputMode(char *cmdline)
2881 char * saveptr;
2883 int timer = -1;
2884 uint8_t mode;
2885 char *tok = strtok_r(cmdline, " ", &saveptr);
2887 int ii;
2889 for (ii = 0; tok != NULL; ii++, tok = strtok_r(NULL, " ", &saveptr)) {
2890 switch (ii) {
2891 case 0:
2892 timer = fastA2I(tok);
2893 if (timer < 0 || timer >= HARDWARE_TIMER_DEFINITION_COUNT) {
2894 cliShowParseError();
2895 return;
2897 break;
2898 case 1:
2899 if(!sl_strcasecmp("AUTO", tok)) {
2900 mode = OUTPUT_MODE_AUTO;
2901 } else if(!sl_strcasecmp("MOTORS", tok)) {
2902 mode = OUTPUT_MODE_MOTORS;
2903 } else if(!sl_strcasecmp("SERVOS", tok)) {
2904 mode = OUTPUT_MODE_SERVOS;
2905 } else if(!sl_strcasecmp("LED", tok)) {
2906 mode = OUTPUT_MODE_LED;
2907 } else {
2908 cliShowParseError();
2909 return;
2911 break;
2912 default:
2913 cliShowParseError();
2914 return;
2918 switch (ii) {
2919 case 0:
2920 FALLTHROUGH;
2921 case 1:
2922 // No args, or just timer. If any of them not provided,
2923 // it will be the -1 that we used during initialization, so printOsdLayout()
2924 // won't use them for filtering.
2925 printTimerOutputModes(DUMP_MASTER, timerOverrides(0), NULL, timer);
2926 break;
2927 case 2:
2928 timerOverridesMutable(timer)->outputMode = mode;
2929 printTimerOutputModes(DUMP_MASTER, timerOverrides(0), NULL, timer);
2930 break;
2931 default:
2932 // Unhandled
2933 cliShowParseError();
2934 return;
2939 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2941 uint32_t mask = featureConfig->enabledFeatures;
2942 uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2943 for (uint32_t i = 0; ; i++) { // disable all feature first
2944 if (featureNames[i] == NULL)
2945 break;
2946 if (featureNames[i][0] == '\0')
2947 continue;
2948 const char *format = "feature -%s";
2949 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2950 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2952 for (uint32_t i = 0; ; i++) { // reenable what we want.
2953 if (featureNames[i] == NULL)
2954 break;
2955 if (featureNames[i][0] == '\0')
2956 continue;
2957 const char *format = "feature %s";
2958 if (defaultMask & (1 << i)) {
2959 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2961 if (mask & (1 << i)) {
2962 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2967 static void cliFeature(char *cmdline)
2969 uint32_t len = strlen(cmdline);
2970 uint32_t mask = featureMask();
2972 if (len == 0) {
2973 cliPrint("Enabled: ");
2974 for (uint32_t i = 0; ; i++) {
2975 if (featureNames[i] == NULL)
2976 break;
2977 if (featureNames[i][0] == '\0')
2978 continue;
2979 if (mask & (1 << i))
2980 cliPrintf("%s ", featureNames[i]);
2982 cliPrintLinefeed();
2983 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
2984 cliPrint("Available: ");
2985 for (uint32_t i = 0; ; i++) {
2986 if (featureNames[i] == NULL)
2987 break;
2988 if (featureNames[i][0] == '\0')
2989 continue;
2990 cliPrintf("%s ", featureNames[i]);
2992 cliPrintLinefeed();
2993 return;
2994 } else {
2995 bool remove = false;
2996 if (cmdline[0] == '-') {
2997 // remove feature
2998 remove = true;
2999 cmdline++; // skip over -
3000 len--;
3003 for (uint32_t i = 0; ; i++) {
3004 if (featureNames[i] == NULL) {
3005 cliPrintErrorLine("Invalid name");
3006 break;
3009 if (sl_strncasecmp(cmdline, featureNames[i], len) == 0) {
3011 mask = 1 << i;
3012 #ifndef USE_GPS
3013 if (mask & FEATURE_GPS) {
3014 cliPrintErrorLine("unavailable");
3015 break;
3017 #endif
3018 if (remove) {
3019 featureClear(mask);
3020 cliPrint("Disabled");
3021 } else {
3022 featureSet(mask);
3023 cliPrint("Enabled");
3025 cliPrintLinef(" %s", featureNames[i]);
3026 break;
3032 #ifdef USE_BLACKBOX
3033 static void printBlackbox(uint8_t dumpMask, const blackboxConfig_t *config, const blackboxConfig_t *configDefault)
3036 UNUSED(configDefault);
3038 uint32_t mask = config->includeFlags;
3040 for (uint8_t i = 0; ; i++) { // reenable what we want.
3041 if (blackboxIncludeFlagNames[i] == NULL) {
3042 break;
3045 const char *formatOn = "blackbox %s";
3046 const char *formatOff = "blackbox -%s";
3048 if (mask & (1 << i)) {
3049 cliDumpPrintLinef(dumpMask, false, formatOn, blackboxIncludeFlagNames[i]);
3050 cliDefaultPrintLinef(dumpMask, false, formatOn, blackboxIncludeFlagNames[i]);
3051 } else {
3052 cliDumpPrintLinef(dumpMask, false, formatOff, blackboxIncludeFlagNames[i]);
3053 cliDefaultPrintLinef(dumpMask, false, formatOff, blackboxIncludeFlagNames[i]);
3059 static void cliBlackbox(char *cmdline)
3061 uint32_t len = strlen(cmdline);
3062 uint32_t mask = blackboxConfig()->includeFlags;
3064 if (len == 0) {
3065 cliPrint("Enabled: ");
3066 for (uint8_t i = 0; ; i++) {
3067 if (blackboxIncludeFlagNames[i] == NULL) {
3068 break;
3071 if (mask & (1 << i))
3072 cliPrintf("%s ", blackboxIncludeFlagNames[i]);
3074 cliPrintLinefeed();
3075 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
3076 cliPrint("Available: ");
3077 for (uint32_t i = 0; ; i++) {
3078 if (blackboxIncludeFlagNames[i] == NULL) {
3079 break;
3082 cliPrintf("%s ", blackboxIncludeFlagNames[i]);
3084 cliPrintLinefeed();
3085 return;
3086 } else {
3087 bool remove = false;
3088 if (cmdline[0] == '-') {
3089 // remove feature
3090 remove = true;
3091 cmdline++; // skip over -
3092 len--;
3095 for (uint32_t i = 0; ; i++) {
3096 if (blackboxIncludeFlagNames[i] == NULL) {
3097 cliPrintErrorLine("Invalid name");
3098 break;
3101 if (sl_strncasecmp(cmdline, blackboxIncludeFlagNames[i], len) == 0) {
3103 mask = 1 << i;
3105 if (remove) {
3106 blackboxIncludeFlagClear(mask);
3107 cliPrint("Disabled");
3108 } else {
3109 blackboxIncludeFlagSet(mask);
3110 cliPrint("Enabled");
3112 cliPrintLinef(" %s", blackboxIncludeFlagNames[i]);
3113 break;
3118 #endif
3120 #if defined(BEEPER) || defined(USE_DSHOT)
3121 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
3123 const uint8_t beeperCount = beeperTableEntryCount();
3124 const uint32_t mask = beeperConfig->beeper_off_flags;
3125 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
3126 for (int i = 0; i < beeperCount - 2; i++) {
3127 const char *formatOff = "beeper -%s";
3128 const char *formatOn = "beeper %s";
3129 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOn : formatOff, beeperNameForTableIndex(i));
3130 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOff : formatOn, beeperNameForTableIndex(i));
3134 static void cliBeeper(char *cmdline)
3136 uint32_t len = strlen(cmdline);
3137 uint8_t beeperCount = beeperTableEntryCount();
3138 uint32_t mask = getBeeperOffMask();
3140 if (len == 0) {
3141 cliPrintf("Disabled:");
3142 for (int32_t i = 0; ; i++) {
3143 if (i == beeperCount - 2){
3144 if (mask == 0)
3145 cliPrint(" none");
3146 break;
3148 if (mask & (1 << (beeperModeForTableIndex(i) - 1)))
3149 cliPrintf(" %s", beeperNameForTableIndex(i));
3151 cliPrintLinefeed();
3152 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
3153 cliPrint("Available:");
3154 for (uint32_t i = 0; i < beeperCount; i++)
3155 cliPrintf(" %s", beeperNameForTableIndex(i));
3156 cliPrintLinefeed();
3157 return;
3158 } else {
3159 bool remove = false;
3160 if (cmdline[0] == '-') {
3161 remove = true; // this is for beeper OFF condition
3162 cmdline++;
3163 len--;
3166 for (uint32_t i = 0; ; i++) {
3167 if (i == beeperCount) {
3168 cliPrintErrorLine("Invalid name");
3169 break;
3171 if (sl_strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
3172 if (remove) { // beeper off
3173 if (i == BEEPER_ALL-1)
3174 beeperOffSetAll(beeperCount-2);
3175 else
3176 if (i == BEEPER_PREFERENCE-1)
3177 setBeeperOffMask(getPreferredBeeperOffMask());
3178 else {
3179 mask = 1 << (beeperModeForTableIndex(i) - 1);
3180 beeperOffSet(mask);
3182 cliPrint("Disabled");
3184 else { // beeper on
3185 if (i == BEEPER_ALL-1)
3186 beeperOffClearAll();
3187 else
3188 if (i == BEEPER_PREFERENCE-1)
3189 setPreferredBeeperOffMask(getBeeperOffMask());
3190 else {
3191 mask = 1 << (beeperModeForTableIndex(i) - 1);
3192 beeperOffClear(mask);
3194 cliPrint("Enabled");
3196 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3197 break;
3202 #endif
3204 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
3206 bool equalsDefault = true;
3207 char buf[16];
3208 char bufDefault[16];
3209 uint32_t i;
3211 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3212 buf[i] = bufDefault[i] = 0;
3215 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3216 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3217 if (defaultRxConfig) {
3218 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3219 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3222 buf[i] = '\0';
3224 const char *formatMap = "map %s";
3225 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3226 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3229 static void cliMap(char *cmdline)
3231 uint32_t len;
3232 char out[MAX_MAPPABLE_RX_INPUTS + 1];
3234 len = strlen(cmdline);
3236 if (len == MAX_MAPPABLE_RX_INPUTS) {
3237 // uppercase it
3238 for (uint32_t i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3239 cmdline[i] = sl_toupper((unsigned char)cmdline[i]);
3241 for (uint32_t i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3242 if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i])) {
3243 continue;
3245 cliShowParseError();
3246 return;
3248 parseRcChannels(cmdline);
3249 } else if (len != 0) {
3250 cliShowParseError();
3252 cliPrint("Map: ");
3253 uint32_t i;
3254 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++){
3255 out[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3257 out[i] = '\0';
3258 cliPrintLinef("%s", out);
3261 static const char *checkCommand(const char *cmdLine, const char *command)
3263 if (!sl_strncasecmp(cmdLine, command, strlen(command)) // command names match
3264 && !sl_isalnum((unsigned)cmdLine[strlen(command)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
3265 return cmdLine + strlen(command) + 1;
3266 } else {
3267 return 0;
3271 static void cliRebootEx(bool bootLoader)
3273 cliPrint("\r\nRebooting");
3274 bufWriterFlush(cliWriter);
3275 waitForSerialPortToFinishTransmitting(cliPort);
3277 fcReboot(bootLoader);
3280 static void cliReboot(void)
3282 cliRebootEx(false);
3285 static void cliDfu(char *cmdline)
3287 UNUSED(cmdline);
3288 #ifndef CLI_MINIMAL_VERBOSITY
3289 cliPrint("\r\nRestarting in DFU mode");
3290 #endif
3291 cliRebootEx(true);
3294 #if defined (USE_SERIALRX_SRXL2)
3295 void cliRxBind(char *cmdline){
3296 UNUSED(cmdline);
3297 if (rxConfig()->receiverType == RX_TYPE_SERIAL) {
3298 switch (rxConfig()->serialrx_provider) {
3299 default:
3300 cliPrint("Not supported.");
3301 break;
3302 #if defined(USE_SERIALRX_SRXL2)
3303 case SERIALRX_SRXL2:
3304 srxl2Bind();
3305 cliPrint("Binding SRXL2 receiver...");
3306 break;
3307 #endif
3308 #if defined(USE_SERIALRX_CRSF)
3309 case SERIALRX_CRSF:
3310 crsfBind();
3311 cliPrint("Binding CRSF receiver...");
3312 break;
3313 #endif
3317 #endif
3319 static void cliExit(char *cmdline)
3321 UNUSED(cmdline);
3323 #ifndef CLI_MINIMAL_VERBOSITY
3324 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
3325 #endif
3326 bufWriterFlush(cliWriter);
3328 *cliBuffer = '\0';
3329 bufferIndex = 0;
3330 cliMode = false;
3331 // incase a motor was left running during motortest, clear it here
3332 mixerResetDisarmedMotors();
3333 cliReboot();
3335 cliWriter = NULL;
3338 #ifdef USE_GPS
3339 static void cliGpsPassthrough(char *cmdline)
3341 UNUSED(cmdline);
3343 gpsEnablePassthrough(cliPort);
3345 #endif
3347 static void cliMotor(char *cmdline)
3349 int motor_index = 0;
3350 int motor_value = 0;
3351 int index = 0;
3352 char *pch = NULL;
3353 char *saveptr;
3355 if (isEmpty(cmdline)) {
3356 cliShowParseError();
3358 return;
3361 pch = strtok_r(cmdline, " ", &saveptr);
3362 while (pch != NULL) {
3363 switch (index) {
3364 case 0:
3365 motor_index = fastA2I(pch);
3366 break;
3367 case 1:
3368 motor_value = fastA2I(pch);
3369 break;
3371 index++;
3372 pch = strtok_r(NULL, " ", &saveptr);
3375 if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
3376 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
3377 return;
3380 if (index == 2) {
3381 if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
3382 cliShowArgumentRangeError("value", 1000, 2000);
3383 return;
3384 } else {
3385 motor_disarmed[motor_index] = motor_value;
3389 cliPrintLinef("motor %d: %d", motor_index, motor_disarmed[motor_index]);
3392 static void cliPlaySound(char *cmdline)
3394 int i;
3395 const char *name;
3396 static int lastSoundIdx = -1;
3398 if (isEmpty(cmdline)) {
3399 i = lastSoundIdx + 1; //next sound index
3400 if ((name=beeperNameForTableIndex(i)) == NULL) {
3401 while (true) { //no name for index; try next one
3402 if (++i >= beeperTableEntryCount())
3403 i = 0; //if end then wrap around to first entry
3404 if ((name=beeperNameForTableIndex(i)) != NULL)
3405 break; //if name OK then play sound below
3406 if (i == lastSoundIdx + 1) { //prevent infinite loop
3407 cliPrintLine("Error playing sound");
3408 return;
3412 } else { //index value was given
3413 i = fastA2I(cmdline);
3414 if ((name=beeperNameForTableIndex(i)) == NULL) {
3415 cliPrintLinef("No sound for index %d", i);
3416 return;
3419 lastSoundIdx = i;
3420 beeperSilence();
3421 cliPrintLinef("Playing sound %d: %s", i, name);
3422 beeper(beeperModeForTableIndex(i));
3425 static void cliControlProfile(char *cmdline)
3427 // CLI profile index is 1-based
3428 if (isEmpty(cmdline)) {
3429 cliPrintLinef("control_profile %d", getConfigProfile() + 1);
3430 return;
3431 } else {
3432 const int i = fastA2I(cmdline) - 1;
3433 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3434 setConfigProfileAndWriteEEPROM(i);
3435 cliControlProfile("");
3440 static void cliDumpControlProfile(uint8_t profileIndex, uint8_t dumpMask)
3442 if (profileIndex >= MAX_PROFILE_COUNT) {
3443 // Faulty values
3444 return;
3446 setConfigProfile(profileIndex);
3447 cliPrintHashLine("control_profile");
3448 cliPrintLinef("control_profile %d\r\n", getConfigProfile() + 1);
3449 dumpAllValues(PROFILE_VALUE, dumpMask);
3450 dumpAllValues(CONTROL_RATE_VALUE, dumpMask);
3451 dumpAllValues(EZ_TUNE_VALUE, dumpMask);
3454 static void cliBatteryProfile(char *cmdline)
3456 // CLI profile index is 1-based
3457 if (isEmpty(cmdline)) {
3458 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
3459 return;
3460 } else {
3461 const int i = fastA2I(cmdline) - 1;
3462 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3463 setConfigBatteryProfileAndWriteEEPROM(i);
3464 cliBatteryProfile("");
3469 static void cliDumpBatteryProfile(uint8_t profileIndex, uint8_t dumpMask)
3471 if (profileIndex >= MAX_BATTERY_PROFILE_COUNT) {
3472 // Faulty values
3473 return;
3475 setConfigBatteryProfile(profileIndex);
3476 cliPrintHashLine("battery_profile");
3477 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
3478 dumpAllValues(BATTERY_CONFIG_VALUE, dumpMask);
3481 static void cliMixerProfile(char *cmdline)
3483 // CLI profile index is 1-based
3484 if (isEmpty(cmdline)) {
3485 cliPrintLinef("mixer_profile %d", getConfigMixerProfile() + 1);
3486 return;
3487 } else {
3488 const int i = fastA2I(cmdline) - 1;
3489 if (i >= 0 && i < MAX_MIXER_PROFILE_COUNT) {
3490 setConfigMixerProfileAndWriteEEPROM(i);
3491 cliMixerProfile("");
3496 static void cliDumpMixerProfile(uint8_t profileIndex, uint8_t dumpMask)
3498 if (profileIndex >= MAX_MIXER_PROFILE_COUNT) {
3499 // Faulty values
3500 return;
3502 setConfigMixerProfile(profileIndex);
3503 cliPrintHashLine("mixer_profile");
3504 cliPrintLinef("mixer_profile %d\r\n", getConfigMixerProfile() + 1);
3505 dumpAllValues(MIXER_CONFIG_VALUE, dumpMask);
3506 cliPrintHashLine("Mixer: motor mixer");
3507 cliDumpPrintLinef(dumpMask, primaryMotorMixer_CopyArray()[0].throttle == 0.0f, "\r\nmmix reset\r\n");
3508 printMotorMix(dumpMask, primaryMotorMixer_CopyArray(), primaryMotorMixer(0));
3509 cliPrintHashLine("Mixer: servo mixer");
3510 cliDumpPrintLinef(dumpMask, customServoMixers_CopyArray()[0].rate == 0, "smix reset\r\n");
3511 printServoMix(dumpMask, customServoMixers_CopyArray(), customServoMixers(0));
3514 #ifdef USE_CLI_BATCH
3515 static void cliPrintCommandBatchWarning(const char *warning)
3517 char errorBuf[59];
3518 tfp_sprintf(errorBuf, "%d ERRORS WERE DETECTED - Please review and fix before continuing!", commandBatchErrorCount);
3520 cliPrintErrorLinef(errorBuf);
3521 if (warning) {
3522 cliPrintErrorLinef(warning);
3526 static void resetCommandBatch(void)
3528 commandBatchActive = false;
3529 commandBatchError = false;
3530 commandBatchErrorCount = 0;
3533 static void cliBatch(char *cmdline)
3535 if (strncasecmp(cmdline, "start", 5) == 0) {
3536 if (!commandBatchActive) {
3537 commandBatchActive = true;
3538 commandBatchError = false;
3539 commandBatchErrorCount = 0;
3541 cliPrintLine("Command batch started");
3542 } else if (strncasecmp(cmdline, "end", 3) == 0) {
3543 if (commandBatchActive && commandBatchError) {
3544 cliPrintCommandBatchWarning(NULL);
3545 } else {
3546 cliPrintLine("Command batch ended");
3548 resetCommandBatch();
3549 } else {
3550 cliPrintErrorLinef("Invalid option");
3553 #endif
3555 static void cliSave(char *cmdline)
3557 UNUSED(cmdline);
3559 #ifdef USE_CLI_BATCH
3560 if (commandBatchActive && commandBatchError) {
3561 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3562 resetCommandBatch();
3563 return;
3565 #endif
3567 cliPrint("Saving");
3568 //copyCurrentProfileToProfileSlot(getConfigProfile();
3569 suspendRxSignal();
3570 writeEEPROM();
3571 resumeRxSignal();
3572 cliReboot();
3575 static void cliDefaults(char *cmdline)
3577 UNUSED(cmdline);
3579 cliPrint("Resetting to defaults");
3580 resetEEPROM();
3581 suspendRxSignal();
3582 writeEEPROM();
3583 resumeRxSignal();
3585 #ifdef USE_CLI_BATCH
3586 commandBatchError = false;
3587 #endif
3589 if (!checkCommand(cmdline, "noreboot"))
3590 cliReboot();
3593 static void cliGet(char *cmdline)
3595 const setting_t *val;
3596 int matchedCommands = 0;
3597 char name[SETTING_MAX_NAME_LENGTH];
3599 while(*cmdline == ' ') ++cmdline; // ignore spaces
3601 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3602 val = settingGet(i);
3603 if (settingNameContains(val, name, cmdline)) {
3604 cliPrintf("%s = ", name);
3605 if (strcmp(name, "name") == 0) {
3606 // if the craftname has a leading space, then enclose the name in quotes
3607 const char * v = (const char *)settingGetValuePointer(val);
3608 cliPrintf(v[0] == ' ' ? "\"%s\"" : "%s", v);
3609 } else {
3610 cliPrintVar(val, 0);
3612 cliPrintLinefeed();
3613 cliPrintVarRange(val);
3614 cliPrintLinefeed();
3616 matchedCommands++;
3621 if (matchedCommands) {
3622 return;
3625 cliPrintErrorLine("Invalid name");
3628 static void cliSet(char *cmdline)
3630 uint32_t len;
3631 const setting_t *val;
3632 char *eqptr = NULL;
3633 char name[SETTING_MAX_NAME_LENGTH];
3635 while(*cmdline == ' ') ++cmdline; // ignore spaces
3637 len = strlen(cmdline);
3639 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
3640 cliPrintLine("Current settings:");
3641 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3642 val = settingGet(i);
3643 settingGetName(val, name);
3644 cliPrintf("%s = ", name);
3645 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3646 cliPrintLinefeed();
3648 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
3649 // has equals
3651 char *lastNonSpaceCharacter = eqptr;
3652 while (*(lastNonSpaceCharacter - 1) == ' ') {
3653 lastNonSpaceCharacter--;
3655 uint8_t variableNameLength = lastNonSpaceCharacter - cmdline;
3657 // skip the '=' and any ' ' characters
3658 eqptr++;
3659 while (*(eqptr) == ' ') {
3660 eqptr++;
3663 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3664 val = settingGet(i);
3665 // ensure exact match when setting to prevent setting variables with shorter names
3666 if (settingNameIsExactMatch(val, name, cmdline, variableNameLength)) {
3667 const setting_type_e type = SETTING_TYPE(val);
3668 if (type == VAR_STRING) {
3669 // Convert strings to uppercase. Lower case is not supported by the OSD.
3670 sl_toupperptr(eqptr);
3671 // if setting the craftname, remove any quotes around the name. This allows leading spaces in the name
3672 if ((strcmp(name, "name") == 0 || strcmp(name, "pilot_name") == 0) && (eqptr[0] == '"' && eqptr[strlen(eqptr)-1] == '"')) {
3673 settingSetString(val, eqptr + 1, strlen(eqptr)-2);
3674 } else {
3675 settingSetString(val, eqptr, strlen(eqptr));
3677 return;
3679 const setting_mode_e mode = SETTING_MODE(val);
3680 bool changeValue = false;
3681 int_float_value_t tmp = {0};
3682 switch (mode) {
3683 case MODE_DIRECT: {
3684 if (*eqptr != 0 && strspn(eqptr, "0123456789.+-") == strlen(eqptr)) {
3685 float valuef = fastA2F(eqptr);
3686 // note: compare float values
3687 if (valuef >= (float)settingGetMin(val) && valuef <= (float)settingGetMax(val)) {
3689 if (type == VAR_FLOAT)
3690 tmp.float_value = valuef;
3691 else if (type == VAR_UINT32)
3692 tmp.uint_value = fastA2UL(eqptr);
3693 else
3694 tmp.int_value = fastA2I(eqptr);
3696 changeValue = true;
3700 break;
3701 case MODE_LOOKUP: {
3702 const lookupTableEntry_t *tableEntry = settingLookupTable(val);
3703 bool matched = false;
3704 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
3705 matched = sl_strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
3707 if (matched) {
3708 tmp.int_value = tableValueIndex;
3709 changeValue = true;
3713 break;
3716 if (changeValue) {
3717 cliSetIntFloatVar(val, tmp);
3719 cliPrintf("%s set to ", name);
3720 cliPrintVar(val, 0);
3721 } else {
3722 cliPrintError("Invalid value. ");
3723 cliPrintVarRange(val);
3724 cliPrintLinefeed();
3727 return;
3730 cliPrintErrorLine("Invalid name");
3731 } else {
3732 // no equals, check for matching variables.
3733 cliGet(cmdline);
3737 static const char * getBatteryStateString(void)
3739 static const char * const batteryStateStrings[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
3741 return batteryStateStrings[getBatteryState()];
3744 static void cliStatus(char *cmdline)
3746 UNUSED(cmdline);
3748 char buf[MAX(FORMATTED_DATE_TIME_BUFSIZE, SETTING_MAX_NAME_LENGTH)];
3749 dateTime_t dt;
3751 cliPrintLinef("%s/%s %s %s / %s (%s) %s",
3752 FC_FIRMWARE_NAME,
3753 targetName,
3754 FC_VERSION_STRING,
3755 buildDate,
3756 buildTime,
3757 shortGitRevision,
3758 FC_VERSION_TYPE
3760 cliPrintLinef("GCC-%s",
3761 compilerVersion
3763 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3764 rtcGetDateTime(&dt);
3765 dateTimeFormatLocal(buf, &dt);
3766 cliPrintLinef("Current Time: %s", buf);
3767 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
3768 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3770 const uint32_t detectedSensorsMask = sensorsMask();
3772 for (int i = 0; i < SENSOR_INDEX_COUNT; i++) {
3774 const uint32_t mask = (1 << i);
3775 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3776 const int sensorHardwareIndex = detectedSensors[i];
3777 if (sensorHardwareNames[i]) {
3778 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3779 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3783 cliPrintLinefeed();
3784 #if !defined(SITL_BUILD)
3785 #if defined(AT32F43x)
3786 cliPrintLine("AT32 system clocks:");
3787 crm_clocks_freq_type clocks;
3788 crm_clocks_freq_get(&clocks);
3790 cliPrintLinef(" SYSCLK = %d MHz", clocks.sclk_freq / 1000000);
3791 cliPrintLinef(" ABH = %d MHz", clocks.ahb_freq / 1000000);
3792 cliPrintLinef(" ABP1 = %d MHz", clocks.apb1_freq / 1000000);
3793 cliPrintLinef(" ABP2 = %d MHz", clocks.apb2_freq / 1000000);
3794 #else
3795 cliPrintLine("STM32 system clocks:");
3796 #if defined(USE_HAL_DRIVER)
3797 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
3798 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
3799 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
3800 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
3801 #else
3802 RCC_ClocksTypeDef clocks;
3803 RCC_GetClocksFreq(&clocks);
3804 cliPrintLinef(" SYSCLK = %d MHz", clocks.SYSCLK_Frequency / 1000000);
3805 cliPrintLinef(" HCLK = %d MHz", clocks.HCLK_Frequency / 1000000);
3806 cliPrintLinef(" PCLK1 = %d MHz", clocks.PCLK1_Frequency / 1000000);
3807 cliPrintLinef(" PCLK2 = %d MHz", clocks.PCLK2_Frequency / 1000000);
3808 #endif
3809 #endif // for if at32
3810 #endif // for SITL
3812 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
3813 hardwareSensorStatusNames[getHwGyroStatus()],
3814 hardwareSensorStatusNames[getHwAccelerometerStatus()],
3815 hardwareSensorStatusNames[getHwCompassStatus()],
3816 hardwareSensorStatusNames[getHwBarometerStatus()],
3817 hardwareSensorStatusNames[getHwRangefinderStatus()],
3818 hardwareSensorStatusNames[getHwOpticalFlowStatus()],
3819 hardwareSensorStatusNames[getHwGPSStatus()]
3822 #ifdef USE_ESC_SENSOR
3823 uint8_t motorCount = getMotorCount();
3824 if (STATE(ESC_SENSOR_ENABLED) && motorCount > 0) {
3825 cliPrintLinef("ESC Temperature(s): Motor Count = %d", motorCount);
3826 for (uint8_t i = 0; i < motorCount; i++) {
3827 const escSensorData_t *escState = getEscTelemetry(i); //Get ESC telemetry
3828 cliPrintf("ESC %d: %d\260C, ", i, escState->temperature);
3830 cliPrintLinefeed();
3832 #endif
3834 #ifdef USE_SDCARD
3835 cliSdInfo(NULL);
3836 #endif
3837 #ifdef USE_I2C
3838 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3839 #elif !defined(SITL_BUILD)
3840 const uint16_t i2cErrorCounter = 0;
3841 #endif
3843 #ifdef STACK_CHECK
3844 cliPrintf("Stack used: %d, ", stackUsedSize());
3845 #endif
3846 #if !defined(SITL_BUILD)
3847 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
3849 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
3850 #endif
3851 #if defined(USE_ADC) && !defined(SITL_BUILD)
3852 static char * adcFunctions[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
3853 cliPrintLine("ADC channel usage:");
3854 for (int i = 0; i < ADC_FUNCTION_COUNT; i++) {
3855 cliPrintf(" %8s :", adcFunctions[i]);
3857 cliPrint(" configured = ");
3858 if (adcChannelConfig()->adcFunctionChannel[i] == ADC_CHN_NONE) {
3859 cliPrint("none");
3861 else {
3862 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel[i]);
3865 cliPrint(", used = ");
3866 if (adcGetFunctionChannelAllocation(i) == ADC_CHN_NONE) {
3867 cliPrintLine("none");
3869 else {
3870 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i));
3873 #endif
3875 cliPrintf("System load: %d", averageSystemLoadPercent);
3876 const timeDelta_t pidTaskDeltaTime = getTaskDeltaTime(TASK_PID);
3877 const int pidRate = pidTaskDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)pidTaskDeltaTime));
3878 const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
3879 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3880 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime, pidRate, rxRate, systemRate);
3881 #if !defined(CLI_MINIMAL_VERBOSITY)
3882 cliPrint("Arming disabled flags:");
3883 uint32_t flags = armingFlags & ARMING_DISABLED_ALL_FLAGS;
3884 while (flags) {
3885 int bitpos = ffs(flags) - 1;
3886 flags &= ~(1 << bitpos);
3887 if (bitpos > 6) cliPrintf(" %s", armingDisableFlagNames[bitpos - 7]);
3889 cliPrintLinefeed();
3890 if (armingFlags & ARMING_DISABLED_INVALID_SETTING) {
3891 unsigned invalidIndex;
3892 if (!settingsValidate(&invalidIndex)) {
3893 settingGetName(settingGet(invalidIndex), buf);
3894 cliPrintErrorLinef("Invalid setting: %s", buf);
3897 #else
3898 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags & ARMING_DISABLED_ALL_FLAGS);
3899 #endif
3901 #if !defined(CLI_MINIMAL_VERBOSITY)
3902 cliPrint("OSD: ");
3903 #if defined(USE_OSD)
3904 displayPort_t *osdDisplayPort = osdGetDisplayPort();
3905 if (osdDisplayPort != NULL) {
3906 cliPrintf("%s [%u x %u]", osdDisplayPort->displayPortType, osdDisplayPort->cols, osdDisplayPort->rows);
3907 } else {
3908 cliPrint("not enabled");
3910 #else
3911 cliPrint("not used");
3912 #endif
3913 cliPrintLinefeed();
3915 cliPrint("VTX: ");
3916 #if defined(USE_VTX_CONTROL)
3917 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
3918 vtxDeviceOsdInfo_t osdInfo;
3919 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo);
3920 cliPrintf("band: %c, chan: %s, power: %c", osdInfo.bandLetter, osdInfo.channelName, osdInfo.powerIndexLetter);
3922 if (osdInfo.powerMilliwatt) {
3923 cliPrintf(" (%d mW)", osdInfo.powerMilliwatt);
3926 if (osdInfo.frequency) {
3927 cliPrintf(", freq: %d MHz", osdInfo.frequency);
3930 else {
3931 cliPrint("not detected");
3933 #else
3934 cliPrint("no VTX control");
3935 #endif
3937 cliPrintLinefeed();
3938 #endif
3940 if (featureConfigured(FEATURE_GPS) && isGpsUblox()) {
3941 cliPrint("GPS: ");
3942 cliPrintf("HW Version: %s Proto: %d.%02d Baud: %d", getGpsHwVersion(), getGpsProtoMajorVersion(), getGpsProtoMinorVersion(), getGpsBaudrate());
3943 if(ubloxVersionLT(15, 0)) {
3944 cliPrintf(" (UBLOX Proto >= 15.0 required)");
3946 cliPrintLinefeed();
3947 cliPrintLinef(" SATS: %i", gpsSol.numSat);
3948 cliPrintLinef(" HDOP: %f", (double)(gpsSol.hdop / (float)HDOP_SCALE));
3949 cliPrintLinef(" EPH : %f m", (double)(gpsSol.eph / 100.0f));
3950 cliPrintLinef(" EPV : %f m", (double)(gpsSol.epv / 100.0f));
3951 //cliPrintLinef(" GNSS Capabilities: %d", gpsUbloxCapLastUpdate());
3952 cliPrintLinef(" GNSS Capabilities:");
3953 cliPrintLine(" GNSS Provider active/default");
3954 cliPrintLine(" GPS 1/1");
3955 if(gpsUbloxHasGalileo())
3956 cliPrintLinef(" Galileo %d/%d", gpsUbloxGalileoEnabled(), gpsUbloxGalileoDefault());
3957 if(gpsUbloxHasBeidou())
3958 cliPrintLinef(" BeiDou %d/%d", gpsUbloxBeidouEnabled(), gpsUbloxBeidouDefault());
3959 if(gpsUbloxHasGlonass())
3960 cliPrintLinef(" Glonass %d/%d", gpsUbloxGlonassEnabled(), gpsUbloxGlonassDefault());
3961 cliPrintLinef(" Max concurrent: %d", gpsUbloxMaxGnss());
3964 // If we are blocked by PWM init - provide more information
3965 if (getPwmInitError() != PWM_INIT_ERROR_NONE) {
3966 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3970 static void cliTasks(char *cmdline)
3972 UNUSED(cmdline);
3973 int maxLoadSum = 0;
3974 int averageLoadSum = 0;
3975 cfCheckFuncInfo_t checkFuncInfo;
3977 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3978 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3979 cfTaskInfo_t taskInfo;
3980 getTaskInfo(taskId, &taskInfo);
3981 if (taskInfo.isEnabled) {
3982 const int taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3983 const int maxLoad = (taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3984 const int averageLoad = (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3985 if (taskId != TASK_SERIAL) {
3986 maxLoadSum += maxLoad;
3987 averageLoadSum += averageLoad;
3989 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
3990 taskId, taskInfo.taskName, taskFrequency, (uint32_t)taskInfo.maxExecutionTime, (uint32_t)taskInfo.averageExecutionTime,
3991 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, (uint32_t)taskInfo.totalExecutionTime / 1000);
3994 getCheckFuncInfo(&checkFuncInfo);
3995 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo.maxExecutionTime, (uint32_t)checkFuncInfo.averageExecutionTime, (uint32_t)checkFuncInfo.totalExecutionTime / 1000);
3996 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3999 static void cliVersion(char *cmdline)
4001 UNUSED(cmdline);
4003 cliPrintLinef("# %s/%s %s %s / %s (%s) %s",
4004 FC_FIRMWARE_NAME,
4005 targetName,
4006 FC_VERSION_STRING,
4007 buildDate,
4008 buildTime,
4009 shortGitRevision,
4010 FC_VERSION_TYPE
4012 cliPrintLinef("# GCC-%s",
4013 compilerVersion
4017 static void cliMemory(char *cmdline)
4019 UNUSED(cmdline);
4020 cliPrintLinef("Dynamic memory usage:");
4021 for (unsigned i = 0; i < OWNER_TOTAL_COUNT; i++) {
4022 const char * owner = ownerNames[i];
4023 const uint32_t memUsed = memGetUsedBytesByOwner(i);
4025 if (memUsed) {
4026 cliPrintLinef("%s : %d bytes", owner, memUsed);
4031 static void cliResource(char *cmdline)
4033 UNUSED(cmdline);
4034 cliPrintLinef("IO:\r\n----------------------");
4035 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
4036 const char* owner;
4037 owner = ownerNames[ioRecs[i].owner];
4039 const char* resource;
4040 resource = resourceNames[ioRecs[i].resource];
4042 if (ioRecs[i].index > 0) {
4043 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, ioRecs[i].index, resource);
4044 } else {
4045 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, resource);
4050 static void backupConfigs(void)
4052 // make copies of configs to do differencing
4053 PG_FOREACH(pg) {
4054 if (pgIsProfile(pg)) {
4055 memcpy(pg->copy, pg->address, pgSize(pg) * MAX_PROFILE_COUNT);
4056 } else {
4057 memcpy(pg->copy, pg->address, pgSize(pg));
4062 static void restoreConfigs(void)
4064 PG_FOREACH(pg) {
4065 if (pgIsProfile(pg)) {
4066 memcpy(pg->address, pg->copy, pgSize(pg) * MAX_PROFILE_COUNT);
4067 } else {
4068 memcpy(pg->address, pg->copy, pgSize(pg));
4073 static void printConfig(const char *cmdline, bool doDiff)
4075 uint8_t dumpMask = DUMP_MASTER;
4076 const char *options;
4077 if ((options = checkCommand(cmdline, "master"))) {
4078 dumpMask = DUMP_MASTER; // only
4079 } else if ((options = checkCommand(cmdline, "control_profile"))) {
4080 dumpMask = DUMP_CONTROL_PROFILE; // only
4081 } else if ((options = checkCommand(cmdline, "mixer_profile"))) {
4082 dumpMask = DUMP_MIXER_PROFILE; // only
4083 } else if ((options = checkCommand(cmdline, "battery_profile"))) {
4084 dumpMask = DUMP_BATTERY_PROFILE; // only
4085 } else if ((options = checkCommand(cmdline, "all"))) {
4086 dumpMask = DUMP_ALL; // all profiles and rates
4087 } else {
4088 options = cmdline;
4091 if (doDiff) {
4092 dumpMask = dumpMask | DO_DIFF;
4095 const int currentControlProfileIndexSave = getConfigProfile();
4096 const int currentMixerProfileIndexSave = getConfigMixerProfile();
4097 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
4098 backupConfigs();
4099 // reset all configs to defaults to do differencing
4100 resetConfigs();
4101 // restore the profile indices, since they should not be reset for proper comparison
4102 setConfigProfile(currentControlProfileIndexSave);
4103 setConfigMixerProfile(currentMixerProfileIndexSave);
4104 setConfigBatteryProfile(currentBatteryProfileIndexSave);
4106 if (checkCommand(options, "showdefaults")) {
4107 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
4110 #ifdef USE_CLI_BATCH
4111 bool batchModeEnabled = false;
4112 #endif
4114 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
4115 cliPrintHashLine("version");
4116 cliVersion(NULL);
4118 #ifdef USE_CLI_BATCH
4119 cliPrintHashLine("start the command batch");
4120 cliPrintLine("batch start");
4121 batchModeEnabled = true;
4122 #endif
4124 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
4125 #ifndef CLI_MINIMAL_VERBOSITY
4126 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
4127 #else
4128 cliPrintLinef("defaults noreboot");
4129 #endif
4132 cliPrintHashLine("resources");
4133 //printResource(dumpMask, &defaultConfig);
4135 cliPrintHashLine("Timer overrides");
4136 printTimerOutputModes(dumpMask, timerOverrides_CopyArray, timerOverrides(0), -1);
4138 // print servo parameters
4139 cliPrintHashLine("Outputs [servo]");
4140 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
4142 #if defined(USE_SAFE_HOME)
4143 cliPrintHashLine("safehome");
4144 printSafeHomes(dumpMask, safeHomeConfig_CopyArray, safeHomeConfig(0));
4145 #endif
4147 #ifdef USE_FW_AUTOLAND
4148 cliPrintHashLine("Fixed Wing Approach");
4149 printFwAutolandApproach(dumpMask, fwAutolandApproachConfig_CopyArray, fwAutolandApproachConfig(0));
4150 #endif
4152 cliPrintHashLine("features");
4153 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
4155 #if defined(BEEPER) || defined(USE_DSHOT)
4156 cliPrintHashLine("beeper");
4157 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
4158 #endif
4160 #ifdef USE_BLACKBOX
4161 cliPrintHashLine("blackbox");
4162 printBlackbox(dumpMask, &blackboxConfig_Copy, blackboxConfig());
4163 #endif
4165 cliPrintHashLine("Receiver: Channel map");
4166 printMap(dumpMask, &rxConfig_Copy, rxConfig());
4168 cliPrintHashLine("Ports");
4169 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
4171 #ifdef USE_LED_STRIP
4172 cliPrintHashLine("LEDs");
4173 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
4175 cliPrintHashLine("LED color");
4176 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
4178 cliPrintHashLine("LED mode_color");
4179 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
4180 #endif
4182 cliPrintHashLine("Modes [aux]");
4183 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
4185 cliPrintHashLine("Adjustments [adjrange]");
4186 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
4188 cliPrintHashLine("Receiver rxrange");
4189 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
4191 #ifdef USE_TEMPERATURE_SENSOR
4192 cliPrintHashLine("temp_sensor");
4193 printTempSensor(dumpMask, tempSensorConfig_CopyArray, tempSensorConfig(0));
4194 #endif
4196 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4197 cliPrintHashLine("Mission Control Waypoints [wp]");
4198 printWaypoints(dumpMask, posControl.waypointList, nonVolatileWaypointList(0));
4199 #endif
4201 #ifdef USE_OSD
4202 cliPrintHashLine("OSD [osd_layout]");
4203 printOsdLayout(dumpMask, &osdLayoutsConfig_Copy, osdLayoutsConfig(), -1, -1);
4204 #endif
4206 #ifdef USE_PROGRAMMING_FRAMEWORK
4207 cliPrintHashLine("Programming: logic");
4208 printLogic(dumpMask, logicConditions_CopyArray, logicConditions(0), -1);
4210 cliPrintHashLine("Programming: global variables");
4211 printGvar(dumpMask, globalVariableConfigs_CopyArray, globalVariableConfigs(0));
4213 cliPrintHashLine("Programming: PID controllers");
4214 printPid(dumpMask, programmingPids_CopyArray, programmingPids(0));
4215 #endif
4216 #ifdef USE_PROGRAMMING_FRAMEWORK
4217 cliPrintHashLine("OSD: custom elements");
4218 printOsdCustomElements(dumpMask, osdCustomElements_CopyArray, osdCustomElements(0));
4219 #endif
4221 cliPrintHashLine("master");
4222 dumpAllValues(MASTER_VALUE, dumpMask);
4224 if (dumpMask & DUMP_ALL) {
4225 // dump all profiles
4226 const int currentControlProfileIndexSave = getConfigProfile();
4227 const int currentMixerProfileIndexSave = getConfigMixerProfile();
4228 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
4229 for (int ii = 0; ii < MAX_PROFILE_COUNT; ++ii) {
4230 cliDumpControlProfile(ii, dumpMask);
4232 for (int ii = 0; ii < MAX_MIXER_PROFILE_COUNT; ++ii) {
4233 cliDumpMixerProfile(ii, dumpMask);
4235 for (int ii = 0; ii < MAX_BATTERY_PROFILE_COUNT; ++ii) {
4236 cliDumpBatteryProfile(ii, dumpMask);
4238 setConfigProfile(currentControlProfileIndexSave);
4239 setConfigMixerProfile(currentMixerProfileIndexSave);
4240 setConfigBatteryProfile(currentBatteryProfileIndexSave);
4242 cliPrintHashLine("restore original profile selection");
4243 cliPrintLinef("control_profile %d", currentControlProfileIndexSave + 1);
4244 cliPrintLinef("mixer_profile %d", currentMixerProfileIndexSave + 1);
4245 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave + 1);
4247 #ifdef USE_CLI_BATCH
4248 batchModeEnabled = false;
4249 #endif
4250 } else {
4251 // dump just the current profiles
4252 cliDumpControlProfile(getConfigProfile(), dumpMask);
4253 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask);
4254 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
4258 if (dumpMask & DUMP_CONTROL_PROFILE) {
4259 cliDumpControlProfile(getConfigProfile(), dumpMask);
4262 if (dumpMask & DUMP_MIXER_PROFILE) {
4263 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask);
4266 if (dumpMask & DUMP_BATTERY_PROFILE) {
4267 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
4270 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
4271 cliPrintHashLine("save configuration\r\nsave");
4274 #ifdef USE_CLI_BATCH
4275 if (batchModeEnabled) {
4276 cliPrintHashLine("end the command batch");
4277 cliPrintLine("batch end");
4279 #endif
4281 // restore configs from copies
4282 restoreConfigs();
4285 static void cliDump(char *cmdline)
4287 printConfig(cmdline, false);
4290 static void cliDiff(char *cmdline)
4292 printConfig(cmdline, true);
4295 #ifdef USE_USB_MSC
4296 static void cliMsc(char *cmdline)
4298 UNUSED(cmdline);
4300 if (false
4301 #ifdef USE_SDCARD
4302 || sdcard_isFunctional()
4303 #endif
4304 #ifdef USE_FLASHFS
4305 || flashfsGetSize() > 0
4306 #endif
4308 cliPrintHashLine("restarting in mass storage mode");
4309 cliPrint("\r\nRebooting");
4310 bufWriterFlush(cliWriter);
4311 delay(1000);
4312 waitForSerialPortToFinishTransmitting(cliPort);
4313 stopPwmAllMotors();
4314 systemResetRequest(RESET_MSC_REQUEST);
4315 } else {
4316 cliPrint("\r\nStorage not present or failed to initialize!");
4317 bufWriterFlush(cliWriter);
4320 #endif
4323 typedef struct {
4324 const char *name;
4325 #ifndef SKIP_CLI_COMMAND_HELP
4326 const char *description;
4327 const char *args;
4328 #endif
4329 void (*func)(char *cmdline);
4330 } clicmd_t;
4332 #ifndef SKIP_CLI_COMMAND_HELP
4333 #define CLI_COMMAND_DEF(name, description, args, method) \
4335 name , \
4336 description , \
4337 args , \
4338 method \
4340 #else
4341 #define CLI_COMMAND_DEF(name, description, args, method) \
4343 name, \
4344 method \
4346 #endif
4348 static void cliCmdDebug(char *arg)
4350 UNUSED(arg);
4351 if (debugMode != DEBUG_NONE) {
4352 cliPrintLinef("Debug fields: [%s (%i)]", debugMode < DEBUG_COUNT ? debugModeNames[debugMode] : "unknown", debugMode);
4353 for (int i = 0; i < DEBUG32_VALUE_COUNT; i++) {
4354 cliPrintLinef("debug[%d] = %d", i, debug[i]);
4356 } else {
4357 cliPrintLine("Debug mode is disabled");
4362 #if defined(USE_GPS) && defined(USE_GPS_PROTO_UBLOX)
4364 static const char* _ubloxGetSigId(uint8_t gnssId, uint8_t sigId)
4366 if(gnssId == 0) {
4367 switch(sigId) {
4368 case 0: return "GPS L1C/A";
4369 case 3: return "GPS L2 CL";
4370 case 4: return "GPS L2 CM";
4371 case 6: return "GPS L5 I";
4372 case 7: return "GPS L5 Q";
4373 default: return "GPS Unknown";
4375 } else if(gnssId == 1) {
4376 switch(sigId) {
4377 case 0: return "SBAS L1C/A";
4378 default: return "SBAS Unknown";
4380 } else if(gnssId == 2) {
4381 switch(sigId) {
4382 case 0: return "Galileo E1 C";
4383 case 1: return "Galileo E1 B";
4384 case 3: return "Galileo E5 al";
4385 case 4: return "Galileo E5 aQ";
4386 case 5: return "Galileo E5 bl";
4387 case 6: return "Galileo E5 bQ";
4388 default: return "Galileo Unknown";
4390 } else if(gnssId == 3) {
4391 switch(sigId) {
4392 case 0: return "BeiDou B1I D1";
4393 case 1: return "BeiDou B1I D2";
4394 case 2: return "BeiDou B2I D1";
4395 case 3: return "BeiDou B2I D2";
4396 case 5: return "BeiDou B1C";
4397 case 7: return "BeiDou B2a";
4398 default: return "BeiDou Unknown";
4400 } else if(gnssId == 5) {
4401 switch(sigId) {
4402 case 0: return "QZSS L1C/A";
4403 case 1: return "QZSS L1S";
4404 case 4: return "QZSS L2 CM";
4405 case 5: return "QZSS L2 CL";
4406 case 8: return "QZSS L5 I";
4407 case 9: return "QZSS L5 Q";
4408 default: return "QZSS Unknown";
4410 } else if(gnssId == 6) {
4411 switch(sigId) {
4412 case 0: return "GLONASS L1 OF";
4413 case 2: return "GLONASS L2 OF";
4414 default: return "GLONASS Unknown";
4418 return "Unknown GNSS/SigId";
4421 static const char *_ubloxGetQuality(uint8_t quality)
4423 switch(quality) {
4424 case UBLOX_SIG_QUALITY_NOSIGNAL: return "No signal";
4425 case UBLOX_SIG_QUALITY_SEARCHING: return "Searching signal...";
4426 case UBLOX_SIG_QUALITY_ACQUIRED: return "Signal acquired";
4427 case UBLOX_SIG_QUALITY_UNUSABLE: return "Signal detected but unusable";
4428 case UBLOX_SIG_QUALITY_CODE_LOCK_TIME_SYNC: return "Code locked and time sync";
4429 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC:
4430 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC2:
4431 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC3:
4432 return "Code and carrier locked and time sync";
4433 default: return "Unknown";
4437 static void cliUbloxPrintSatelites(char *arg)
4439 UNUSED(arg);
4440 if(!isGpsUblox() /*|| !(gpsState.flags.sig || gpsState.flags.sat)*/) {
4441 cliPrint("GPS is not UBLOX or does not report satelites.");
4442 return;
4445 cliPrintLine("UBLOX Satelites");
4447 for(int i = 0; i < UBLOX_MAX_SIGNALS; ++i)
4449 const ubx_nav_sig_info *sat = gpsGetUbloxSatelite(i);
4450 if(sat == NULL) {
4451 continue;
4454 cliPrintLinef("satelite[%d]: %d:%d", i+1, sat->gnssId, sat->svId);
4455 cliPrintLinef("sigId: %d (%s)", sat->sigId, _ubloxGetSigId(sat->gnssId, sat->sigId));
4456 cliPrintLinef("signal strength: %i dbHz", sat->cno);
4457 cliPrintLinef("quality: %i (%s)", sat->quality, _ubloxGetQuality(sat->quality));
4458 //cliPrintLinef("Correlation: %i", sat->corrSource);
4459 //cliPrintLinef("Iono model: %i", sat->ionoModel);
4460 cliPrintLinef("signal flags: 0x%02X", sat->sigFlags);
4461 switch(sat->sigFlags & UBLOX_SIG_HEALTH_MASK) {
4462 case UBLOX_SIG_HEALTH_HEALTHY:
4463 cliPrintLine("signal: Healthy");
4464 break;
4465 case UBLOX_SIG_HEALTH_UNHEALTHY:
4466 cliPrintLine("signal: Unhealthy");
4467 break;
4468 case UBLOX_SIG_HEALTH_UNKNOWN:
4469 default:
4470 cliPrintLinef("signal: Unknown (0x%X)", sat->sigFlags & UBLOX_SIG_HEALTH_MASK);
4471 break;
4473 cliPrintLinefeed();
4476 #endif
4478 static void cliHelp(char *cmdline);
4480 // should be sorted a..z for bsearch()
4481 const clicmd_t cmdTable[] = {
4482 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
4483 #if defined(USE_ASSERT)
4484 CLI_COMMAND_DEF("assert", "", NULL, cliAssert),
4485 #endif
4486 CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
4487 #ifdef USE_CLI_BATCH
4488 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
4489 #endif
4490 #if defined(BEEPER) || defined(USE_DSHOT)
4491 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
4492 "\t<+|->[name]", cliBeeper),
4493 #endif
4494 #if defined (USE_SERIALRX_SRXL2)
4495 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
4496 #endif
4497 #if defined(USE_BOOTLOG)
4498 CLI_COMMAND_DEF("bootlog", "show boot events", NULL, cliBootlog),
4499 #endif
4500 #ifdef USE_LED_STRIP
4501 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
4502 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
4503 #endif
4504 CLI_COMMAND_DEF("cli_delay", "CLI Delay", "Delay in ms", cliDelay),
4505 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
4506 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL, cliDfu),
4507 CLI_COMMAND_DEF("diff", "list configuration changes from default",
4508 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDiff),
4509 CLI_COMMAND_DEF("dump", "dump configuration",
4510 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDump),
4511 #ifdef USE_RX_ELERES
4512 CLI_COMMAND_DEF("eleres_bind", NULL, NULL, cliEleresBind),
4513 #endif // USE_RX_ELERES
4514 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
4515 CLI_COMMAND_DEF("feature", "configure features",
4516 "list\r\n"
4517 "\t<+|->[name]", cliFeature),
4518 #ifdef USE_BLACKBOX
4519 CLI_COMMAND_DEF("blackbox", "configure blackbox fields",
4520 "list\r\n"
4521 "\t<+|->[name]", cliBlackbox),
4522 #endif
4523 #ifdef USE_FLASHFS
4524 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
4525 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
4526 #ifdef USE_FLASH_TOOLS
4527 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
4528 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
4529 #endif
4530 #endif
4531 #ifdef USE_FW_AUTOLAND
4532 CLI_COMMAND_DEF("fwapproach", "Fixed Wing Approach Settings", NULL, cliFwAutolandApproach),
4533 #endif
4534 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
4535 #ifdef USE_GPS
4536 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
4537 CLI_COMMAND_DEF("gpssats", "show GPS satellites", NULL, cliUbloxPrintSatelites),
4538 #endif
4539 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
4540 #ifdef USE_LED_STRIP
4541 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
4542 CLI_COMMAND_DEF("ledpinpwm", "start/stop PWM on LED pin, 0..100 duty ratio", "[<value>]\r\n", cliLedPinPWM),
4543 #endif
4544 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
4545 CLI_COMMAND_DEF("memory", "view memory usage", NULL, cliMemory),
4546 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
4547 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
4548 #ifdef USE_USB_MSC
4549 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
4550 #endif
4551 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]\r\n", cliPlaySound),
4552 CLI_COMMAND_DEF("control_profile", "change control profile", "[<index>]", cliControlProfile),
4553 CLI_COMMAND_DEF("mixer_profile", "change mixer profile", "[<index>]", cliMixerProfile),
4554 CLI_COMMAND_DEF("battery_profile", "change battery profile", "[<index>]", cliBatteryProfile),
4555 CLI_COMMAND_DEF("resource", "view currently used resources", NULL, cliResource),
4556 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
4557 #if defined(USE_SAFE_HOME)
4558 CLI_COMMAND_DEF("safehome", "safe home list", NULL, cliSafeHomes),
4559 #endif
4560 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
4561 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
4562 #ifdef USE_SERIAL_PASSTHROUGH
4563 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] [options]: passthrough to serial", cliSerialPassthrough),
4564 #endif
4565 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
4566 #ifdef USE_PROGRAMMING_FRAMEWORK
4567 CLI_COMMAND_DEF("logic", "configure logic conditions",
4568 "<rule> <enabled> <activatorId> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
4569 "\treset\r\n", cliLogic),
4571 CLI_COMMAND_DEF("gvar", "configure global variables",
4572 "<gvar> <default> <min> <max>\r\n"
4573 "\treset\r\n", cliGvar),
4575 CLI_COMMAND_DEF("pid", "configurable PID controllers",
4576 "<#> <enabled> <setpoint type> <setpoint value> <measurement type> <measurement value> <P gain> <I gain> <D gain> <FF gain>\r\n"
4577 "\treset\r\n", cliPid),
4579 CLI_COMMAND_DEF("osd_custom_elements", "configurable OSD custom elements",
4580 "<#> <part0 type> <part0 value> <part1 type> <part1 value> <part2 type> <part2 value> <visibility type> <visibility value> <text>\r\n"
4581 , osdCustom),
4582 #endif
4583 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
4584 CLI_COMMAND_DEF("smix", "servo mixer",
4585 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
4586 "\treset\r\n", cliServoMix),
4587 #ifdef USE_SDCARD
4588 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
4589 #endif
4590 CLI_COMMAND_DEF("showdebug", "Show debug fields.", NULL, cliCmdDebug),
4591 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
4592 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
4593 #ifdef USE_TEMPERATURE_SENSOR
4594 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL, cliTempSensor),
4595 #endif
4596 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
4597 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4598 CLI_COMMAND_DEF("wp", "waypoint list", NULL, cliWaypoints),
4599 #endif
4600 #ifdef USE_OSD
4601 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout),
4602 #endif
4603 CLI_COMMAND_DEF("timer_output_mode", "get or set the outputmode for a given timer.", "[<timer> [<AUTO|MOTORS|SERVOS>]]", cliTimerOutputMode),
4606 static void cliHelp(char *cmdline)
4608 UNUSED(cmdline);
4610 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
4611 cliPrint(cmdTable[i].name);
4612 #ifndef SKIP_CLI_COMMAND_HELP
4613 if (cmdTable[i].description) {
4614 cliPrintf(" - %s", cmdTable[i].description);
4616 if (cmdTable[i].args) {
4617 cliPrintf("\r\n\t%s", cmdTable[i].args);
4619 #endif
4620 cliPrintLinefeed();
4624 void cliProcess(void)
4626 if (!cliWriter) {
4627 return;
4630 // Be a little bit tricky. Flush the last inputs buffer, if any.
4631 bufWriterFlush(cliWriter);
4633 while (serialRxBytesWaiting(cliPort)) {
4634 uint8_t c = serialRead(cliPort);
4635 if (c == '\t' || c == '?') {
4636 // do tab completion
4637 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
4638 uint32_t i = bufferIndex;
4639 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4640 if (bufferIndex && (sl_strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
4641 continue;
4642 if (!pstart)
4643 pstart = cmd;
4644 pend = cmd;
4646 if (pstart) { /* Buffer matches one or more commands */
4647 for (; ; bufferIndex++) {
4648 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
4649 break;
4650 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
4651 /* Unambiguous -- append a space */
4652 cliBuffer[bufferIndex++] = ' ';
4653 cliBuffer[bufferIndex] = '\0';
4654 break;
4656 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
4659 if (!bufferIndex || pstart != pend) {
4660 /* Print list of ambiguous matches */
4661 cliPrint("\r\033[K");
4662 for (cmd = pstart; cmd <= pend; cmd++) {
4663 cliPrint(cmd->name);
4664 cliWrite('\t');
4666 cliPrompt();
4667 i = 0; /* Redraw prompt */
4669 for (; i < bufferIndex; i++)
4670 cliWrite(cliBuffer[i]);
4671 } else if (!bufferIndex && c == 4) { // CTRL-D
4672 cliExit(cliBuffer);
4673 return;
4674 } else if (c == 12) { // NewPage / CTRL-L
4675 // clear screen
4676 cliPrint("\033[2J\033[1;1H");
4677 cliPrompt();
4678 } else if (bufferIndex && (c == '\n' || c == '\r')) {
4679 // enter pressed
4680 cliPrintLinefeed();
4682 // Strip comment starting with # from line
4683 char *p = cliBuffer;
4684 p = strchr(p, '#');
4685 if (NULL != p) {
4686 bufferIndex = (uint32_t)(p - cliBuffer);
4689 // Strip trailing whitespace
4690 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
4691 bufferIndex--;
4694 // Process non-empty lines
4695 if (bufferIndex > 0) {
4696 cliBuffer[bufferIndex] = 0; // null terminate
4698 const clicmd_t *cmd;
4699 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4700 if (!sl_strncasecmp(cliBuffer, cmd->name, strlen(cmd->name)) // command names match
4701 && !sl_isalnum((unsigned)cliBuffer[strlen(cmd->name)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
4702 break;
4704 if (cmd < cmdTable + ARRAYLEN(cmdTable))
4705 cmd->func(cliBuffer + strlen(cmd->name) + 1);
4706 else
4707 cliPrintError("Unknown command, try 'help'");
4708 bufferIndex = 0;
4711 ZERO_FARRAY(cliBuffer);
4713 // 'exit' will reset this flag, so we don't need to print prompt again
4714 if (!cliMode)
4715 return;
4717 cliPrompt();
4718 } else if (c == 127) {
4719 // backspace
4720 if (bufferIndex) {
4721 cliBuffer[--bufferIndex] = 0;
4722 cliPrint("\010 \010");
4724 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
4725 if (!bufferIndex && c == ' ')
4726 continue; // Ignore leading spaces
4727 cliBuffer[bufferIndex++] = c;
4728 cliWrite(c);
4733 void cliEnter(serialPort_t *serialPort)
4735 if (cliMode) {
4736 return;
4739 cliMode = true;
4740 cliPort = serialPort;
4741 setPrintfSerialPort(cliPort);
4742 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
4744 #ifndef CLI_MINIMAL_VERBOSITY
4745 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4746 #else
4747 cliPrintLine("\r\nCLI");
4748 #endif
4749 cliPrompt();
4751 #ifdef USE_CLI_BATCH
4752 resetCommandBatch();
4753 #endif
4755 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI);
4758 void cliInit(const serialConfig_t *serialConfig)
4760 UNUSED(serialConfig);