Blackbox device type 'file' (SITL) considered working when file handler is available
[inav.git] / src / main / fc / cli.c
blob580859fec0cb0ad78896d37d5dd5587dd7286b17
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 NULL
188 #endif
190 static const char *debugModeNames[DEBUG_COUNT] = {
191 "NONE",
192 "AGL",
193 "FLOW_RAW",
194 "FLOW",
195 "ALWAYS",
196 "SAG_COMP_VOLTAGE",
197 "VIBE",
198 "CRUISE",
199 "REM_FLIGHT_TIME",
200 "SMARTAUDIO",
201 "ACC",
202 "NAV_YAW",
203 "PCF8574",
204 "DYN_GYRO_LPF",
205 "AUTOLEVEL",
206 "ALTITUDE",
207 "AUTOTRIM",
208 "AUTOTUNE",
209 "RATE_DYNAMICS",
210 "LANDING",
211 "POS_EST",
212 "ADAPTIVE_FILTER",
213 "HEADTRACKER",
214 "GPS"
217 /* Sensor names (used in lookup tables for *_hardware settings and in status
218 command output) */
219 // sync with gyroSensor_e
220 static const char *const gyroNames[] = {
221 "NONE", "AUTO", "MPU6000", "MPU6500", "MPU9250", "BMI160",
222 "ICM20689", "BMI088", "ICM42605", "BMI270", "LSM6DXX", "FAKE"};
224 // sync this with sensors_e
225 static const char * const sensorTypeNames[] = {
226 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "PITOT", "OPFLOW", "GPS", "GPS+MAG", NULL
229 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER | SENSOR_PITOT | SENSOR_OPFLOW)
231 static const char * const hardwareSensorStatusNames[] = {
232 "NONE", "OK", "UNAVAILABLE", "FAILING"
235 static const char * const *sensorHardwareNames[] = {
236 gyroNames,
237 table_acc_hardware,
238 #ifdef USE_BARO
239 table_baro_hardware,
240 #else
241 NULL,
242 #endif
243 #ifdef USE_MAG
244 table_mag_hardware,
245 #else
246 NULL,
247 #endif
248 #ifdef USE_RANGEFINDER
249 table_rangefinder_hardware,
250 #else
251 NULL,
252 #endif
253 #ifdef USE_PITOT
254 table_pitot_hardware,
255 #else
256 NULL,
257 #endif
258 #ifdef USE_OPFLOW
259 table_opflow_hardware,
260 #else
261 NULL,
262 #endif
265 static void cliPrint(const char *str)
267 while (*str) {
268 bufWriterAppend(cliWriter, *str++);
272 static void cliPrintLinefeed(void)
274 cliPrint("\r\n");
275 if (cliDelayMs) {
276 delay(cliDelayMs);
280 static void cliPrintLine(const char *str)
282 cliPrint(str);
283 cliPrintLinefeed();
287 static void cliPrintError(const char *str)
289 cliPrint("### ERROR: ");
290 cliPrint(str);
291 #ifdef USE_CLI_BATCH
292 if (commandBatchActive) {
293 commandBatchError = true;
294 commandBatchErrorCount++;
296 #endif
299 static void cliPrintErrorLine(const char *str)
301 cliPrint("### ERROR: ");
302 cliPrintLine(str);
303 #ifdef USE_CLI_BATCH
304 if (commandBatchActive) {
305 commandBatchError = true;
306 commandBatchErrorCount++;
308 #endif
311 #ifdef CLI_MINIMAL_VERBOSITY
312 #define cliPrintHashLine(str)
313 #else
314 static void cliPrintHashLine(const char *str)
316 cliPrint("\r\n# ");
317 cliPrintLine(str);
319 #endif
321 static void cliPutp(void *p, char ch)
323 bufWriterAppend(p, ch);
326 typedef enum {
327 DUMP_MASTER = (1 << 0),
328 DUMP_CONTROL_PROFILE = (1 << 1),
329 DUMP_BATTERY_PROFILE = (1 << 2),
330 DUMP_MIXER_PROFILE = (1 << 3),
331 DUMP_ALL = (1 << 4),
332 DO_DIFF = (1 << 5),
333 SHOW_DEFAULTS = (1 << 6),
334 HIDE_UNUSED = (1 << 7)
335 } dumpFlags_e;
337 static void cliPrintfva(const char *format, va_list va)
339 tfp_format(cliWriter, cliPutp, format, va);
340 bufWriterFlush(cliWriter);
343 static void cliPrintLinefva(const char *format, va_list va)
345 tfp_format(cliWriter, cliPutp, format, va);
346 bufWriterFlush(cliWriter);
347 cliPrintLinefeed();
350 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
352 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
353 va_list va;
354 va_start(va, format);
355 cliPrintLinefva(format, va);
356 va_end(va);
357 return true;
358 } else {
359 return false;
363 static void cliWrite(uint8_t ch)
365 bufWriterAppend(cliWriter, ch);
368 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
370 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
371 cliWrite('#');
373 va_list va;
374 va_start(va, format);
375 cliPrintLinefva(format, va);
376 va_end(va);
377 return true;
378 } else {
379 return false;
383 static void cliPrintf(const char *format, ...)
385 va_list va;
386 va_start(va, format);
387 cliPrintfva(format, va);
388 va_end(va);
392 static void cliPrintLinef(const char *format, ...)
394 va_list va;
395 va_start(va, format);
396 cliPrintLinefva(format, va);
397 va_end(va);
400 static void cliPrintErrorVa(const char *format, va_list va)
402 cliPrint("### ERROR: ");
403 cliPrintfva(format, va);
404 va_end(va);
406 #ifdef USE_CLI_BATCH
407 if (commandBatchActive) {
408 commandBatchError = true;
409 commandBatchErrorCount++;
411 #endif
414 static void cliPrintErrorLinef(const char *format, ...)
416 va_list va;
417 va_start(va, format);
418 cliPrintErrorVa(format, va);
419 cliPrintLinefeed();
422 static void printValuePointer(const setting_t *var, const void *valuePointer, uint32_t full)
424 int32_t value = 0;
425 char buf[SETTING_MAX_NAME_LENGTH];
427 switch (SETTING_TYPE(var)) {
428 case VAR_UINT8:
429 value = *(uint8_t *)valuePointer;
430 break;
432 case VAR_INT8:
433 value = *(int8_t *)valuePointer;
434 break;
436 case VAR_UINT16:
437 value = *(uint16_t *)valuePointer;
438 break;
440 case VAR_INT16:
441 value = *(int16_t *)valuePointer;
442 break;
444 case VAR_UINT32:
445 value = *(uint32_t *)valuePointer;
446 break;
448 case VAR_FLOAT:
449 cliPrintf("%s", ftoa(*(float *)valuePointer, buf));
450 if (full) {
451 if (SETTING_MODE(var) == MODE_DIRECT) {
452 cliPrintf(" %s", ftoa((float)settingGetMin(var), buf));
453 cliPrintf(" %s", ftoa((float)settingGetMax(var), buf));
456 return; // return from case for float only
458 case VAR_STRING:
459 cliPrintf("%s", (const char *)valuePointer);
460 return;
463 switch (SETTING_MODE(var)) {
464 case MODE_DIRECT:
465 if (SETTING_TYPE(var) == VAR_UINT32)
466 cliPrintf("%u", value);
467 else
468 cliPrintf("%d", value);
469 if (full) {
470 if (SETTING_MODE(var) == MODE_DIRECT) {
471 cliPrintf(" %d %u", settingGetMin(var), settingGetMax(var));
474 break;
475 case MODE_LOOKUP:
477 const char *name = settingLookupValueName(var, value);
478 if (name) {
479 cliPrintf(name);
480 } else {
481 settingGetName(var, buf);
482 cliPrintErrorLinef("VALUE %d OUT OF RANGE FOR %s", (int)value, buf);
484 break;
489 static bool valuePtrEqualsDefault(const setting_t *value, const void *ptr, const void *ptrDefault)
491 bool result = false;
492 switch (SETTING_TYPE(value)) {
493 case VAR_UINT8:
494 result = *(uint8_t *)ptr == *(uint8_t *)ptrDefault;
495 break;
497 case VAR_INT8:
498 result = *(int8_t *)ptr == *(int8_t *)ptrDefault;
499 break;
501 case VAR_UINT16:
502 result = *(uint16_t *)ptr == *(uint16_t *)ptrDefault;
503 break;
505 case VAR_INT16:
506 result = *(int16_t *)ptr == *(int16_t *)ptrDefault;
507 break;
509 case VAR_UINT32:
510 result = *(uint32_t *)ptr == *(uint32_t *)ptrDefault;
511 break;
513 case VAR_FLOAT:
514 result = *(float *)ptr == *(float *)ptrDefault;
515 break;
517 case VAR_STRING:
518 result = strncmp(ptr, ptrDefault, settingGetStringMaxLength(value) + 1) == 0;
519 break;
521 return result;
524 static void dumpPgValue(const setting_t *value, uint8_t dumpMask)
526 char name[SETTING_MAX_NAME_LENGTH];
527 const char *format = "set %s = ";
528 const char *defaultFormat = "#set %s = ";
529 // During a dump, the PGs have been backed up to their "copy"
530 // regions and the actual values have been reset to its
531 // defaults. This means that settingGetValuePointer() will
532 // return the default value while settingGetCopyValuePointer()
533 // will return the actual value.
534 const void *valuePointer = settingGetCopyValuePointer(value);
535 const void *defaultValuePointer = settingGetValuePointer(value);
536 const bool equalsDefault = valuePtrEqualsDefault(value, valuePointer, defaultValuePointer);
537 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
538 settingGetName(value, name);
539 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
540 cliPrintf(defaultFormat, name);
541 // if the craftname has a leading space, then enclose the name in quotes
542 if (strcmp(name, "name") == 0 && ((const char *)valuePointer)[0] == ' ') {
543 cliPrintf("\"%s\"", (const char *)valuePointer);
544 } else {
545 printValuePointer(value, valuePointer, 0);
547 cliPrintLinefeed();
549 cliPrintf(format, name);
550 printValuePointer(value, valuePointer, 0);
551 cliPrintLinefeed();
555 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
557 for (unsigned i = 0; i < SETTINGS_TABLE_COUNT; i++) {
558 const setting_t *value = settingGet(i);
559 bufWriterFlush(cliWriter);
560 if (SETTING_SECTION(value) == valueSection) {
561 dumpPgValue(value, dumpMask);
566 static void cliPrintVar(const setting_t *var, uint32_t full)
568 const void *ptr = settingGetValuePointer(var);
570 printValuePointer(var, ptr, full);
573 static void cliPrintVarRange(const setting_t *var)
575 switch (SETTING_MODE(var)) {
576 case MODE_DIRECT:
577 if (SETTING_TYPE(var) == VAR_STRING) {
578 cliPrintLinef("Max. length: %u", settingGetStringMaxLength(var));
579 break;
581 cliPrintLinef("Allowed range: %d - %u", settingGetMin(var), settingGetMax(var));
582 break;
583 case MODE_LOOKUP:
585 const lookupTableEntry_t *tableEntry = settingLookupTable(var);
586 cliPrint("Allowed values:");
587 for (uint32_t i = 0; i < tableEntry->valueCount ; i++) {
588 if (i > 0)
589 cliPrint(",");
590 cliPrintf(" %s", tableEntry->values[i]);
592 cliPrintLinefeed();
594 break;
598 typedef union {
599 uint32_t uint_value;
600 int32_t int_value;
601 float float_value;
602 } int_float_value_t;
604 static void cliSetIntFloatVar(const setting_t *var, const int_float_value_t value)
606 void *ptr = settingGetValuePointer(var);
608 switch (SETTING_TYPE(var)) {
609 case VAR_UINT8:
610 case VAR_INT8:
611 *(int8_t *)ptr = value.int_value;
612 break;
614 case VAR_UINT16:
615 case VAR_INT16:
616 *(int16_t *)ptr = value.int_value;
617 break;
619 case VAR_UINT32:
620 *(uint32_t *)ptr = value.uint_value;
621 break;
623 case VAR_FLOAT:
624 *(float *)ptr = (float)value.float_value;
625 break;
627 case VAR_STRING:
628 // Handled by cliSet directly
629 break;
633 static void cliPrompt(void)
635 cliPrint("\r\n# ");
636 bufWriterFlush(cliWriter);
639 static void cliShowParseError(void)
641 cliPrintErrorLinef("Parse error");
644 static void cliShowArgumentRangeError(char *name, int min, int max)
646 cliPrintErrorLinef("%s must be between %d and %d", name, min, max);
649 static const char *nextArg(const char *currentArg)
651 const char *ptr = strchr(currentArg, ' ');
652 while (ptr && *ptr == ' ') {
653 ptr++;
656 return ptr;
659 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
661 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
662 ptr = nextArg(ptr);
663 if (ptr) {
664 int val = fastA2I(ptr);
665 val = CHANNEL_VALUE_TO_STEP(val);
666 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
667 if (argIndex == 0) {
668 range->startStep = val;
669 } else {
670 range->endStep = val;
672 (*validArgumentCount)++;
677 return ptr;
680 // Check if a string's length is zero
681 static bool isEmpty(const char *string)
683 return (string == NULL || *string == '\0') ? true : false;
686 #if defined(USE_ASSERT)
687 static void cliAssert(char *cmdline)
689 UNUSED(cmdline);
691 if (assertFailureLine) {
692 if (assertFailureFile) {
693 cliPrintErrorLinef("Assertion failed at line %d, file %s", assertFailureLine, assertFailureFile);
695 else {
696 cliPrintErrorLinef("Assertion failed at line %d", assertFailureLine);
698 #ifdef USE_CLI_BATCH
699 if (commandBatchActive) {
700 commandBatchError = true;
701 commandBatchErrorCount++;
703 #endif
705 else {
706 cliPrintLine("No assert() failed");
709 #endif
711 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
713 const char *format = "aux %u %u %u %u %u";
714 // print out aux channel settings
715 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
716 const modeActivationCondition_t *mac = &modeActivationConditions[i];
717 bool equalsDefault = false;
718 if (defaultModeActivationConditions) {
719 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
720 equalsDefault = mac->modeId == macDefault->modeId
721 && mac->auxChannelIndex == macDefault->auxChannelIndex
722 && mac->range.startStep == macDefault->range.startStep
723 && mac->range.endStep == macDefault->range.endStep;
724 const box_t *box = findBoxByActiveBoxId(macDefault->modeId);
725 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
727 box->permanentId,
728 macDefault->auxChannelIndex,
729 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
730 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep)
733 const box_t *box = findBoxByActiveBoxId(mac->modeId);
734 cliDumpPrintLinef(dumpMask, equalsDefault, format,
736 box->permanentId,
737 mac->auxChannelIndex,
738 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
739 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep)
744 static void cliAux(char *cmdline)
746 int i, val = 0;
747 const char *ptr;
749 if (isEmpty(cmdline)) {
750 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
751 } else {
752 ptr = cmdline;
753 i = fastA2I(ptr++);
754 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
755 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
756 uint8_t validArgumentCount = 0;
757 ptr = nextArg(ptr);
758 if (ptr) {
759 val = fastA2I(ptr);
760 if (val >= 0) {
761 const box_t *box = findBoxByPermanentId(val);
762 if (box != NULL) {
763 mac->modeId = box->boxId;
764 validArgumentCount++;
768 ptr = nextArg(ptr);
769 if (ptr) {
770 val = fastA2I(ptr);
771 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
772 mac->auxChannelIndex = val;
773 validArgumentCount++;
776 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
778 if (validArgumentCount != 4) {
779 memset(mac, 0, sizeof(modeActivationCondition_t));
781 } else {
782 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
787 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
789 const char *format = "serial %d %d %ld %ld %ld %ld";
790 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
791 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
792 continue;
794 bool equalsDefault = false;
795 if (serialConfigDefault) {
796 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
797 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
798 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
799 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
800 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
801 && serialConfig->portConfigs[i].peripheral_baudrateIndex == serialConfigDefault->portConfigs[i].peripheral_baudrateIndex;
802 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
803 serialConfigDefault->portConfigs[i].identifier,
804 serialConfigDefault->portConfigs[i].functionMask,
805 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
806 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
807 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
808 baudRates[serialConfigDefault->portConfigs[i].peripheral_baudrateIndex]
811 cliDumpPrintLinef(dumpMask, equalsDefault, format,
812 serialConfig->portConfigs[i].identifier,
813 serialConfig->portConfigs[i].functionMask,
814 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
815 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
816 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
817 baudRates[serialConfig->portConfigs[i].peripheral_baudrateIndex]
822 static void cliSerial(char *cmdline)
824 if (isEmpty(cmdline)) {
825 printSerial(DUMP_MASTER, serialConfig(), NULL);
826 return;
828 serialPortConfig_t portConfig;
830 serialPortConfig_t *currentConfig;
832 uint8_t validArgumentCount = 0;
834 const char *ptr = cmdline;
836 int val = fastA2I(ptr++);
837 currentConfig = serialFindPortConfiguration(val);
838 if (!currentConfig) {
839 // Invalid port ID
840 cliPrintErrorLinef("Invalid port ID %d", val);
841 return;
843 memcpy(&portConfig, currentConfig, sizeof(portConfig));
844 validArgumentCount++;
846 ptr = nextArg(ptr);
847 if (ptr) {
848 switch (*ptr) {
849 case '+':
850 // Add function
851 ptr++;
852 val = fastA2I(ptr);
853 portConfig.functionMask |= (1 << val);
854 break;
855 case '-':
856 // Remove function
857 ptr++;
858 val = fastA2I(ptr);
859 portConfig.functionMask &= 0xFFFFFFFF ^ (1 << val);
860 break;
861 default:
862 // Set functions
863 val = fastA2I(ptr);
864 portConfig.functionMask = val & 0xFFFFFFFF;
865 break;
867 validArgumentCount++;
870 for (int i = 0; i < 4; i ++) {
871 ptr = nextArg(ptr);
872 if (!ptr) {
873 break;
876 val = fastA2I(ptr);
878 uint8_t baudRateIndex = lookupBaudRateIndex(val);
879 if (baudRates[baudRateIndex] != (uint32_t) val) {
880 break;
883 switch (i) {
884 case 0:
885 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
886 portConfig.msp_baudrateIndex = baudRateIndex;
887 break;
888 case 1:
889 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
890 portConfig.gps_baudrateIndex = baudRateIndex;
891 break;
892 case 2:
893 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
894 portConfig.telemetry_baudrateIndex = baudRateIndex;
895 break;
896 case 3:
897 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
898 portConfig.peripheral_baudrateIndex = baudRateIndex;
899 break;
902 validArgumentCount++;
905 if (validArgumentCount < 2) {
906 cliShowParseError();
907 return;
910 memcpy(currentConfig, &portConfig, sizeof(portConfig));
913 #ifdef USE_SERIAL_PASSTHROUGH
914 static void cliSerialPassthrough(char *cmdline)
916 char * saveptr;
918 if (isEmpty(cmdline)) {
919 cliShowParseError();
920 return;
923 int id = -1;
924 uint32_t baud = 0;
925 unsigned mode = 0;
926 char* tok = strtok_r(cmdline, " ", &saveptr);
927 int index = 0;
929 while (tok != NULL) {
930 switch (index) {
931 case 0:
932 id = fastA2I(tok);
933 break;
934 case 1:
935 baud = fastA2I(tok);
936 break;
937 case 2:
938 if (strstr(tok, "rx") || strstr(tok, "RX"))
939 mode |= MODE_RX;
940 if (strstr(tok, "tx") || strstr(tok, "TX"))
941 mode |= MODE_TX;
942 break;
944 index++;
945 tok = strtok_r(NULL, " ", &saveptr);
948 serialPort_t *passThroughPort;
949 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
950 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
951 if (!baud) {
952 tfp_printf("Port %d is closed, must specify baud.\r\n", id);
953 return;
955 if (!mode)
956 mode = MODE_RXTX;
958 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
959 baud, mode,
960 SERIAL_NOT_INVERTED);
961 if (!passThroughPort) {
962 tfp_printf("Port %d could not be opened.\r\n", id);
963 return;
965 tfp_printf("Port %d opened, baud = %u.\r\n", id, (unsigned)baud);
966 } else {
967 passThroughPort = passThroughPortUsage->serialPort;
968 // If the user supplied a mode, override the port's mode, otherwise
969 // leave the mode unchanged. serialPassthrough() handles one-way ports.
970 tfp_printf("Port %d already open.\r\n", id);
971 if (mode && passThroughPort->mode != mode) {
972 tfp_printf("Adjusting mode from %d to %d.\r\n",
973 passThroughPort->mode, mode);
974 serialSetMode(passThroughPort, mode);
976 // If this port has a rx callback associated we need to remove it now.
977 // Otherwise no data will be pushed in the serial port buffer!
978 if (passThroughPort->rxCallback) {
979 tfp_printf("Removing rxCallback\r\n");
980 passThroughPort->rxCallback = 0;
984 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id);
986 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
988 #endif
990 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
992 const char *format = "adjrange %u %u %u %u %u %u %u";
993 // print out adjustment ranges channel settings
994 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
995 const adjustmentRange_t *ar = &adjustmentRanges[i];
996 bool equalsDefault = false;
997 if (defaultAdjustmentRanges) {
998 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
999 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
1000 && ar->range.startStep == arDefault->range.startStep
1001 && ar->range.endStep == arDefault->range.endStep
1002 && ar->adjustmentFunction == arDefault->adjustmentFunction
1003 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
1004 && ar->adjustmentIndex == arDefault->adjustmentIndex;
1005 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1007 arDefault->adjustmentIndex,
1008 arDefault->auxChannelIndex,
1009 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
1010 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
1011 arDefault->adjustmentFunction,
1012 arDefault->auxSwitchChannelIndex
1015 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1017 ar->adjustmentIndex,
1018 ar->auxChannelIndex,
1019 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1020 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1021 ar->adjustmentFunction,
1022 ar->auxSwitchChannelIndex
1027 static void cliAdjustmentRange(char *cmdline)
1029 int i, val = 0;
1030 const char *ptr;
1032 if (isEmpty(cmdline)) {
1033 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
1034 } else {
1035 ptr = cmdline;
1036 i = fastA2I(ptr++);
1037 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1038 adjustmentRange_t *ar = adjustmentRangesMutable(i);
1039 uint8_t validArgumentCount = 0;
1041 ptr = nextArg(ptr);
1042 if (ptr) {
1043 val = fastA2I(ptr);
1044 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1045 ar->adjustmentIndex = val;
1046 validArgumentCount++;
1049 ptr = nextArg(ptr);
1050 if (ptr) {
1051 val = fastA2I(ptr);
1052 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1053 ar->auxChannelIndex = val;
1054 validArgumentCount++;
1058 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1060 ptr = nextArg(ptr);
1061 if (ptr) {
1062 val = fastA2I(ptr);
1063 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1064 ar->adjustmentFunction = val;
1065 validArgumentCount++;
1068 ptr = nextArg(ptr);
1069 if (ptr) {
1070 val = fastA2I(ptr);
1071 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1072 ar->auxSwitchChannelIndex = val;
1073 validArgumentCount++;
1077 if (validArgumentCount != 6) {
1078 memset(ar, 0, sizeof(adjustmentRange_t));
1079 cliShowParseError();
1081 } else {
1082 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1087 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *primaryMotorMixer, const motorMixer_t *defaultprimaryMotorMixer)
1089 const char *format = "mmix %d %s %s %s %s";
1090 char buf0[FTOA_BUFFER_SIZE];
1091 char buf1[FTOA_BUFFER_SIZE];
1092 char buf2[FTOA_BUFFER_SIZE];
1093 char buf3[FTOA_BUFFER_SIZE];
1094 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1095 if (primaryMotorMixer[i].throttle == 0.0f)
1096 break;
1097 const float thr = primaryMotorMixer[i].throttle;
1098 const float roll = primaryMotorMixer[i].roll;
1099 const float pitch = primaryMotorMixer[i].pitch;
1100 const float yaw = primaryMotorMixer[i].yaw;
1101 bool equalsDefault = false;
1102 if (defaultprimaryMotorMixer) {
1103 const float thrDefault = defaultprimaryMotorMixer[i].throttle;
1104 const float rollDefault = defaultprimaryMotorMixer[i].roll;
1105 const float pitchDefault = defaultprimaryMotorMixer[i].pitch;
1106 const float yawDefault = defaultprimaryMotorMixer[i].yaw;
1107 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1109 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1111 ftoa(thrDefault, buf0),
1112 ftoa(rollDefault, buf1),
1113 ftoa(pitchDefault, buf2),
1114 ftoa(yawDefault, buf3));
1116 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1118 ftoa(thr, buf0),
1119 ftoa(roll, buf1),
1120 ftoa(pitch, buf2),
1121 ftoa(yaw, buf3));
1125 static void cliMotorMix(char *cmdline)
1127 int check = 0;
1128 const char *ptr;
1130 if (isEmpty(cmdline)) {
1131 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1132 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1133 // erase custom mixer
1134 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1135 primaryMotorMixerMutable(i)->throttle = 0.0f;
1137 } else {
1138 ptr = cmdline;
1139 uint32_t i = fastA2I(ptr); // get motor number
1140 if (i < MAX_SUPPORTED_MOTORS) {
1141 ptr = nextArg(ptr);
1142 if (ptr) {
1143 primaryMotorMixerMutable(i)->throttle = fastA2F(ptr);
1144 check++;
1146 ptr = nextArg(ptr);
1147 if (ptr) {
1148 primaryMotorMixerMutable(i)->roll = fastA2F(ptr);
1149 check++;
1151 ptr = nextArg(ptr);
1152 if (ptr) {
1153 primaryMotorMixerMutable(i)->pitch = fastA2F(ptr);
1154 check++;
1156 ptr = nextArg(ptr);
1157 if (ptr) {
1158 primaryMotorMixerMutable(i)->yaw = fastA2F(ptr);
1159 check++;
1161 if (check != 4) {
1162 cliShowParseError();
1163 } else {
1164 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1166 } else {
1167 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1172 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1174 const char *format = "rxrange %u %u %u";
1175 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1176 bool equalsDefault = false;
1177 if (defaultChannelRangeConfigs) {
1178 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1179 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1180 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1182 defaultChannelRangeConfigs[i].min,
1183 defaultChannelRangeConfigs[i].max
1186 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1188 channelRangeConfigs[i].min,
1189 channelRangeConfigs[i].max
1194 static void cliRxRange(char *cmdline)
1196 int i, validArgumentCount = 0;
1197 const char *ptr;
1199 if (isEmpty(cmdline)) {
1200 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1201 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1202 resetAllRxChannelRangeConfigurations();
1203 } else {
1204 ptr = cmdline;
1205 i = fastA2I(ptr);
1206 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1207 int rangeMin = 0, rangeMax = 0;
1209 ptr = nextArg(ptr);
1210 if (ptr) {
1211 rangeMin = fastA2I(ptr);
1212 validArgumentCount++;
1215 ptr = nextArg(ptr);
1216 if (ptr) {
1217 rangeMax = fastA2I(ptr);
1218 validArgumentCount++;
1221 if (validArgumentCount != 2) {
1222 cliShowParseError();
1223 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1224 cliShowParseError();
1225 } else {
1226 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1227 channelRangeConfig->min = rangeMin;
1228 channelRangeConfig->max = rangeMax;
1230 } else {
1231 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1236 #ifdef USE_TEMPERATURE_SENSOR
1237 static void printTempSensor(uint8_t dumpMask, const tempSensorConfig_t *tempSensorConfigs, const tempSensorConfig_t *defaultTempSensorConfigs)
1239 const char *format = "temp_sensor %u %u %s %d %d %u %s";
1240 for (uint8_t i = 0; i < MAX_TEMP_SENSORS; i++) {
1241 bool equalsDefault = false;
1242 char label[5], hex_address[17];
1243 strncpy(label, tempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN);
1244 label[4] = '\0';
1245 tempSensorAddressToString(tempSensorConfigs[i].address, hex_address);
1246 if (defaultTempSensorConfigs) {
1247 equalsDefault = tempSensorConfigs[i].type == defaultTempSensorConfigs[i].type
1248 && tempSensorConfigs[i].address == defaultTempSensorConfigs[i].address
1249 && tempSensorConfigs[i].osdSymbol == defaultTempSensorConfigs[i].osdSymbol
1250 && !memcmp(tempSensorConfigs[i].label, defaultTempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN)
1251 && tempSensorConfigs[i].alarm_min == defaultTempSensorConfigs[i].alarm_min
1252 && tempSensorConfigs[i].alarm_max == defaultTempSensorConfigs[i].alarm_max;
1253 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1255 defaultTempSensorConfigs[i].type,
1256 "0",
1257 defaultTempSensorConfigs[i].alarm_min,
1258 defaultTempSensorConfigs[i].alarm_max,
1263 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1265 tempSensorConfigs[i].type,
1266 hex_address,
1267 tempSensorConfigs[i].alarm_min,
1268 tempSensorConfigs[i].alarm_max,
1269 tempSensorConfigs[i].osdSymbol,
1270 label
1275 static void cliTempSensor(char *cmdline)
1277 if (isEmpty(cmdline)) {
1278 printTempSensor(DUMP_MASTER, tempSensorConfig(0), NULL);
1279 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1280 resetTempSensorConfig();
1281 } else {
1282 int16_t i;
1283 const char *ptr = cmdline, *label;
1284 int16_t type=0, alarm_min=0, alarm_max=0;
1285 bool addressValid = false;
1286 uint64_t address;
1287 int8_t osdSymbol=0;
1288 uint8_t validArgumentCount = 0;
1289 i = fastA2I(ptr);
1290 if (i >= 0 && i < MAX_TEMP_SENSORS) {
1292 ptr = nextArg(ptr);
1293 if (ptr) {
1294 type = fastA2I(ptr);
1295 validArgumentCount++;
1298 ptr = nextArg(ptr);
1299 if (ptr) {
1300 addressValid = tempSensorStringToAddress(ptr, &address);
1301 validArgumentCount++;
1304 ptr = nextArg(ptr);
1305 if (ptr) {
1306 alarm_min = fastA2I(ptr);
1307 validArgumentCount++;
1310 ptr = nextArg(ptr);
1311 if (ptr) {
1312 alarm_max = fastA2I(ptr);
1313 validArgumentCount++;
1316 ptr = nextArg(ptr);
1317 if (ptr) {
1318 osdSymbol = fastA2I(ptr);
1319 validArgumentCount++;
1322 label = nextArg(ptr);
1323 if (label)
1324 ++validArgumentCount;
1325 else
1326 label = "";
1328 if (validArgumentCount < 4) {
1329 cliShowParseError();
1330 } 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) {
1331 cliShowParseError();
1332 } else {
1333 tempSensorConfig_t *sensorConfig = tempSensorConfigMutable(i);
1334 sensorConfig->type = type;
1335 sensorConfig->address = address;
1336 sensorConfig->alarm_min = alarm_min;
1337 sensorConfig->alarm_max = alarm_max;
1338 sensorConfig->osdSymbol = osdSymbol;
1339 for (uint8_t index = 0; index < TEMPERATURE_LABEL_LEN; ++index) {
1340 sensorConfig->label[index] = toupper(label[index]);
1341 if (label[index] == '\0') break;
1344 } else {
1345 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS - 1);
1349 #endif
1351 #ifdef USE_FW_AUTOLAND
1352 static void printFwAutolandApproach(uint8_t dumpMask, const navFwAutolandApproach_t *navFwAutolandApproach, const navFwAutolandApproach_t *defaultFwAutolandApproach)
1354 const char *format = "fwapproach %u %d %d %u %d %d %u";
1355 for (uint8_t i = 0; i < MAX_FW_LAND_APPOACH_SETTINGS; i++) {
1356 bool equalsDefault = false;
1357 if (defaultFwAutolandApproach) {
1358 equalsDefault = navFwAutolandApproach[i].approachDirection == defaultFwAutolandApproach[i].approachDirection
1359 && navFwAutolandApproach[i].approachAlt == defaultFwAutolandApproach[i].approachAlt
1360 && navFwAutolandApproach[i].landAlt == defaultFwAutolandApproach[i].landAlt
1361 && navFwAutolandApproach[i].landApproachHeading1 == defaultFwAutolandApproach[i].landApproachHeading1
1362 && navFwAutolandApproach[i].landApproachHeading2 == defaultFwAutolandApproach[i].landApproachHeading2
1363 && navFwAutolandApproach[i].isSeaLevelRef == defaultFwAutolandApproach[i].isSeaLevelRef;
1364 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,
1365 defaultFwAutolandApproach[i].approachAlt, defaultFwAutolandApproach[i].landAlt, defaultFwAutolandApproach[i].approachDirection, defaultFwAutolandApproach[i].landApproachHeading1, defaultFwAutolandApproach[i].landApproachHeading2, defaultFwAutolandApproach[i].isSeaLevelRef);
1367 cliDumpPrintLinef(dumpMask, equalsDefault, format, i,
1368 navFwAutolandApproach[i].approachAlt, navFwAutolandApproach[i].landAlt, navFwAutolandApproach[i].approachDirection, navFwAutolandApproach[i].landApproachHeading1, navFwAutolandApproach[i].landApproachHeading2, navFwAutolandApproach[i].isSeaLevelRef);
1372 static void cliFwAutolandApproach(char * cmdline)
1374 if (isEmpty(cmdline)) {
1375 printFwAutolandApproach(DUMP_MASTER, fwAutolandApproachConfig(0), NULL);
1376 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1377 resetFwAutolandApproach(-1);
1378 } else {
1379 int32_t approachAlt = 0, heading1 = 0, heading2 = 0, landDirection = 0, landAlt = 0;
1380 bool isSeaLevelRef = false;
1381 uint8_t validArgumentCount = 0;
1382 const char *ptr = cmdline;
1383 int8_t i = fastA2I(ptr);
1384 if (i < 0 || i >= MAX_FW_LAND_APPOACH_SETTINGS) {
1385 cliShowArgumentRangeError("fwapproach index", 0, MAX_FW_LAND_APPOACH_SETTINGS - 1);
1386 } else {
1387 if ((ptr = nextArg(ptr))) {
1388 approachAlt = fastA2I(ptr);
1389 validArgumentCount++;
1392 if ((ptr = nextArg(ptr))) {
1393 landAlt = fastA2I(ptr);
1394 validArgumentCount++;
1397 if ((ptr = nextArg(ptr))) {
1398 landDirection = fastA2I(ptr);
1400 if (landDirection != 0 && landDirection != 1) {
1401 cliShowParseError();
1402 return;
1405 validArgumentCount++;
1408 if ((ptr = nextArg(ptr))) {
1409 heading1 = fastA2I(ptr);
1411 if (heading1 < -360 || heading1 > 360) {
1412 cliShowParseError();
1413 return;
1416 validArgumentCount++;
1419 if ((ptr = nextArg(ptr))) {
1420 heading2 = fastA2I(ptr);
1422 if (heading2 < -360 || heading2 > 360) {
1423 cliShowParseError();
1424 return;
1427 validArgumentCount++;
1430 if ((ptr = nextArg(ptr))) {
1431 isSeaLevelRef = fastA2I(ptr);
1432 validArgumentCount++;
1435 if ((ptr = nextArg(ptr))) {
1436 // check for too many arguments
1437 validArgumentCount++;
1440 if (validArgumentCount != 6) {
1441 cliShowParseError();
1442 } else {
1443 fwAutolandApproachConfigMutable(i)->approachAlt = approachAlt;
1444 fwAutolandApproachConfigMutable(i)->landAlt = landAlt;
1445 fwAutolandApproachConfigMutable(i)->approachDirection = (fwAutolandApproachDirection_e)landDirection;
1446 fwAutolandApproachConfigMutable(i)->landApproachHeading1 = (int16_t)heading1;
1447 fwAutolandApproachConfigMutable(i)->landApproachHeading2 = (int16_t)heading2;
1448 fwAutolandApproachConfigMutable(i)->isSeaLevelRef = isSeaLevelRef;
1453 #endif
1455 #if defined(USE_SAFE_HOME)
1456 static void printSafeHomes(uint8_t dumpMask, const navSafeHome_t *navSafeHome, const navSafeHome_t *defaultSafeHome)
1458 const char *format = "safehome %u %u %d %d"; // uint8_t enabled, int32_t lat; int32_t lon
1459 for (uint8_t i = 0; i < MAX_SAFE_HOMES; i++) {
1460 bool equalsDefault = false;
1461 if (defaultSafeHome) {
1462 equalsDefault = navSafeHome[i].enabled == defaultSafeHome[i].enabled
1463 && navSafeHome[i].lat == defaultSafeHome[i].lat
1464 && navSafeHome[i].lon == defaultSafeHome[i].lon;
1465 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,
1466 defaultSafeHome[i].enabled, defaultSafeHome[i].lat, defaultSafeHome[i].lon);
1468 cliDumpPrintLinef(dumpMask, equalsDefault, format, i,
1469 navSafeHome[i].enabled, navSafeHome[i].lat, navSafeHome[i].lon);
1473 static void cliSafeHomes(char *cmdline)
1475 if (isEmpty(cmdline)) {
1476 printSafeHomes(DUMP_MASTER, safeHomeConfig(0), NULL);
1477 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1478 resetSafeHomes();
1479 } else {
1480 int32_t lat=0, lon=0;
1481 bool enabled=false;
1482 uint8_t validArgumentCount = 0;
1483 const char *ptr = cmdline;
1484 int8_t i = fastA2I(ptr);
1485 if (i < 0 || i >= MAX_SAFE_HOMES) {
1486 cliShowArgumentRangeError("safehome index", 0, MAX_SAFE_HOMES - 1);
1487 } else {
1488 if ((ptr = nextArg(ptr))) {
1489 enabled = fastA2I(ptr);
1490 validArgumentCount++;
1492 if ((ptr = nextArg(ptr))) {
1493 lat = fastA2I(ptr);
1494 validArgumentCount++;
1496 if ((ptr = nextArg(ptr))) {
1497 lon = fastA2I(ptr);
1498 validArgumentCount++;
1500 if ((ptr = nextArg(ptr))) {
1501 // check for too many arguments
1502 validArgumentCount++;
1504 if (validArgumentCount != 3) {
1505 cliShowParseError();
1506 } else {
1507 safeHomeConfigMutable(i)->enabled = enabled;
1508 safeHomeConfigMutable(i)->lat = lat;
1509 safeHomeConfigMutable(i)->lon = lon;
1515 #endif
1516 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1517 static void printWaypoints(uint8_t dumpMask, const navWaypoint_t *navWaypoint, const navWaypoint_t *defaultNavWaypoint)
1519 cliPrintLinef("#wp %d %svalid", posControl.waypointCount, posControl.waypointListValid ? "" : "in"); //int8_t bool
1520 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
1521 for (uint8_t i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1522 bool equalsDefault = false;
1523 if (defaultNavWaypoint) {
1524 equalsDefault = navWaypoint[i].action == defaultNavWaypoint[i].action
1525 && navWaypoint[i].lat == defaultNavWaypoint[i].lat
1526 && navWaypoint[i].lon == defaultNavWaypoint[i].lon
1527 && navWaypoint[i].alt == defaultNavWaypoint[i].alt
1528 && navWaypoint[i].p1 == defaultNavWaypoint[i].p1
1529 && navWaypoint[i].p2 == defaultNavWaypoint[i].p2
1530 && navWaypoint[i].p3 == defaultNavWaypoint[i].p3
1531 && navWaypoint[i].flag == defaultNavWaypoint[i].flag;
1532 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1534 defaultNavWaypoint[i].action,
1535 defaultNavWaypoint[i].lat,
1536 defaultNavWaypoint[i].lon,
1537 defaultNavWaypoint[i].alt,
1538 defaultNavWaypoint[i].p1,
1539 defaultNavWaypoint[i].p2,
1540 defaultNavWaypoint[i].p3,
1541 defaultNavWaypoint[i].flag
1544 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1546 navWaypoint[i].action,
1547 navWaypoint[i].lat,
1548 navWaypoint[i].lon,
1549 navWaypoint[i].alt,
1550 navWaypoint[i].p1,
1551 navWaypoint[i].p2,
1552 navWaypoint[i].p3,
1553 navWaypoint[i].flag
1558 static void cliWaypoints(char *cmdline)
1560 #ifdef USE_MULTI_MISSION
1561 static int8_t multiMissionWPCounter = 0;
1562 #endif
1563 if (isEmpty(cmdline)) {
1564 printWaypoints(DUMP_MASTER, posControl.waypointList, NULL);
1565 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1566 resetWaypointList();
1567 } else if (sl_strcasecmp(cmdline, "load") == 0) {
1568 loadNonVolatileWaypointList(true);
1569 } else if (sl_strcasecmp(cmdline, "save") == 0) {
1570 posControl.waypointListValid = false;
1571 for (int i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1572 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;
1573 if (posControl.waypointList[i].flag == NAV_WP_FLAG_LAST) {
1574 #ifdef USE_MULTI_MISSION
1575 if (posControl.multiMissionCount == 1) {
1576 posControl.waypointCount = i + 1;
1577 posControl.waypointListValid = true;
1578 multiMissionWPCounter = 0;
1579 posControl.multiMissionCount = 0;
1580 break;
1581 } else {
1582 posControl.multiMissionCount -= 1;
1584 #else
1585 posControl.waypointCount = i + 1;
1586 posControl.waypointListValid = true;
1587 break;
1588 #endif
1591 if (posControl.waypointListValid) {
1592 saveNonVolatileWaypointList();
1593 } else {
1594 cliShowParseError();
1596 } else {
1597 int16_t i, p1=0,p2=0,p3=0,tmp=0;
1598 uint8_t action=0, flag=0;
1599 int32_t lat=0, lon=0, alt=0;
1600 uint8_t validArgumentCount = 0;
1601 const char *ptr = cmdline;
1602 i = fastA2I(ptr);
1603 #ifdef USE_MULTI_MISSION
1604 if (i + multiMissionWPCounter >= 0 && i + multiMissionWPCounter < NAV_MAX_WAYPOINTS) {
1605 #else
1606 if (i >= 0 && i < NAV_MAX_WAYPOINTS) {
1607 #endif
1608 ptr = nextArg(ptr);
1609 if (ptr) {
1610 action = fastA2I(ptr);
1611 validArgumentCount++;
1613 ptr = nextArg(ptr);
1614 if (ptr) {
1615 lat = fastA2I(ptr);
1616 validArgumentCount++;
1618 ptr = nextArg(ptr);
1619 if (ptr) {
1620 lon = fastA2I(ptr);
1621 validArgumentCount++;
1623 ptr = nextArg(ptr);
1624 if (ptr) {
1625 alt = fastA2I(ptr);
1626 validArgumentCount++;
1628 ptr = nextArg(ptr);
1629 if (ptr) {
1630 p1 = fastA2I(ptr);
1631 validArgumentCount++;
1633 ptr = nextArg(ptr);
1634 if (ptr) {
1635 tmp = fastA2I(ptr);
1636 validArgumentCount++;
1638 /* We support pre-2.5 6 values (... p1,flags) or
1639 * 2.5 and later, 8 values (... p1,p2,p3,flags)
1641 ptr = nextArg(ptr);
1642 if (ptr) {
1643 p2 = tmp;
1644 p3 = fastA2I(ptr);
1645 validArgumentCount++;
1646 ptr = nextArg(ptr);
1647 if (ptr) {
1648 flag = fastA2I(ptr);
1649 validArgumentCount++;
1651 } else {
1652 flag = tmp;
1655 if (!(validArgumentCount == 6 || validArgumentCount == 8)) {
1656 cliShowParseError();
1657 } 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)) {
1658 cliShowParseError();
1659 } else {
1660 #ifdef USE_MULTI_MISSION
1661 if (i + multiMissionWPCounter == 0) {
1662 posControl.multiMissionCount = 0;
1665 posControl.waypointList[i + multiMissionWPCounter].action = action;
1666 posControl.waypointList[i + multiMissionWPCounter].lat = lat;
1667 posControl.waypointList[i + multiMissionWPCounter].lon = lon;
1668 posControl.waypointList[i + multiMissionWPCounter].alt = alt;
1669 posControl.waypointList[i + multiMissionWPCounter].p1 = p1;
1670 posControl.waypointList[i + multiMissionWPCounter].p2 = p2;
1671 posControl.waypointList[i + multiMissionWPCounter].p3 = p3;
1672 posControl.waypointList[i + multiMissionWPCounter].flag = flag;
1674 // Process WP entries made up of multiple successive WP missions (multiple NAV_WP_FLAG_LAST entries)
1675 // Individial missions loaded at runtime, mission selected nav_waypoint_multi_mission_index
1676 if (flag == NAV_WP_FLAG_LAST) {
1677 multiMissionWPCounter += i + 1;
1678 posControl.multiMissionCount += 1;
1680 #else
1681 posControl.waypointList[i].action = action;
1682 posControl.waypointList[i].lat = lat;
1683 posControl.waypointList[i].lon = lon;
1684 posControl.waypointList[i].alt = alt;
1685 posControl.waypointList[i].p1 = p1;
1686 posControl.waypointList[i].p2 = p2;
1687 posControl.waypointList[i].p3 = p3;
1688 posControl.waypointList[i].flag = flag;
1689 #endif
1691 } else {
1692 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS - 1);
1697 #endif
1699 #ifdef USE_LED_STRIP
1700 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1702 const char *format = "led %u %s";
1703 char ledConfigBuffer[20];
1704 char ledConfigDefaultBuffer[20];
1705 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1706 ledConfig_t ledConfig = ledConfigs[i];
1707 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1708 bool equalsDefault = false;
1709 if (defaultLedConfigs) {
1710 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1711 equalsDefault = !memcmp(&ledConfig, &ledConfigDefault, sizeof(ledConfig_t));
1712 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1713 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1715 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1719 static void cliLed(char *cmdline)
1721 int i;
1722 const char *ptr;
1724 if (isEmpty(cmdline)) {
1725 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1726 } else {
1727 ptr = cmdline;
1728 i = fastA2I(ptr);
1729 if (i < LED_MAX_STRIP_LENGTH) {
1730 ptr = nextArg(cmdline);
1731 if (!parseLedStripConfig(i, ptr)) {
1732 cliShowParseError();
1734 } else {
1735 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1740 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1742 const char *format = "color %u %d,%u,%u";
1743 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1744 const hsvColor_t *color = &colors[i];
1745 bool equalsDefault = false;
1746 if (defaultColors) {
1747 const hsvColor_t *colorDefault = &defaultColors[i];
1748 equalsDefault = color->h == colorDefault->h
1749 && color->s == colorDefault->s
1750 && color->v == colorDefault->v;
1751 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1753 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1757 static void cliColor(char *cmdline)
1759 if (isEmpty(cmdline)) {
1760 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1761 } else {
1762 const char *ptr = cmdline;
1763 const int i = fastA2I(ptr);
1764 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1765 ptr = nextArg(cmdline);
1766 if (!parseColor(i, ptr)) {
1767 cliShowParseError();
1769 } else {
1770 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1775 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1777 const char *format = "mode_color %u %u %u";
1778 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1779 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1780 int colorIndex = ledStripConfig->modeColors[i].color[j];
1781 bool equalsDefault = false;
1782 if (defaultLedStripConfig) {
1783 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1784 equalsDefault = colorIndex == colorIndexDefault;
1785 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1787 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1791 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1792 const int colorIndex = ledStripConfig->specialColors.color[j];
1793 bool equalsDefault = false;
1794 if (defaultLedStripConfig) {
1795 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1796 equalsDefault = colorIndex == colorIndexDefault;
1797 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1799 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1803 static void cliModeColor(char *cmdline)
1805 char * saveptr;
1807 if (isEmpty(cmdline)) {
1808 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1809 } else {
1810 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1811 int args[ARGS_COUNT];
1812 int argNo = 0;
1813 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1814 while (ptr && argNo < ARGS_COUNT) {
1815 args[argNo++] = fastA2I(ptr);
1816 ptr = strtok_r(NULL, " ", &saveptr);
1819 if (ptr != NULL || argNo != ARGS_COUNT) {
1820 cliShowParseError();
1821 return;
1824 int modeIdx = args[MODE];
1825 int funIdx = args[FUNCTION];
1826 int color = args[COLOR];
1827 if (!setModeColor(modeIdx, funIdx, color)) {
1828 cliShowParseError();
1829 return;
1831 // values are validated
1832 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1836 static void cliLedPinPWM(char *cmdline)
1838 int i;
1840 if (isEmpty(cmdline)) {
1841 ledPinStopPWM();
1842 cliPrintLine("PWM stopped");
1843 } else {
1844 i = fastA2I(cmdline);
1845 ledPinStartPWM(i);
1846 cliPrintLinef("PWM started: %d%%",i);
1849 #endif
1851 static void cliDelay(char* cmdLine) {
1852 int ms = 0;
1853 if (isEmpty(cmdLine)) {
1854 cliDelayMs = 0;
1855 cliPrintLine("CLI delay deactivated");
1856 return;
1859 ms = fastA2I(cmdLine);
1860 if (ms) {
1861 cliDelayMs = ms;
1862 cliPrintLinef("CLI delay set to %d ms", ms);
1864 } else {
1865 cliShowParseError();
1870 static void printServo(uint8_t dumpMask, const servoParam_t *servoParam, const servoParam_t *defaultServoParam)
1872 // print out servo settings
1873 const char *format = "servo %u %d %d %d %d";
1874 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1875 const servoParam_t *servoConf = &servoParam[i];
1876 bool equalsDefault = false;
1877 if (defaultServoParam) {
1878 const servoParam_t *servoConfDefault = &defaultServoParam[i];
1879 equalsDefault = servoConf->min == servoConfDefault->min
1880 && servoConf->max == servoConfDefault->max
1881 && servoConf->middle == servoConfDefault->middle
1882 && servoConf->rate == servoConfDefault->rate;
1883 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1885 servoConfDefault->min,
1886 servoConfDefault->max,
1887 servoConfDefault->middle,
1888 servoConfDefault->rate
1891 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1893 servoConf->min,
1894 servoConf->max,
1895 servoConf->middle,
1896 servoConf->rate
1901 static void cliServo(char *cmdline)
1903 enum { SERVO_ARGUMENT_COUNT = 5 };
1904 int16_t arguments[SERVO_ARGUMENT_COUNT];
1906 servoParam_t *servo;
1908 int i;
1909 const char *ptr;
1911 if (isEmpty(cmdline)) {
1912 printServo(DUMP_MASTER, servoParams(0), NULL);
1913 } else {
1914 int validArgumentCount = 0;
1916 ptr = cmdline;
1918 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1920 // If command line doesn't fit the format, don't modify the config
1921 while (*ptr) {
1922 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1923 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1924 cliShowParseError();
1925 return;
1928 arguments[validArgumentCount++] = fastA2I(ptr);
1930 do {
1931 ptr++;
1932 } while (*ptr >= '0' && *ptr <= '9');
1933 } else if (*ptr == ' ') {
1934 ptr++;
1935 } else {
1936 cliShowParseError();
1937 return;
1941 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE};
1943 i = arguments[INDEX];
1945 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1946 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1947 cliShowParseError();
1948 return;
1951 servo = servoParamsMutable(i);
1953 if (
1954 arguments[MIN] < SERVO_OUTPUT_MIN || arguments[MIN] > SERVO_OUTPUT_MAX ||
1955 arguments[MAX] < SERVO_OUTPUT_MIN || arguments[MAX] > SERVO_OUTPUT_MAX ||
1956 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1957 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1958 arguments[RATE] < -125 || arguments[RATE] > 125
1960 cliShowParseError();
1961 return;
1964 servo->min = arguments[MIN];
1965 servo->max = arguments[MAX];
1966 servo->middle = arguments[MIDDLE];
1967 servo->rate = arguments[RATE];
1971 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1973 const char *format = "smix %d %d %d %d %d %d";
1974 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1975 const servoMixer_t customServoMixer = customServoMixers[i];
1976 if (customServoMixer.rate == 0) {
1977 break;
1980 bool equalsDefault = false;
1981 if (defaultCustomServoMixers) {
1982 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1983 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1984 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1985 && customServoMixer.rate == customServoMixerDefault.rate
1986 && customServoMixer.speed == customServoMixerDefault.speed
1987 #ifdef USE_PROGRAMMING_FRAMEWORK
1988 && customServoMixer.conditionId == customServoMixerDefault.conditionId
1989 #endif
1992 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1994 customServoMixerDefault.targetChannel,
1995 customServoMixerDefault.inputSource,
1996 customServoMixerDefault.rate,
1997 customServoMixerDefault.speed,
1998 #ifdef USE_PROGRAMMING_FRAMEWORK
1999 customServoMixer.conditionId
2000 #else
2002 #endif
2005 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2007 customServoMixer.targetChannel,
2008 customServoMixer.inputSource,
2009 customServoMixer.rate,
2010 customServoMixer.speed,
2011 #ifdef USE_PROGRAMMING_FRAMEWORK
2012 customServoMixer.conditionId
2013 #else
2015 #endif
2020 static void cliServoMix(char *cmdline)
2022 char * saveptr;
2023 int args[6], check = 0;
2024 uint8_t len = strlen(cmdline);
2026 if (len == 0) {
2027 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
2028 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2029 // erase custom mixer
2030 Reset_servoMixers(customServoMixersMutable(0));
2031 } else {
2032 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, CONDITION, ARGS_COUNT};
2033 char *ptr = strtok_r(cmdline, " ", &saveptr);
2034 args[CONDITION] = -1;
2035 while (ptr != NULL && check < ARGS_COUNT) {
2036 args[check++] = fastA2I(ptr);
2037 ptr = strtok_r(NULL, " ", &saveptr);
2040 if (ptr != NULL || (check < ARGS_COUNT - 1)) {
2041 cliShowParseError();
2042 return;
2045 int32_t i = args[RULE];
2046 if (
2047 i >= 0 && i < MAX_SERVO_RULES &&
2048 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
2049 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
2050 args[RATE] >= -1000 && args[RATE] <= 1000 &&
2051 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
2052 args[CONDITION] >= -1 && args[CONDITION] < MAX_LOGIC_CONDITIONS
2054 customServoMixersMutable(i)->targetChannel = args[TARGET];
2055 customServoMixersMutable(i)->inputSource = args[INPUT];
2056 customServoMixersMutable(i)->rate = args[RATE];
2057 customServoMixersMutable(i)->speed = args[SPEED];
2058 #ifdef USE_PROGRAMMING_FRAMEWORK
2059 customServoMixersMutable(i)->conditionId = args[CONDITION];
2060 #endif
2061 cliServoMix("");
2062 } else {
2063 cliShowParseError();
2068 #ifdef USE_PROGRAMMING_FRAMEWORK
2070 static void printLogic(uint8_t dumpMask, const logicCondition_t *logicConditions, const logicCondition_t *defaultLogicConditions, int16_t showLC)
2072 const char *format = "logic %d %d %d %d %d %d %d %d %d";
2073 for (uint8_t i = 0; i < MAX_LOGIC_CONDITIONS; i++) {
2074 if (showLC == -1 || showLC == i) {
2075 const logicCondition_t logic = logicConditions[i];
2077 bool equalsDefault = false;
2078 if (defaultLogicConditions) {
2079 logicCondition_t defaultValue = defaultLogicConditions[i];
2080 equalsDefault =
2081 logic.enabled == defaultValue.enabled &&
2082 logic.activatorId == defaultValue.activatorId &&
2083 logic.operation == defaultValue.operation &&
2084 logic.operandA.type == defaultValue.operandA.type &&
2085 logic.operandA.value == defaultValue.operandA.value &&
2086 logic.operandB.type == defaultValue.operandB.type &&
2087 logic.operandB.value == defaultValue.operandB.value &&
2088 logic.flags == defaultValue.flags;
2090 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2092 logic.enabled,
2093 logic.activatorId,
2094 logic.operation,
2095 logic.operandA.type,
2096 logic.operandA.value,
2097 logic.operandB.type,
2098 logic.operandB.value,
2099 logic.flags
2102 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2104 logic.enabled,
2105 logic.activatorId,
2106 logic.operation,
2107 logic.operandA.type,
2108 logic.operandA.value,
2109 logic.operandB.type,
2110 logic.operandB.value,
2111 logic.flags
2117 static void processCliLogic(char *cmdline, int16_t lcIndex) {
2118 char * saveptr;
2119 int args[9], check = 0;
2120 uint8_t len = strlen(cmdline);
2122 if (len == 0) {
2123 if (!commandBatchActive) {
2124 printLogic(DUMP_MASTER, logicConditions(0), NULL, -1);
2125 } else if (lcIndex >= 0) {
2126 printLogic(DUMP_MASTER, logicConditions(0), NULL, lcIndex);
2128 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2129 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS);
2130 } else {
2131 enum {
2132 INDEX = 0,
2133 ENABLED,
2134 ACTIVATOR_ID,
2135 OPERATION,
2136 OPERAND_A_TYPE,
2137 OPERAND_A_VALUE,
2138 OPERAND_B_TYPE,
2139 OPERAND_B_VALUE,
2140 FLAGS,
2141 ARGS_COUNT
2143 char *ptr = strtok_r(cmdline, " ", &saveptr);
2144 while (ptr != NULL && check < ARGS_COUNT) {
2145 args[check++] = fastA2I(ptr);
2146 ptr = strtok_r(NULL, " ", &saveptr);
2149 if (ptr != NULL || check != ARGS_COUNT) {
2150 cliShowParseError();
2151 return;
2154 int32_t i = args[INDEX];
2155 if (
2156 i >= 0 && i < MAX_LOGIC_CONDITIONS &&
2157 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
2158 args[ACTIVATOR_ID] >= -1 && args[ACTIVATOR_ID] < MAX_LOGIC_CONDITIONS &&
2159 args[OPERATION] >= 0 && args[OPERATION] < LOGIC_CONDITION_LAST &&
2160 args[OPERAND_A_TYPE] >= 0 && args[OPERAND_A_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2161 args[OPERAND_A_VALUE] >= -1000000 && args[OPERAND_A_VALUE] <= 1000000 &&
2162 args[OPERAND_B_TYPE] >= 0 && args[OPERAND_B_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2163 args[OPERAND_B_VALUE] >= -1000000 && args[OPERAND_B_VALUE] <= 1000000 &&
2164 args[FLAGS] >= 0 && args[FLAGS] <= 255
2167 logicConditionsMutable(i)->enabled = args[ENABLED];
2168 logicConditionsMutable(i)->activatorId = args[ACTIVATOR_ID];
2169 logicConditionsMutable(i)->operation = args[OPERATION];
2170 logicConditionsMutable(i)->operandA.type = args[OPERAND_A_TYPE];
2171 logicConditionsMutable(i)->operandA.value = args[OPERAND_A_VALUE];
2172 logicConditionsMutable(i)->operandB.type = args[OPERAND_B_TYPE];
2173 logicConditionsMutable(i)->operandB.value = args[OPERAND_B_VALUE];
2174 logicConditionsMutable(i)->flags = args[FLAGS];
2176 processCliLogic("", i);
2177 } else {
2178 cliShowParseError();
2183 static void cliLogic(char *cmdline) {
2184 processCliLogic(cmdline, -1);
2187 static void printGvar(uint8_t dumpMask, const globalVariableConfig_t *gvars, const globalVariableConfig_t *defaultGvars)
2189 const char *format = "gvar %d %d %d %d";
2190 for (uint32_t i = 0; i < MAX_GLOBAL_VARIABLES; i++) {
2191 const globalVariableConfig_t gvar = gvars[i];
2193 bool equalsDefault = false;
2194 if (defaultGvars) {
2195 globalVariableConfig_t defaultValue = defaultGvars[i];
2196 equalsDefault =
2197 gvar.defaultValue == defaultValue.defaultValue &&
2198 gvar.min == defaultValue.min &&
2199 gvar.max == defaultValue.max;
2201 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2203 gvar.defaultValue,
2204 gvar.min,
2205 gvar.max
2208 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2210 gvar.defaultValue,
2211 gvar.min,
2212 gvar.max
2217 static void cliGvar(char *cmdline) {
2218 char * saveptr;
2219 int args[4], check = 0;
2220 uint8_t len = strlen(cmdline);
2222 if (len == 0) {
2223 printGvar(DUMP_MASTER, globalVariableConfigs(0), NULL);
2224 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2225 pgResetCopy(globalVariableConfigsMutable(0), PG_GLOBAL_VARIABLE_CONFIG);
2226 } else {
2227 enum {
2228 INDEX = 0,
2229 DEFAULT,
2230 MIN,
2231 MAX,
2232 ARGS_COUNT
2234 char *ptr = strtok_r(cmdline, " ", &saveptr);
2235 while (ptr != NULL && check < ARGS_COUNT) {
2236 args[check++] = fastA2I(ptr);
2237 ptr = strtok_r(NULL, " ", &saveptr);
2240 if (ptr != NULL || check != ARGS_COUNT) {
2241 cliShowParseError();
2242 return;
2245 int32_t i = args[INDEX];
2246 if (
2247 i >= 0 && i < MAX_GLOBAL_VARIABLES &&
2248 args[DEFAULT] >= INT32_MIN && args[DEFAULT] <= INT32_MAX &&
2249 args[MIN] >= INT32_MIN && args[MIN] <= INT32_MAX &&
2250 args[MAX] >= INT32_MIN && args[MAX] <= INT32_MAX
2252 globalVariableConfigsMutable(i)->defaultValue = args[DEFAULT];
2253 globalVariableConfigsMutable(i)->min = args[MIN];
2254 globalVariableConfigsMutable(i)->max = args[MAX];
2256 cliGvar("");
2257 } else {
2258 cliShowParseError();
2263 static void printPid(uint8_t dumpMask, const programmingPid_t *programmingPids, const programmingPid_t *defaultProgrammingPids)
2265 const char *format = "pid %d %d %d %d %d %d %d %d %d %d";
2266 for (uint32_t i = 0; i < MAX_PROGRAMMING_PID_COUNT; i++) {
2267 const programmingPid_t pid = programmingPids[i];
2269 bool equalsDefault = false;
2270 if (defaultProgrammingPids) {
2271 programmingPid_t defaultValue = defaultProgrammingPids[i];
2272 equalsDefault =
2273 pid.enabled == defaultValue.enabled &&
2274 pid.setpoint.type == defaultValue.setpoint.type &&
2275 pid.setpoint.value == defaultValue.setpoint.value &&
2276 pid.measurement.type == defaultValue.measurement.type &&
2277 pid.measurement.value == defaultValue.measurement.value &&
2278 pid.gains.P == defaultValue.gains.P &&
2279 pid.gains.I == defaultValue.gains.I &&
2280 pid.gains.D == defaultValue.gains.D &&
2281 pid.gains.FF == defaultValue.gains.FF;
2283 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2285 pid.enabled,
2286 pid.setpoint.type,
2287 pid.setpoint.value,
2288 pid.measurement.type,
2289 pid.measurement.value,
2290 pid.gains.P,
2291 pid.gains.I,
2292 pid.gains.D,
2293 pid.gains.FF
2296 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2298 pid.enabled,
2299 pid.setpoint.type,
2300 pid.setpoint.value,
2301 pid.measurement.type,
2302 pid.measurement.value,
2303 pid.gains.P,
2304 pid.gains.I,
2305 pid.gains.D,
2306 pid.gains.FF
2311 static void cliPid(char *cmdline) {
2312 char * saveptr;
2313 int args[10], check = 0;
2314 uint8_t len = strlen(cmdline);
2316 if (len == 0) {
2317 printPid(DUMP_MASTER, programmingPids(0), NULL);
2318 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2319 pgResetCopy(programmingPidsMutable(0), PG_LOGIC_CONDITIONS);
2320 } else {
2321 enum {
2322 INDEX = 0,
2323 ENABLED,
2324 SETPOINT_TYPE,
2325 SETPOINT_VALUE,
2326 MEASUREMENT_TYPE,
2327 MEASUREMENT_VALUE,
2328 P_GAIN,
2329 I_GAIN,
2330 D_GAIN,
2331 FF_GAIN,
2332 ARGS_COUNT
2334 char *ptr = strtok_r(cmdline, " ", &saveptr);
2335 while (ptr != NULL && check < ARGS_COUNT) {
2336 args[check++] = fastA2I(ptr);
2337 ptr = strtok_r(NULL, " ", &saveptr);
2340 if (ptr != NULL || check != ARGS_COUNT) {
2341 cliShowParseError();
2342 return;
2345 int32_t i = args[INDEX];
2346 if (
2347 i >= 0 && i < MAX_PROGRAMMING_PID_COUNT &&
2348 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
2349 args[SETPOINT_TYPE] >= 0 && args[SETPOINT_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2350 args[SETPOINT_VALUE] >= -1000000 && args[SETPOINT_VALUE] <= 1000000 &&
2351 args[MEASUREMENT_TYPE] >= 0 && args[MEASUREMENT_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2352 args[MEASUREMENT_VALUE] >= -1000000 && args[MEASUREMENT_VALUE] <= 1000000 &&
2353 args[P_GAIN] >= 0 && args[P_GAIN] <= INT16_MAX &&
2354 args[I_GAIN] >= 0 && args[I_GAIN] <= INT16_MAX &&
2355 args[D_GAIN] >= 0 && args[D_GAIN] <= INT16_MAX &&
2356 args[FF_GAIN] >= 0 && args[FF_GAIN] <= INT16_MAX
2358 programmingPidsMutable(i)->enabled = args[ENABLED];
2359 programmingPidsMutable(i)->setpoint.type = args[SETPOINT_TYPE];
2360 programmingPidsMutable(i)->setpoint.value = args[SETPOINT_VALUE];
2361 programmingPidsMutable(i)->measurement.type = args[MEASUREMENT_TYPE];
2362 programmingPidsMutable(i)->measurement.value = args[MEASUREMENT_VALUE];
2363 programmingPidsMutable(i)->gains.P = args[P_GAIN];
2364 programmingPidsMutable(i)->gains.I = args[I_GAIN];
2365 programmingPidsMutable(i)->gains.D = args[D_GAIN];
2366 programmingPidsMutable(i)->gains.FF = args[FF_GAIN];
2368 cliPid("");
2369 } else {
2370 cliShowParseError();
2375 static void printOsdCustomElements(uint8_t dumpMask, const osdCustomElement_t *osdCustomElements, const osdCustomElement_t *defaultosdCustomElements)
2377 const char *format = "osd_custom_elements %d %d %d %d %d %d %d %d %d \"%s\"";
2379 if(CUSTOM_ELEMENTS_PARTS != 3)
2381 cliPrintHashLine("Incompatible count of elements for custom OSD elements");
2384 for (uint8_t i = 0; i < MAX_CUSTOM_ELEMENTS; i++) {
2385 bool equalsDefault = false;
2387 const osdCustomElement_t osdCustomElement = osdCustomElements[i];
2388 if(defaultosdCustomElements){
2389 const osdCustomElement_t defaultValue = defaultosdCustomElements[i];
2390 equalsDefault =
2391 osdCustomElement.part[0].type == defaultValue.part[0].type &&
2392 osdCustomElement.part[0].value == defaultValue.part[0].value &&
2393 osdCustomElement.part[1].type == defaultValue.part[1].type &&
2394 osdCustomElement.part[1].value == defaultValue.part[1].value &&
2395 osdCustomElement.part[2].type == defaultValue.part[2].type &&
2396 osdCustomElement.part[2].value == defaultValue.part[2].value &&
2397 osdCustomElement.visibility.type == defaultValue.visibility.type &&
2398 osdCustomElement.visibility.value == defaultValue.visibility.value &&
2399 strcmp(osdCustomElement.osdCustomElementText, defaultValue.osdCustomElementText) == 0;
2401 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2403 osdCustomElement.part[0].type,
2404 osdCustomElement.part[0].value,
2405 osdCustomElement.part[1].type,
2406 osdCustomElement.part[1].value,
2407 osdCustomElement.part[2].type,
2408 osdCustomElement.part[2].value,
2409 osdCustomElement.visibility.type,
2410 osdCustomElement.visibility.value,
2411 osdCustomElement.osdCustomElementText
2415 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2417 osdCustomElement.part[0].type,
2418 osdCustomElement.part[0].value,
2419 osdCustomElement.part[1].type,
2420 osdCustomElement.part[1].value,
2421 osdCustomElement.part[2].type,
2422 osdCustomElement.part[2].value,
2423 osdCustomElement.visibility.type,
2424 osdCustomElement.visibility.value,
2425 osdCustomElement.osdCustomElementText
2430 static void osdCustom(char *cmdline){
2431 char * saveptrMain;
2432 char * saveptrParams;
2433 int args[10], check = 0;
2434 char text[OSD_CUSTOM_ELEMENT_TEXT_SIZE];
2435 uint8_t len = strlen(cmdline);
2437 if (len == 0) {
2438 printOsdCustomElements(DUMP_MASTER, osdCustomElements(0), NULL);
2439 } else {
2440 //split by ", first are params second is text
2441 char *ptrMain = strtok_r(cmdline, "\"", &saveptrMain);
2442 enum {
2443 INDEX = 0,
2444 PART0_TYPE,
2445 PART0_VALUE,
2446 PART1_TYPE,
2447 PART1_VALUE,
2448 PART2_TYPE,
2449 PART2_VALUE,
2450 VISIBILITY_TYPE,
2451 VISIBILITY_VALUE,
2452 ARGS_COUNT
2454 char *ptrParams = strtok_r(ptrMain, " ", &saveptrParams);
2455 while (ptrParams != NULL && check < ARGS_COUNT) {
2456 args[check++] = fastA2I(ptrParams);
2457 ptrParams = strtok_r(NULL, " ", &saveptrParams);
2460 if (check != ARGS_COUNT) {
2461 cliShowParseError();
2462 return;
2465 //text
2466 char *ptrText = strtok_r(NULL, "\"", &saveptrMain);
2467 size_t copySize = 0;
2468 if(ptrText != NULL){
2469 copySize = MIN(strlen(ptrText), (size_t)(sizeof(text) - 1));
2470 if(copySize > 0){
2471 memcpy(text, ptrText, copySize);
2474 text[copySize] = '\0';
2476 int32_t i = args[INDEX];
2477 if (
2478 i >= 0 && i < MAX_CUSTOM_ELEMENTS &&
2479 args[PART0_TYPE] >= 0 && args[PART0_TYPE] <= 7 &&
2480 args[PART0_VALUE] >= 0 && args[PART0_VALUE] <= UINT8_MAX &&
2481 args[PART1_TYPE] >= 0 && args[PART1_TYPE] <= 7 &&
2482 args[PART1_VALUE] >= 0 && args[PART1_VALUE] <= UINT8_MAX &&
2483 args[PART2_TYPE] >= 0 && args[PART2_TYPE] <= 7 &&
2484 args[PART2_VALUE] >= 0 && args[PART2_VALUE] <= UINT8_MAX &&
2485 args[VISIBILITY_TYPE] >= 0 && args[VISIBILITY_TYPE] <= 2 &&
2486 args[VISIBILITY_VALUE] >= 0 && args[VISIBILITY_VALUE] <= UINT8_MAX
2488 osdCustomElementsMutable(i)->part[0].type = args[PART0_TYPE];
2489 osdCustomElementsMutable(i)->part[0].value = args[PART0_VALUE];
2490 osdCustomElementsMutable(i)->part[1].type = args[PART1_TYPE];
2491 osdCustomElementsMutable(i)->part[1].value = args[PART1_VALUE];
2492 osdCustomElementsMutable(i)->part[2].type = args[PART2_TYPE];
2493 osdCustomElementsMutable(i)->part[2].value = args[PART2_VALUE];
2494 osdCustomElementsMutable(i)->visibility.type = args[VISIBILITY_TYPE];
2495 osdCustomElementsMutable(i)->visibility.value = args[VISIBILITY_VALUE];
2496 memcpy(osdCustomElementsMutable(i)->osdCustomElementText, text, OSD_CUSTOM_ELEMENT_TEXT_SIZE);
2498 osdCustom("");
2499 } else {
2500 cliShowParseError();
2506 #endif
2508 #ifdef USE_SDCARD
2510 static void cliWriteBytes(const uint8_t *buffer, int count)
2512 while (count > 0) {
2513 cliWrite(*buffer);
2514 buffer++;
2515 count--;
2519 static void cliSdInfo(char *cmdline)
2521 UNUSED(cmdline);
2523 cliPrint("SD card: ");
2525 if (!sdcard_isInserted()) {
2526 cliPrintLine("None inserted");
2527 return;
2530 if (!sdcard_isInitialized()) {
2531 cliPrintLine("Startup failed");
2532 return;
2535 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2537 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2538 metadata->manufacturerID,
2539 metadata->numBlocks / 2, /* One block is half a kB */
2540 metadata->productionMonth,
2541 metadata->productionYear,
2542 metadata->productRevisionMajor,
2543 metadata->productRevisionMinor
2546 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2548 cliPrint("'\r\n" "Filesystem: ");
2550 switch (afatfs_getFilesystemState()) {
2551 case AFATFS_FILESYSTEM_STATE_READY:
2552 cliPrint("Ready");
2553 break;
2554 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2555 cliPrint("Initializing");
2556 break;
2557 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2558 case AFATFS_FILESYSTEM_STATE_FATAL:
2559 cliPrint("Fatal");
2561 switch (afatfs_getLastError()) {
2562 case AFATFS_ERROR_BAD_MBR:
2563 cliPrint(" - no FAT MBR partitions");
2564 break;
2565 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2566 cliPrint(" - bad FAT header");
2567 break;
2568 case AFATFS_ERROR_GENERIC:
2569 case AFATFS_ERROR_NONE:
2570 ; // Nothing more detailed to print
2571 break;
2573 break;
2575 cliPrintLinefeed();
2578 #endif
2580 #ifdef USE_FLASHFS
2582 static void cliFlashInfo(char *cmdline)
2584 UNUSED(cmdline);
2586 const flashGeometry_t *layout = flashGetGeometry();
2588 if (layout->totalSize == 0) {
2589 cliPrintLine("Flash not available");
2590 return;
2593 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2594 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2596 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2597 const flashPartition_t *partition;
2598 if (index == 0) {
2599 cliPrintLine("Paritions:");
2601 partition = flashPartitionFindByIndex(index);
2602 if (!partition) {
2603 break;
2605 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2607 #ifdef USE_FLASHFS
2608 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2610 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2611 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2612 flashfsGetOffset()
2614 #endif
2617 static void cliFlashErase(char *cmdline)
2619 UNUSED(cmdline);
2621 const flashGeometry_t *layout = flashGetGeometry();
2623 if (layout->totalSize == 0) {
2624 cliPrintLine("Flash not available");
2625 return;
2628 cliPrintLine("Erasing...");
2629 flashfsEraseCompletely();
2631 while (!flashIsReady()) {
2632 delay(100);
2635 cliPrintLine("Done.");
2638 #ifdef USE_FLASH_TOOLS
2640 static void cliFlashWrite(char *cmdline)
2642 const uint32_t address = fastA2I(cmdline);
2643 const char *text = strchr(cmdline, ' ');
2645 if (!text) {
2646 cliShowParseError();
2647 } else {
2648 flashfsSeekAbs(address);
2649 flashfsWrite((uint8_t*)text, strlen(text), true);
2650 flashfsFlushSync();
2652 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2656 static void cliFlashRead(char *cmdline)
2658 uint32_t address = fastA2I(cmdline);
2660 const char *nextArg = strchr(cmdline, ' ');
2662 if (!nextArg) {
2663 cliShowParseError();
2664 } else {
2665 uint32_t length = fastA2I(nextArg);
2667 cliPrintLinef("Reading %u bytes at %u:", length, address);
2669 uint8_t buffer[32];
2670 while (length > 0) {
2671 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2673 for (int i = 0; i < bytesRead; i++) {
2674 cliWrite(buffer[i]);
2677 length -= bytesRead;
2678 address += bytesRead;
2680 if (bytesRead == 0) {
2681 //Assume we reached the end of the volume or something fatal happened
2682 break;
2685 cliPrintLinefeed();
2689 #endif
2690 #endif
2692 #ifdef USE_OSD
2693 static void printOsdLayout(uint8_t dumpMask, const osdLayoutsConfig_t *config, const osdLayoutsConfig_t *configDefault, int layout, int item)
2695 // "<layout> <item> <col> <row> <visible>"
2696 const char *format = "osd_layout %d %d %d %d %c";
2697 for (int ii = 0; ii < OSD_LAYOUT_COUNT; ii++) {
2698 if (layout >= 0 && layout != ii) {
2699 continue;
2701 const uint16_t *layoutItems = config->item_pos[ii];
2702 const uint16_t *defaultLayoutItems = configDefault->item_pos[ii];
2703 for (int jj = 0; jj < OSD_ITEM_COUNT; jj++) {
2704 if (item >= 0 && item != jj) {
2705 continue;
2707 bool equalsDefault = layoutItems[jj] == defaultLayoutItems[jj];
2708 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2709 ii, jj,
2710 OSD_X(defaultLayoutItems[jj]),
2711 OSD_Y(defaultLayoutItems[jj]),
2712 OSD_VISIBLE(defaultLayoutItems[jj]) ? 'V' : 'H');
2714 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2715 ii, jj,
2716 OSD_X(layoutItems[jj]),
2717 OSD_Y(layoutItems[jj]),
2718 OSD_VISIBLE(layoutItems[jj]) ? 'V' : 'H');
2723 static void cliOsdLayout(char *cmdline)
2725 char * saveptr;
2727 int layout = -1;
2728 int item = -1;
2729 int col = 0;
2730 int row = 0;
2731 bool visible = false;
2732 char *tok = strtok_r(cmdline, " ", &saveptr);
2734 int ii;
2736 for (ii = 0; tok != NULL; ii++, tok = strtok_r(NULL, " ", &saveptr)) {
2737 switch (ii) {
2738 case 0:
2739 layout = fastA2I(tok);
2740 if (layout < 0 || layout >= OSD_LAYOUT_COUNT) {
2741 cliShowParseError();
2742 return;
2744 break;
2745 case 1:
2746 item = fastA2I(tok);
2747 if (item < 0 || item >= OSD_ITEM_COUNT) {
2748 cliShowParseError();
2749 return;
2751 break;
2752 case 2:
2753 col = fastA2I(tok);
2754 if (col < 0 || col > OSD_X(OSD_POS_MAX)) {
2755 cliShowParseError();
2756 return;
2758 break;
2759 case 3:
2760 row = fastA2I(tok);
2761 if (row < 0 || row > OSD_Y(OSD_POS_MAX)) {
2762 cliShowParseError();
2763 return;
2765 break;
2766 case 4:
2767 switch (*tok) {
2768 case 'H':
2769 visible = false;
2770 break;
2771 case 'V':
2772 visible = true;
2773 break;
2774 default:
2775 cliShowParseError();
2776 return;
2778 break;
2779 default:
2780 cliShowParseError();
2781 return;
2785 switch (ii) {
2786 case 0:
2787 FALLTHROUGH;
2788 case 1:
2789 FALLTHROUGH;
2790 case 2:
2791 // No args, or just layout or layout and item. If any of them not provided,
2792 // it will be the -1 that we used during initialization, so printOsdLayout()
2793 // won't use them for filtering.
2794 printOsdLayout(DUMP_MASTER, osdLayoutsConfig(), osdLayoutsConfig(), layout, item);
2795 break;
2796 case 4:
2797 // No visibility provided. Keep the previous one.
2798 visible = OSD_VISIBLE(osdLayoutsConfig()->item_pos[layout][item]);
2799 FALLTHROUGH;
2800 case 5:
2801 // Layout, item, pos and visibility. Set the item.
2802 osdLayoutsConfigMutable()->item_pos[layout][item] = OSD_POS(col, row) | (visible ? OSD_VISIBLE_FLAG : 0);
2803 break;
2804 default:
2805 // Unhandled
2806 cliShowParseError();
2807 return;
2811 #endif
2813 static void printTimerOutputModes(dumpFlags_e dumpFlags, const timerOverride_t* to, const timerOverride_t* defaultTimerOverride, int timer)
2815 const char *format = "timer_output_mode %d %s";
2817 for (int i = 0; i < HARDWARE_TIMER_DEFINITION_COUNT; ++i) {
2818 if (timer < 0 || timer == i) {
2819 outputMode_e mode = to[i].outputMode;
2820 bool equalsDefault = false;
2821 if(defaultTimerOverride) {
2822 outputMode_e defaultMode = defaultTimerOverride[i].outputMode;
2823 equalsDefault = mode == defaultMode;
2824 cliDefaultPrintLinef(dumpFlags, equalsDefault, format, i, outputModeNames[defaultMode]);
2826 cliDumpPrintLinef(dumpFlags, equalsDefault, format, i, outputModeNames[mode]);
2831 static void cliTimerOutputMode(char *cmdline)
2833 char * saveptr;
2835 int timer = -1;
2836 uint8_t mode;
2837 char *tok = strtok_r(cmdline, " ", &saveptr);
2839 int ii;
2841 for (ii = 0; tok != NULL; ii++, tok = strtok_r(NULL, " ", &saveptr)) {
2842 switch (ii) {
2843 case 0:
2844 timer = fastA2I(tok);
2845 if (timer < 0 || timer >= HARDWARE_TIMER_DEFINITION_COUNT) {
2846 cliShowParseError();
2847 return;
2849 break;
2850 case 1:
2851 if(!sl_strcasecmp("AUTO", tok)) {
2852 mode = OUTPUT_MODE_AUTO;
2853 } else if(!sl_strcasecmp("MOTORS", tok)) {
2854 mode = OUTPUT_MODE_MOTORS;
2855 } else if(!sl_strcasecmp("SERVOS", tok)) {
2856 mode = OUTPUT_MODE_SERVOS;
2857 } else if(!sl_strcasecmp("LED", tok)) {
2858 mode = OUTPUT_MODE_LED;
2859 } else {
2860 cliShowParseError();
2861 return;
2863 break;
2864 default:
2865 cliShowParseError();
2866 return;
2870 switch (ii) {
2871 case 0:
2872 FALLTHROUGH;
2873 case 1:
2874 // No args, or just timer. If any of them not provided,
2875 // it will be the -1 that we used during initialization, so printOsdLayout()
2876 // won't use them for filtering.
2877 printTimerOutputModes(DUMP_MASTER, timerOverrides(0), NULL, timer);
2878 break;
2879 case 2:
2880 timerOverridesMutable(timer)->outputMode = mode;
2881 printTimerOutputModes(DUMP_MASTER, timerOverrides(0), NULL, timer);
2882 break;
2883 default:
2884 // Unhandled
2885 cliShowParseError();
2886 return;
2891 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2893 uint32_t mask = featureConfig->enabledFeatures;
2894 uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2895 for (uint32_t i = 0; ; i++) { // disable all feature first
2896 if (featureNames[i] == NULL)
2897 break;
2898 if (featureNames[i][0] == '\0')
2899 continue;
2900 const char *format = "feature -%s";
2901 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2902 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2904 for (uint32_t i = 0; ; i++) { // reenable what we want.
2905 if (featureNames[i] == NULL)
2906 break;
2907 if (featureNames[i][0] == '\0')
2908 continue;
2909 const char *format = "feature %s";
2910 if (defaultMask & (1 << i)) {
2911 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2913 if (mask & (1 << i)) {
2914 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2919 static void cliFeature(char *cmdline)
2921 uint32_t len = strlen(cmdline);
2922 uint32_t mask = featureMask();
2924 if (len == 0) {
2925 cliPrint("Enabled: ");
2926 for (uint32_t i = 0; ; i++) {
2927 if (featureNames[i] == NULL)
2928 break;
2929 if (featureNames[i][0] == '\0')
2930 continue;
2931 if (mask & (1 << i))
2932 cliPrintf("%s ", featureNames[i]);
2934 cliPrintLinefeed();
2935 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
2936 cliPrint("Available: ");
2937 for (uint32_t i = 0; ; i++) {
2938 if (featureNames[i] == NULL)
2939 break;
2940 if (featureNames[i][0] == '\0')
2941 continue;
2942 cliPrintf("%s ", featureNames[i]);
2944 cliPrintLinefeed();
2945 return;
2946 } else {
2947 bool remove = false;
2948 if (cmdline[0] == '-') {
2949 // remove feature
2950 remove = true;
2951 cmdline++; // skip over -
2952 len--;
2955 for (uint32_t i = 0; ; i++) {
2956 if (featureNames[i] == NULL) {
2957 cliPrintErrorLine("Invalid name");
2958 break;
2961 if (sl_strncasecmp(cmdline, featureNames[i], len) == 0) {
2963 mask = 1 << i;
2964 #ifndef USE_GPS
2965 if (mask & FEATURE_GPS) {
2966 cliPrintErrorLine("unavailable");
2967 break;
2969 #endif
2970 if (remove) {
2971 featureClear(mask);
2972 cliPrint("Disabled");
2973 } else {
2974 featureSet(mask);
2975 cliPrint("Enabled");
2977 cliPrintLinef(" %s", featureNames[i]);
2978 break;
2984 #ifdef USE_BLACKBOX
2985 static void printBlackbox(uint8_t dumpMask, const blackboxConfig_t *config, const blackboxConfig_t *configDefault)
2988 UNUSED(configDefault);
2990 uint32_t mask = config->includeFlags;
2992 for (uint8_t i = 0; ; i++) { // reenable what we want.
2993 if (blackboxIncludeFlagNames[i] == NULL) {
2994 break;
2997 const char *formatOn = "blackbox %s";
2998 const char *formatOff = "blackbox -%s";
3000 if (mask & (1 << i)) {
3001 cliDumpPrintLinef(dumpMask, false, formatOn, blackboxIncludeFlagNames[i]);
3002 cliDefaultPrintLinef(dumpMask, false, formatOn, blackboxIncludeFlagNames[i]);
3003 } else {
3004 cliDumpPrintLinef(dumpMask, false, formatOff, blackboxIncludeFlagNames[i]);
3005 cliDefaultPrintLinef(dumpMask, false, formatOff, blackboxIncludeFlagNames[i]);
3011 static void cliBlackbox(char *cmdline)
3013 uint32_t len = strlen(cmdline);
3014 uint32_t mask = blackboxConfig()->includeFlags;
3016 if (len == 0) {
3017 cliPrint("Enabled: ");
3018 for (uint8_t i = 0; ; i++) {
3019 if (blackboxIncludeFlagNames[i] == NULL) {
3020 break;
3023 if (mask & (1 << i))
3024 cliPrintf("%s ", blackboxIncludeFlagNames[i]);
3026 cliPrintLinefeed();
3027 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
3028 cliPrint("Available: ");
3029 for (uint32_t i = 0; ; i++) {
3030 if (blackboxIncludeFlagNames[i] == NULL) {
3031 break;
3034 cliPrintf("%s ", blackboxIncludeFlagNames[i]);
3036 cliPrintLinefeed();
3037 return;
3038 } else {
3039 bool remove = false;
3040 if (cmdline[0] == '-') {
3041 // remove feature
3042 remove = true;
3043 cmdline++; // skip over -
3044 len--;
3047 for (uint32_t i = 0; ; i++) {
3048 if (blackboxIncludeFlagNames[i] == NULL) {
3049 cliPrintErrorLine("Invalid name");
3050 break;
3053 if (sl_strncasecmp(cmdline, blackboxIncludeFlagNames[i], len) == 0) {
3055 mask = 1 << i;
3057 if (remove) {
3058 blackboxIncludeFlagClear(mask);
3059 cliPrint("Disabled");
3060 } else {
3061 blackboxIncludeFlagSet(mask);
3062 cliPrint("Enabled");
3064 cliPrintLinef(" %s", blackboxIncludeFlagNames[i]);
3065 break;
3070 #endif
3072 #if defined(BEEPER) || defined(USE_DSHOT)
3073 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
3075 const uint8_t beeperCount = beeperTableEntryCount();
3076 const uint32_t mask = beeperConfig->beeper_off_flags;
3077 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
3078 for (int i = 0; i < beeperCount - 2; i++) {
3079 const char *formatOff = "beeper -%s";
3080 const char *formatOn = "beeper %s";
3081 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOn : formatOff, beeperNameForTableIndex(i));
3082 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOff : formatOn, beeperNameForTableIndex(i));
3086 static void cliBeeper(char *cmdline)
3088 uint32_t len = strlen(cmdline);
3089 uint8_t beeperCount = beeperTableEntryCount();
3090 uint32_t mask = getBeeperOffMask();
3092 if (len == 0) {
3093 cliPrintf("Disabled:");
3094 for (int32_t i = 0; ; i++) {
3095 if (i == beeperCount - 2){
3096 if (mask == 0)
3097 cliPrint(" none");
3098 break;
3100 if (mask & (1 << (beeperModeForTableIndex(i) - 1)))
3101 cliPrintf(" %s", beeperNameForTableIndex(i));
3103 cliPrintLinefeed();
3104 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
3105 cliPrint("Available:");
3106 for (uint32_t i = 0; i < beeperCount; i++)
3107 cliPrintf(" %s", beeperNameForTableIndex(i));
3108 cliPrintLinefeed();
3109 return;
3110 } else {
3111 bool remove = false;
3112 if (cmdline[0] == '-') {
3113 remove = true; // this is for beeper OFF condition
3114 cmdline++;
3115 len--;
3118 for (uint32_t i = 0; ; i++) {
3119 if (i == beeperCount) {
3120 cliPrintErrorLine("Invalid name");
3121 break;
3123 if (sl_strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
3124 if (remove) { // beeper off
3125 if (i == BEEPER_ALL-1)
3126 beeperOffSetAll(beeperCount-2);
3127 else
3128 if (i == BEEPER_PREFERENCE-1)
3129 setBeeperOffMask(getPreferredBeeperOffMask());
3130 else {
3131 mask = 1 << (beeperModeForTableIndex(i) - 1);
3132 beeperOffSet(mask);
3134 cliPrint("Disabled");
3136 else { // beeper on
3137 if (i == BEEPER_ALL-1)
3138 beeperOffClearAll();
3139 else
3140 if (i == BEEPER_PREFERENCE-1)
3141 setPreferredBeeperOffMask(getBeeperOffMask());
3142 else {
3143 mask = 1 << (beeperModeForTableIndex(i) - 1);
3144 beeperOffClear(mask);
3146 cliPrint("Enabled");
3148 cliPrintLinef(" %s", beeperNameForTableIndex(i));
3149 break;
3154 #endif
3156 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
3158 bool equalsDefault = true;
3159 char buf[16];
3160 char bufDefault[16];
3161 uint32_t i;
3163 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3164 buf[i] = bufDefault[i] = 0;
3167 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3168 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
3169 if (defaultRxConfig) {
3170 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
3171 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
3174 buf[i] = '\0';
3176 const char *formatMap = "map %s";
3177 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
3178 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
3181 static void cliMap(char *cmdline)
3183 uint32_t len;
3184 char out[MAX_MAPPABLE_RX_INPUTS + 1];
3186 len = strlen(cmdline);
3188 if (len == MAX_MAPPABLE_RX_INPUTS) {
3189 // uppercase it
3190 for (uint32_t i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3191 cmdline[i] = sl_toupper((unsigned char)cmdline[i]);
3193 for (uint32_t i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
3194 if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i])) {
3195 continue;
3197 cliShowParseError();
3198 return;
3200 parseRcChannels(cmdline);
3201 } else if (len != 0) {
3202 cliShowParseError();
3204 cliPrint("Map: ");
3205 uint32_t i;
3206 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++){
3207 out[rxConfig()->rcmap[i]] = rcChannelLetters[i];
3209 out[i] = '\0';
3210 cliPrintLinef("%s", out);
3213 static const char *checkCommand(const char *cmdLine, const char *command)
3215 if (!sl_strncasecmp(cmdLine, command, strlen(command)) // command names match
3216 && !sl_isalnum((unsigned)cmdLine[strlen(command)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
3217 return cmdLine + strlen(command) + 1;
3218 } else {
3219 return 0;
3223 static void cliRebootEx(bool bootLoader)
3225 cliPrint("\r\nRebooting");
3226 bufWriterFlush(cliWriter);
3227 waitForSerialPortToFinishTransmitting(cliPort);
3229 fcReboot(bootLoader);
3232 static void cliReboot(void)
3234 cliRebootEx(false);
3237 static void cliDfu(char *cmdline)
3239 UNUSED(cmdline);
3240 #ifndef CLI_MINIMAL_VERBOSITY
3241 cliPrint("\r\nRestarting in DFU mode");
3242 #endif
3243 cliRebootEx(true);
3246 #if defined (USE_SERIALRX_SRXL2)
3247 void cliRxBind(char *cmdline){
3248 UNUSED(cmdline);
3249 if (rxConfig()->receiverType == RX_TYPE_SERIAL) {
3250 switch (rxConfig()->serialrx_provider) {
3251 default:
3252 cliPrint("Not supported.");
3253 break;
3254 #if defined(USE_SERIALRX_SRXL2)
3255 case SERIALRX_SRXL2:
3256 srxl2Bind();
3257 cliPrint("Binding SRXL2 receiver...");
3258 break;
3259 #endif
3260 #if defined(USE_SERIALRX_CRSF)
3261 case SERIALRX_CRSF:
3262 crsfBind();
3263 cliPrint("Binding CRSF receiver...");
3264 break;
3265 #endif
3269 #endif
3271 static void cliExit(char *cmdline)
3273 UNUSED(cmdline);
3275 #ifndef CLI_MINIMAL_VERBOSITY
3276 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
3277 #endif
3278 bufWriterFlush(cliWriter);
3280 *cliBuffer = '\0';
3281 bufferIndex = 0;
3282 cliMode = false;
3283 // incase a motor was left running during motortest, clear it here
3284 mixerResetDisarmedMotors();
3285 cliReboot();
3287 cliWriter = NULL;
3290 #ifdef USE_GPS
3291 static void cliGpsPassthrough(char *cmdline)
3293 UNUSED(cmdline);
3295 gpsEnablePassthrough(cliPort);
3297 #endif
3299 static void cliMotor(char *cmdline)
3301 int motor_index = 0;
3302 int motor_value = 0;
3303 int index = 0;
3304 char *pch = NULL;
3305 char *saveptr;
3307 if (isEmpty(cmdline)) {
3308 cliShowParseError();
3310 return;
3313 pch = strtok_r(cmdline, " ", &saveptr);
3314 while (pch != NULL) {
3315 switch (index) {
3316 case 0:
3317 motor_index = fastA2I(pch);
3318 break;
3319 case 1:
3320 motor_value = fastA2I(pch);
3321 break;
3323 index++;
3324 pch = strtok_r(NULL, " ", &saveptr);
3327 if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
3328 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
3329 return;
3332 if (index == 2) {
3333 if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
3334 cliShowArgumentRangeError("value", 1000, 2000);
3335 return;
3336 } else {
3337 motor_disarmed[motor_index] = motor_value;
3341 cliPrintLinef("motor %d: %d", motor_index, motor_disarmed[motor_index]);
3344 static void cliPlaySound(char *cmdline)
3346 int i;
3347 const char *name;
3348 static int lastSoundIdx = -1;
3350 if (isEmpty(cmdline)) {
3351 i = lastSoundIdx + 1; //next sound index
3352 if ((name=beeperNameForTableIndex(i)) == NULL) {
3353 while (true) { //no name for index; try next one
3354 if (++i >= beeperTableEntryCount())
3355 i = 0; //if end then wrap around to first entry
3356 if ((name=beeperNameForTableIndex(i)) != NULL)
3357 break; //if name OK then play sound below
3358 if (i == lastSoundIdx + 1) { //prevent infinite loop
3359 cliPrintLine("Error playing sound");
3360 return;
3364 } else { //index value was given
3365 i = fastA2I(cmdline);
3366 if ((name=beeperNameForTableIndex(i)) == NULL) {
3367 cliPrintLinef("No sound for index %d", i);
3368 return;
3371 lastSoundIdx = i;
3372 beeperSilence();
3373 cliPrintLinef("Playing sound %d: %s", i, name);
3374 beeper(beeperModeForTableIndex(i));
3377 static void cliControlProfile(char *cmdline)
3379 // CLI profile index is 1-based
3380 if (isEmpty(cmdline)) {
3381 cliPrintLinef("control_profile %d", getConfigProfile() + 1);
3382 return;
3383 } else {
3384 const int i = fastA2I(cmdline) - 1;
3385 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3386 setConfigProfileAndWriteEEPROM(i);
3387 cliControlProfile("");
3392 static void cliDumpControlProfile(uint8_t profileIndex, uint8_t dumpMask)
3394 if (profileIndex >= MAX_PROFILE_COUNT) {
3395 // Faulty values
3396 return;
3398 setConfigProfile(profileIndex);
3399 cliPrintHashLine("control_profile");
3400 cliPrintLinef("control_profile %d\r\n", getConfigProfile() + 1);
3401 dumpAllValues(PROFILE_VALUE, dumpMask);
3402 dumpAllValues(CONTROL_RATE_VALUE, dumpMask);
3403 dumpAllValues(EZ_TUNE_VALUE, dumpMask);
3406 static void cliBatteryProfile(char *cmdline)
3408 // CLI profile index is 1-based
3409 if (isEmpty(cmdline)) {
3410 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
3411 return;
3412 } else {
3413 const int i = fastA2I(cmdline) - 1;
3414 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3415 setConfigBatteryProfileAndWriteEEPROM(i);
3416 cliBatteryProfile("");
3421 static void cliDumpBatteryProfile(uint8_t profileIndex, uint8_t dumpMask)
3423 if (profileIndex >= MAX_BATTERY_PROFILE_COUNT) {
3424 // Faulty values
3425 return;
3427 setConfigBatteryProfile(profileIndex);
3428 cliPrintHashLine("battery_profile");
3429 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
3430 dumpAllValues(BATTERY_CONFIG_VALUE, dumpMask);
3433 static void cliMixerProfile(char *cmdline)
3435 // CLI profile index is 1-based
3436 if (isEmpty(cmdline)) {
3437 cliPrintLinef("mixer_profile %d", getConfigMixerProfile() + 1);
3438 return;
3439 } else {
3440 const int i = fastA2I(cmdline) - 1;
3441 if (i >= 0 && i < MAX_MIXER_PROFILE_COUNT) {
3442 setConfigMixerProfileAndWriteEEPROM(i);
3443 cliMixerProfile("");
3448 static void cliDumpMixerProfile(uint8_t profileIndex, uint8_t dumpMask)
3450 if (profileIndex >= MAX_MIXER_PROFILE_COUNT) {
3451 // Faulty values
3452 return;
3454 setConfigMixerProfile(profileIndex);
3455 cliPrintHashLine("mixer_profile");
3456 cliPrintLinef("mixer_profile %d\r\n", getConfigMixerProfile() + 1);
3457 dumpAllValues(MIXER_CONFIG_VALUE, dumpMask);
3458 cliPrintHashLine("Mixer: motor mixer");
3459 cliDumpPrintLinef(dumpMask, primaryMotorMixer_CopyArray()[0].throttle == 0.0f, "\r\nmmix reset\r\n");
3460 printMotorMix(dumpMask, primaryMotorMixer_CopyArray(), primaryMotorMixer(0));
3461 cliPrintHashLine("Mixer: servo mixer");
3462 cliDumpPrintLinef(dumpMask, customServoMixers_CopyArray()[0].rate == 0, "smix reset\r\n");
3463 printServoMix(dumpMask, customServoMixers_CopyArray(), customServoMixers(0));
3466 #ifdef USE_CLI_BATCH
3467 static void cliPrintCommandBatchWarning(const char *warning)
3469 char errorBuf[59];
3470 tfp_sprintf(errorBuf, "%d ERRORS WERE DETECTED - Please review and fix before continuing!", commandBatchErrorCount);
3472 cliPrintErrorLinef(errorBuf);
3473 if (warning) {
3474 cliPrintErrorLinef(warning);
3478 static void resetCommandBatch(void)
3480 commandBatchActive = false;
3481 commandBatchError = false;
3482 commandBatchErrorCount = 0;
3485 static void cliBatch(char *cmdline)
3487 if (strncasecmp(cmdline, "start", 5) == 0) {
3488 if (!commandBatchActive) {
3489 commandBatchActive = true;
3490 commandBatchError = false;
3491 commandBatchErrorCount = 0;
3493 cliPrintLine("Command batch started");
3494 } else if (strncasecmp(cmdline, "end", 3) == 0) {
3495 if (commandBatchActive && commandBatchError) {
3496 cliPrintCommandBatchWarning(NULL);
3497 } else {
3498 cliPrintLine("Command batch ended");
3500 resetCommandBatch();
3501 } else {
3502 cliPrintErrorLinef("Invalid option");
3505 #endif
3507 static void cliSave(char *cmdline)
3509 UNUSED(cmdline);
3511 #ifdef USE_CLI_BATCH
3512 if (commandBatchActive && commandBatchError) {
3513 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3514 resetCommandBatch();
3515 return;
3517 #endif
3519 cliPrint("Saving");
3520 //copyCurrentProfileToProfileSlot(getConfigProfile();
3521 suspendRxSignal();
3522 writeEEPROM();
3523 resumeRxSignal();
3524 cliReboot();
3527 static void cliDefaults(char *cmdline)
3529 UNUSED(cmdline);
3531 cliPrint("Resetting to defaults");
3532 resetEEPROM();
3533 suspendRxSignal();
3534 writeEEPROM();
3535 resumeRxSignal();
3537 #ifdef USE_CLI_BATCH
3538 commandBatchError = false;
3539 #endif
3541 if (!checkCommand(cmdline, "noreboot"))
3542 cliReboot();
3545 static void cliGet(char *cmdline)
3547 const setting_t *val;
3548 int matchedCommands = 0;
3549 char name[SETTING_MAX_NAME_LENGTH];
3551 while(*cmdline == ' ') ++cmdline; // ignore spaces
3553 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3554 val = settingGet(i);
3555 if (settingNameContains(val, name, cmdline)) {
3556 cliPrintf("%s = ", name);
3557 if (strcmp(name, "name") == 0) {
3558 // if the craftname has a leading space, then enclose the name in quotes
3559 const char * v = (const char *)settingGetValuePointer(val);
3560 cliPrintf(v[0] == ' ' ? "\"%s\"" : "%s", v);
3561 } else {
3562 cliPrintVar(val, 0);
3564 cliPrintLinefeed();
3565 cliPrintVarRange(val);
3566 cliPrintLinefeed();
3568 matchedCommands++;
3573 if (matchedCommands) {
3574 return;
3577 cliPrintErrorLine("Invalid name");
3580 static void cliSet(char *cmdline)
3582 uint32_t len;
3583 const setting_t *val;
3584 char *eqptr = NULL;
3585 char name[SETTING_MAX_NAME_LENGTH];
3587 while(*cmdline == ' ') ++cmdline; // ignore spaces
3589 len = strlen(cmdline);
3591 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
3592 cliPrintLine("Current settings:");
3593 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3594 val = settingGet(i);
3595 settingGetName(val, name);
3596 cliPrintf("%s = ", name);
3597 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3598 cliPrintLinefeed();
3600 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
3601 // has equals
3603 char *lastNonSpaceCharacter = eqptr;
3604 while (*(lastNonSpaceCharacter - 1) == ' ') {
3605 lastNonSpaceCharacter--;
3607 uint8_t variableNameLength = lastNonSpaceCharacter - cmdline;
3609 // skip the '=' and any ' ' characters
3610 eqptr++;
3611 while (*(eqptr) == ' ') {
3612 eqptr++;
3615 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3616 val = settingGet(i);
3617 // ensure exact match when setting to prevent setting variables with shorter names
3618 if (settingNameIsExactMatch(val, name, cmdline, variableNameLength)) {
3619 const setting_type_e type = SETTING_TYPE(val);
3620 if (type == VAR_STRING) {
3621 // Convert strings to uppercase. Lower case is not supported by the OSD.
3622 sl_toupperptr(eqptr);
3623 // if setting the craftname, remove any quotes around the name. This allows leading spaces in the name
3624 if ((strcmp(name, "name") == 0 || strcmp(name, "pilot_name") == 0) && (eqptr[0] == '"' && eqptr[strlen(eqptr)-1] == '"')) {
3625 settingSetString(val, eqptr + 1, strlen(eqptr)-2);
3626 } else {
3627 settingSetString(val, eqptr, strlen(eqptr));
3629 return;
3631 const setting_mode_e mode = SETTING_MODE(val);
3632 bool changeValue = false;
3633 int_float_value_t tmp = {0};
3634 switch (mode) {
3635 case MODE_DIRECT: {
3636 if (*eqptr != 0 && strspn(eqptr, "0123456789.+-") == strlen(eqptr)) {
3637 float valuef = fastA2F(eqptr);
3638 // note: compare float values
3639 if (valuef >= (float)settingGetMin(val) && valuef <= (float)settingGetMax(val)) {
3641 if (type == VAR_FLOAT)
3642 tmp.float_value = valuef;
3643 else if (type == VAR_UINT32)
3644 tmp.uint_value = fastA2UL(eqptr);
3645 else
3646 tmp.int_value = fastA2I(eqptr);
3648 changeValue = true;
3652 break;
3653 case MODE_LOOKUP: {
3654 const lookupTableEntry_t *tableEntry = settingLookupTable(val);
3655 bool matched = false;
3656 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
3657 matched = sl_strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
3659 if (matched) {
3660 tmp.int_value = tableValueIndex;
3661 changeValue = true;
3665 break;
3668 if (changeValue) {
3669 cliSetIntFloatVar(val, tmp);
3671 cliPrintf("%s set to ", name);
3672 cliPrintVar(val, 0);
3673 } else {
3674 cliPrintError("Invalid value. ");
3675 cliPrintVarRange(val);
3676 cliPrintLinefeed();
3679 return;
3682 cliPrintErrorLine("Invalid name");
3683 } else {
3684 // no equals, check for matching variables.
3685 cliGet(cmdline);
3689 static const char * getBatteryStateString(void)
3691 static const char * const batteryStateStrings[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
3693 return batteryStateStrings[getBatteryState()];
3696 static void cliStatus(char *cmdline)
3698 UNUSED(cmdline);
3700 char buf[MAX(FORMATTED_DATE_TIME_BUFSIZE, SETTING_MAX_NAME_LENGTH)];
3701 dateTime_t dt;
3703 cliPrintLinef("%s/%s %s %s / %s (%s) %s",
3704 FC_FIRMWARE_NAME,
3705 targetName,
3706 FC_VERSION_STRING,
3707 buildDate,
3708 buildTime,
3709 shortGitRevision,
3710 FC_VERSION_TYPE
3712 cliPrintLinef("GCC-%s",
3713 compilerVersion
3715 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3716 rtcGetDateTime(&dt);
3717 dateTimeFormatLocal(buf, &dt);
3718 cliPrintLinef("Current Time: %s", buf);
3719 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
3720 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3722 const uint32_t detectedSensorsMask = sensorsMask();
3724 for (int i = 0; i < SENSOR_INDEX_COUNT; i++) {
3726 const uint32_t mask = (1 << i);
3727 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3728 const int sensorHardwareIndex = detectedSensors[i];
3729 if (sensorHardwareNames[i]) {
3730 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3731 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3735 cliPrintLinefeed();
3736 #if !defined(SITL_BUILD)
3737 #if defined(AT32F43x)
3738 cliPrintLine("AT32 system clocks:");
3739 crm_clocks_freq_type clocks;
3740 crm_clocks_freq_get(&clocks);
3742 cliPrintLinef(" SYSCLK = %d MHz", clocks.sclk_freq / 1000000);
3743 cliPrintLinef(" ABH = %d MHz", clocks.ahb_freq / 1000000);
3744 cliPrintLinef(" ABP1 = %d MHz", clocks.apb1_freq / 1000000);
3745 cliPrintLinef(" ABP2 = %d MHz", clocks.apb2_freq / 1000000);
3746 #else
3747 cliPrintLine("STM32 system clocks:");
3748 #if defined(USE_HAL_DRIVER)
3749 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
3750 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
3751 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
3752 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
3753 #else
3754 RCC_ClocksTypeDef clocks;
3755 RCC_GetClocksFreq(&clocks);
3756 cliPrintLinef(" SYSCLK = %d MHz", clocks.SYSCLK_Frequency / 1000000);
3757 cliPrintLinef(" HCLK = %d MHz", clocks.HCLK_Frequency / 1000000);
3758 cliPrintLinef(" PCLK1 = %d MHz", clocks.PCLK1_Frequency / 1000000);
3759 cliPrintLinef(" PCLK2 = %d MHz", clocks.PCLK2_Frequency / 1000000);
3760 #endif
3761 #endif // for if at32
3762 #endif // for SITL
3764 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
3765 hardwareSensorStatusNames[getHwGyroStatus()],
3766 hardwareSensorStatusNames[getHwAccelerometerStatus()],
3767 hardwareSensorStatusNames[getHwCompassStatus()],
3768 hardwareSensorStatusNames[getHwBarometerStatus()],
3769 hardwareSensorStatusNames[getHwRangefinderStatus()],
3770 hardwareSensorStatusNames[getHwOpticalFlowStatus()],
3771 hardwareSensorStatusNames[getHwGPSStatus()]
3774 #ifdef USE_ESC_SENSOR
3775 uint8_t motorCount = getMotorCount();
3776 if (STATE(ESC_SENSOR_ENABLED) && motorCount > 0) {
3777 cliPrintLinef("ESC Temperature(s): Motor Count = %d", motorCount);
3778 for (uint8_t i = 0; i < motorCount; i++) {
3779 const escSensorData_t *escState = getEscTelemetry(i); //Get ESC telemetry
3780 cliPrintf("ESC %d: %d\260C, ", i, escState->temperature);
3782 cliPrintLinefeed();
3784 #endif
3786 #ifdef USE_SDCARD
3787 cliSdInfo(NULL);
3788 #endif
3789 #ifdef USE_I2C
3790 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3791 #elif !defined(SITL_BUILD)
3792 const uint16_t i2cErrorCounter = 0;
3793 #endif
3795 #ifdef STACK_CHECK
3796 cliPrintf("Stack used: %d, ", stackUsedSize());
3797 #endif
3798 #if !defined(SITL_BUILD)
3799 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
3801 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
3802 #endif
3803 #if defined(USE_ADC) && !defined(SITL_BUILD)
3804 static char * adcFunctions[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
3805 cliPrintLine("ADC channel usage:");
3806 for (int i = 0; i < ADC_FUNCTION_COUNT; i++) {
3807 cliPrintf(" %8s :", adcFunctions[i]);
3809 cliPrint(" configured = ");
3810 if (adcChannelConfig()->adcFunctionChannel[i] == ADC_CHN_NONE) {
3811 cliPrint("none");
3813 else {
3814 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel[i]);
3817 cliPrint(", used = ");
3818 if (adcGetFunctionChannelAllocation(i) == ADC_CHN_NONE) {
3819 cliPrintLine("none");
3821 else {
3822 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i));
3825 #endif
3827 cliPrintf("System load: %d", averageSystemLoadPercent);
3828 const timeDelta_t pidTaskDeltaTime = getTaskDeltaTime(TASK_PID);
3829 const int pidRate = pidTaskDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)pidTaskDeltaTime));
3830 const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
3831 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3832 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime, pidRate, rxRate, systemRate);
3833 #if !defined(CLI_MINIMAL_VERBOSITY)
3834 cliPrint("Arming disabled flags:");
3835 uint32_t flags = armingFlags & ARMING_DISABLED_ALL_FLAGS;
3836 while (flags) {
3837 int bitpos = ffs(flags) - 1;
3838 flags &= ~(1 << bitpos);
3839 if (bitpos > 6) cliPrintf(" %s", armingDisableFlagNames[bitpos - 7]);
3841 cliPrintLinefeed();
3842 if (armingFlags & ARMING_DISABLED_INVALID_SETTING) {
3843 unsigned invalidIndex;
3844 if (!settingsValidate(&invalidIndex)) {
3845 settingGetName(settingGet(invalidIndex), buf);
3846 cliPrintErrorLinef("Invalid setting: %s", buf);
3849 #else
3850 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags & ARMING_DISABLED_ALL_FLAGS);
3851 #endif
3853 #if !defined(CLI_MINIMAL_VERBOSITY)
3854 cliPrint("OSD: ");
3855 #if defined(USE_OSD)
3856 displayPort_t *osdDisplayPort = osdGetDisplayPort();
3857 if (osdDisplayPort != NULL) {
3858 cliPrintf("%s [%u x %u]", osdDisplayPort->displayPortType, osdDisplayPort->cols, osdDisplayPort->rows);
3859 } else {
3860 cliPrint("not enabled");
3862 #else
3863 cliPrint("not used");
3864 #endif
3865 cliPrintLinefeed();
3867 cliPrint("VTX: ");
3868 #if defined(USE_VTX_CONTROL)
3869 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
3870 vtxDeviceOsdInfo_t osdInfo;
3871 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo);
3872 cliPrintf("band: %c, chan: %s, power: %c", osdInfo.bandLetter, osdInfo.channelName, osdInfo.powerIndexLetter);
3874 if (osdInfo.powerMilliwatt) {
3875 cliPrintf(" (%d mW)", osdInfo.powerMilliwatt);
3878 if (osdInfo.frequency) {
3879 cliPrintf(", freq: %d MHz", osdInfo.frequency);
3882 else {
3883 cliPrint("not detected");
3885 #else
3886 cliPrint("no VTX control");
3887 #endif
3889 cliPrintLinefeed();
3890 #endif
3892 if (featureConfigured(FEATURE_GPS) && isGpsUblox()) {
3893 cliPrint("GPS: ");
3894 cliPrintf("HW Version: %s Proto: %d.%02d Baud: %d", getGpsHwVersion(), getGpsProtoMajorVersion(), getGpsProtoMinorVersion(), getGpsBaudrate());
3895 if(ubloxVersionLT(15, 0)) {
3896 cliPrintf(" (UBLOX Proto >= 15.0 required)");
3898 cliPrintLinefeed();
3899 cliPrintLinef(" SATS: %i", gpsSol.numSat);
3900 cliPrintLinef(" HDOP: %f", (double)(gpsSol.hdop / (float)HDOP_SCALE));
3901 cliPrintLinef(" EPH : %f m", (double)(gpsSol.eph / 100.0f));
3902 cliPrintLinef(" EPV : %f m", (double)(gpsSol.epv / 100.0f));
3903 //cliPrintLinef(" GNSS Capabilities: %d", gpsUbloxCapLastUpdate());
3904 cliPrintLinef(" GNSS Capabilities:");
3905 cliPrintLine(" GNSS Provider active/default");
3906 cliPrintLine(" GPS 1/1");
3907 if(gpsUbloxHasGalileo())
3908 cliPrintLinef(" Galileo %d/%d", gpsUbloxGalileoEnabled(), gpsUbloxGalileoDefault());
3909 if(gpsUbloxHasBeidou())
3910 cliPrintLinef(" BeiDou %d/%d", gpsUbloxBeidouEnabled(), gpsUbloxBeidouDefault());
3911 if(gpsUbloxHasGlonass())
3912 cliPrintLinef(" Glonass %d/%d", gpsUbloxGlonassEnabled(), gpsUbloxGlonassDefault());
3913 cliPrintLinef(" Max concurrent: %d", gpsUbloxMaxGnss());
3916 // If we are blocked by PWM init - provide more information
3917 if (getPwmInitError() != PWM_INIT_ERROR_NONE) {
3918 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3922 static void cliTasks(char *cmdline)
3924 UNUSED(cmdline);
3925 int maxLoadSum = 0;
3926 int averageLoadSum = 0;
3927 cfCheckFuncInfo_t checkFuncInfo;
3929 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3930 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3931 cfTaskInfo_t taskInfo;
3932 getTaskInfo(taskId, &taskInfo);
3933 if (taskInfo.isEnabled) {
3934 const int taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3935 const int maxLoad = (taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3936 const int averageLoad = (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3937 if (taskId != TASK_SERIAL) {
3938 maxLoadSum += maxLoad;
3939 averageLoadSum += averageLoad;
3941 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
3942 taskId, taskInfo.taskName, taskFrequency, (uint32_t)taskInfo.maxExecutionTime, (uint32_t)taskInfo.averageExecutionTime,
3943 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, (uint32_t)taskInfo.totalExecutionTime / 1000);
3946 getCheckFuncInfo(&checkFuncInfo);
3947 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo.maxExecutionTime, (uint32_t)checkFuncInfo.averageExecutionTime, (uint32_t)checkFuncInfo.totalExecutionTime / 1000);
3948 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3951 static void cliVersion(char *cmdline)
3953 UNUSED(cmdline);
3955 cliPrintLinef("# %s/%s %s %s / %s (%s) %s",
3956 FC_FIRMWARE_NAME,
3957 targetName,
3958 FC_VERSION_STRING,
3959 buildDate,
3960 buildTime,
3961 shortGitRevision,
3962 FC_VERSION_TYPE
3964 cliPrintLinef("# GCC-%s",
3965 compilerVersion
3969 static void cliMemory(char *cmdline)
3971 UNUSED(cmdline);
3972 cliPrintLinef("Dynamic memory usage:");
3973 for (unsigned i = 0; i < OWNER_TOTAL_COUNT; i++) {
3974 const char * owner = ownerNames[i];
3975 const uint32_t memUsed = memGetUsedBytesByOwner(i);
3977 if (memUsed) {
3978 cliPrintLinef("%s : %d bytes", owner, memUsed);
3983 static void cliResource(char *cmdline)
3985 UNUSED(cmdline);
3986 cliPrintLinef("IO:\r\n----------------------");
3987 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3988 const char* owner;
3989 owner = ownerNames[ioRecs[i].owner];
3991 const char* resource;
3992 resource = resourceNames[ioRecs[i].resource];
3994 if (ioRecs[i].index > 0) {
3995 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, ioRecs[i].index, resource);
3996 } else {
3997 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, resource);
4002 static void backupConfigs(void)
4004 // make copies of configs to do differencing
4005 PG_FOREACH(pg) {
4006 if (pgIsProfile(pg)) {
4007 memcpy(pg->copy, pg->address, pgSize(pg) * MAX_PROFILE_COUNT);
4008 } else {
4009 memcpy(pg->copy, pg->address, pgSize(pg));
4014 static void restoreConfigs(void)
4016 PG_FOREACH(pg) {
4017 if (pgIsProfile(pg)) {
4018 memcpy(pg->address, pg->copy, pgSize(pg) * MAX_PROFILE_COUNT);
4019 } else {
4020 memcpy(pg->address, pg->copy, pgSize(pg));
4025 static void printConfig(const char *cmdline, bool doDiff)
4027 uint8_t dumpMask = DUMP_MASTER;
4028 const char *options;
4029 if ((options = checkCommand(cmdline, "master"))) {
4030 dumpMask = DUMP_MASTER; // only
4031 } else if ((options = checkCommand(cmdline, "control_profile"))) {
4032 dumpMask = DUMP_CONTROL_PROFILE; // only
4033 } else if ((options = checkCommand(cmdline, "mixer_profile"))) {
4034 dumpMask = DUMP_MIXER_PROFILE; // only
4035 } else if ((options = checkCommand(cmdline, "battery_profile"))) {
4036 dumpMask = DUMP_BATTERY_PROFILE; // only
4037 } else if ((options = checkCommand(cmdline, "all"))) {
4038 dumpMask = DUMP_ALL; // all profiles and rates
4039 } else {
4040 options = cmdline;
4043 if (doDiff) {
4044 dumpMask = dumpMask | DO_DIFF;
4047 const int currentControlProfileIndexSave = getConfigProfile();
4048 const int currentMixerProfileIndexSave = getConfigMixerProfile();
4049 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
4050 backupConfigs();
4051 // reset all configs to defaults to do differencing
4052 resetConfigs();
4053 // restore the profile indices, since they should not be reset for proper comparison
4054 setConfigProfile(currentControlProfileIndexSave);
4055 setConfigMixerProfile(currentMixerProfileIndexSave);
4056 setConfigBatteryProfile(currentBatteryProfileIndexSave);
4058 if (checkCommand(options, "showdefaults")) {
4059 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
4062 #ifdef USE_CLI_BATCH
4063 bool batchModeEnabled = false;
4064 #endif
4066 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
4067 cliPrintHashLine("version");
4068 cliVersion(NULL);
4070 #ifdef USE_CLI_BATCH
4071 cliPrintHashLine("start the command batch");
4072 cliPrintLine("batch start");
4073 batchModeEnabled = true;
4074 #endif
4076 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
4077 #ifndef CLI_MINIMAL_VERBOSITY
4078 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
4079 #else
4080 cliPrintLinef("defaults noreboot");
4081 #endif
4084 cliPrintHashLine("resources");
4085 //printResource(dumpMask, &defaultConfig);
4087 cliPrintHashLine("Timer overrides");
4088 printTimerOutputModes(dumpMask, timerOverrides_CopyArray, timerOverrides(0), -1);
4090 // print servo parameters
4091 cliPrintHashLine("Outputs [servo]");
4092 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
4094 #if defined(USE_SAFE_HOME)
4095 cliPrintHashLine("safehome");
4096 printSafeHomes(dumpMask, safeHomeConfig_CopyArray, safeHomeConfig(0));
4097 #endif
4099 #ifdef USE_FW_AUTOLAND
4100 cliPrintHashLine("Fixed Wing Approach");
4101 printFwAutolandApproach(dumpMask, fwAutolandApproachConfig_CopyArray, fwAutolandApproachConfig(0));
4102 #endif
4104 cliPrintHashLine("features");
4105 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
4107 #if defined(BEEPER) || defined(USE_DSHOT)
4108 cliPrintHashLine("beeper");
4109 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
4110 #endif
4112 #ifdef USE_BLACKBOX
4113 cliPrintHashLine("blackbox");
4114 printBlackbox(dumpMask, &blackboxConfig_Copy, blackboxConfig());
4115 #endif
4117 cliPrintHashLine("Receiver: Channel map");
4118 printMap(dumpMask, &rxConfig_Copy, rxConfig());
4120 cliPrintHashLine("Ports");
4121 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
4123 #ifdef USE_LED_STRIP
4124 cliPrintHashLine("LEDs");
4125 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
4127 cliPrintHashLine("LED color");
4128 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
4130 cliPrintHashLine("LED mode_color");
4131 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
4132 #endif
4134 cliPrintHashLine("Modes [aux]");
4135 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
4137 cliPrintHashLine("Adjustments [adjrange]");
4138 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
4140 cliPrintHashLine("Receiver rxrange");
4141 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
4143 #ifdef USE_TEMPERATURE_SENSOR
4144 cliPrintHashLine("temp_sensor");
4145 printTempSensor(dumpMask, tempSensorConfig_CopyArray, tempSensorConfig(0));
4146 #endif
4148 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4149 cliPrintHashLine("Mission Control Waypoints [wp]");
4150 printWaypoints(dumpMask, posControl.waypointList, nonVolatileWaypointList(0));
4151 #endif
4153 #ifdef USE_OSD
4154 cliPrintHashLine("OSD [osd_layout]");
4155 printOsdLayout(dumpMask, &osdLayoutsConfig_Copy, osdLayoutsConfig(), -1, -1);
4156 #endif
4158 #ifdef USE_PROGRAMMING_FRAMEWORK
4159 cliPrintHashLine("Programming: logic");
4160 printLogic(dumpMask, logicConditions_CopyArray, logicConditions(0), -1);
4162 cliPrintHashLine("Programming: global variables");
4163 printGvar(dumpMask, globalVariableConfigs_CopyArray, globalVariableConfigs(0));
4165 cliPrintHashLine("Programming: PID controllers");
4166 printPid(dumpMask, programmingPids_CopyArray, programmingPids(0));
4167 #endif
4168 #ifdef USE_PROGRAMMING_FRAMEWORK
4169 cliPrintHashLine("OSD: custom elements");
4170 printOsdCustomElements(dumpMask, osdCustomElements_CopyArray, osdCustomElements(0));
4171 #endif
4173 cliPrintHashLine("master");
4174 dumpAllValues(MASTER_VALUE, dumpMask);
4176 if (dumpMask & DUMP_ALL) {
4177 // dump all profiles
4178 const int currentControlProfileIndexSave = getConfigProfile();
4179 const int currentMixerProfileIndexSave = getConfigMixerProfile();
4180 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
4181 for (int ii = 0; ii < MAX_PROFILE_COUNT; ++ii) {
4182 cliDumpControlProfile(ii, dumpMask);
4184 for (int ii = 0; ii < MAX_MIXER_PROFILE_COUNT; ++ii) {
4185 cliDumpMixerProfile(ii, dumpMask);
4187 for (int ii = 0; ii < MAX_BATTERY_PROFILE_COUNT; ++ii) {
4188 cliDumpBatteryProfile(ii, dumpMask);
4190 setConfigProfile(currentControlProfileIndexSave);
4191 setConfigMixerProfile(currentMixerProfileIndexSave);
4192 setConfigBatteryProfile(currentBatteryProfileIndexSave);
4194 cliPrintHashLine("restore original profile selection");
4195 cliPrintLinef("control_profile %d", currentControlProfileIndexSave + 1);
4196 cliPrintLinef("mixer_profile %d", currentMixerProfileIndexSave + 1);
4197 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave + 1);
4199 #ifdef USE_CLI_BATCH
4200 batchModeEnabled = false;
4201 #endif
4202 } else {
4203 // dump just the current profiles
4204 cliDumpControlProfile(getConfigProfile(), dumpMask);
4205 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask);
4206 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
4210 if (dumpMask & DUMP_CONTROL_PROFILE) {
4211 cliDumpControlProfile(getConfigProfile(), dumpMask);
4214 if (dumpMask & DUMP_MIXER_PROFILE) {
4215 cliDumpMixerProfile(getConfigMixerProfile(), dumpMask);
4218 if (dumpMask & DUMP_BATTERY_PROFILE) {
4219 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
4222 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
4223 cliPrintHashLine("save configuration\r\nsave");
4226 #ifdef USE_CLI_BATCH
4227 if (batchModeEnabled) {
4228 cliPrintHashLine("end the command batch");
4229 cliPrintLine("batch end");
4231 #endif
4233 // restore configs from copies
4234 restoreConfigs();
4237 static void cliDump(char *cmdline)
4239 printConfig(cmdline, false);
4242 static void cliDiff(char *cmdline)
4244 printConfig(cmdline, true);
4247 #ifdef USE_USB_MSC
4248 static void cliMsc(char *cmdline)
4250 UNUSED(cmdline);
4252 if (false
4253 #ifdef USE_SDCARD
4254 || sdcard_isFunctional()
4255 #endif
4256 #ifdef USE_FLASHFS
4257 || flashfsGetSize() > 0
4258 #endif
4260 cliPrintHashLine("restarting in mass storage mode");
4261 cliPrint("\r\nRebooting");
4262 bufWriterFlush(cliWriter);
4263 delay(1000);
4264 waitForSerialPortToFinishTransmitting(cliPort);
4265 stopPwmAllMotors();
4266 systemResetRequest(RESET_MSC_REQUEST);
4267 } else {
4268 cliPrint("\r\nStorage not present or failed to initialize!");
4269 bufWriterFlush(cliWriter);
4272 #endif
4275 typedef struct {
4276 const char *name;
4277 #ifndef SKIP_CLI_COMMAND_HELP
4278 const char *description;
4279 const char *args;
4280 #endif
4281 void (*func)(char *cmdline);
4282 } clicmd_t;
4284 #ifndef SKIP_CLI_COMMAND_HELP
4285 #define CLI_COMMAND_DEF(name, description, args, method) \
4287 name , \
4288 description , \
4289 args , \
4290 method \
4292 #else
4293 #define CLI_COMMAND_DEF(name, description, args, method) \
4295 name, \
4296 method \
4298 #endif
4300 static void cliCmdDebug(char *arg)
4302 UNUSED(arg);
4303 if (debugMode != DEBUG_NONE) {
4304 cliPrintLinef("Debug fields: [%s (%i)]", debugMode < DEBUG_COUNT ? debugModeNames[debugMode] : "unknown", debugMode);
4305 for (int i = 0; i < DEBUG32_VALUE_COUNT; i++) {
4306 cliPrintLinef("debug[%d] = %d", i, debug[i]);
4308 } else {
4309 cliPrintLine("Debug mode is disabled");
4314 #if defined(USE_GPS) && defined(USE_GPS_PROTO_UBLOX)
4316 static const char* _ubloxGetSigId(uint8_t gnssId, uint8_t sigId)
4318 if(gnssId == 0) {
4319 switch(sigId) {
4320 case 0: return "GPS L1C/A";
4321 case 3: return "GPS L2 CL";
4322 case 4: return "GPS L2 CM";
4323 case 6: return "GPS L5 I";
4324 case 7: return "GPS L5 Q";
4325 default: return "GPS Unknown";
4327 } else if(gnssId == 1) {
4328 switch(sigId) {
4329 case 0: return "SBAS L1C/A";
4330 default: return "SBAS Unknown";
4332 } else if(gnssId == 2) {
4333 switch(sigId) {
4334 case 0: return "Galileo E1 C";
4335 case 1: return "Galileo E1 B";
4336 case 3: return "Galileo E5 al";
4337 case 4: return "Galileo E5 aQ";
4338 case 5: return "Galileo E5 bl";
4339 case 6: return "Galileo E5 bQ";
4340 default: return "Galileo Unknown";
4342 } else if(gnssId == 3) {
4343 switch(sigId) {
4344 case 0: return "BeiDou B1I D1";
4345 case 1: return "BeiDou B1I D2";
4346 case 2: return "BeiDou B2I D1";
4347 case 3: return "BeiDou B2I D2";
4348 case 5: return "BeiDou B1C";
4349 case 7: return "BeiDou B2a";
4350 default: return "BeiDou Unknown";
4352 } else if(gnssId == 5) {
4353 switch(sigId) {
4354 case 0: return "QZSS L1C/A";
4355 case 1: return "QZSS L1S";
4356 case 4: return "QZSS L2 CM";
4357 case 5: return "QZSS L2 CL";
4358 case 8: return "QZSS L5 I";
4359 case 9: return "QZSS L5 Q";
4360 default: return "QZSS Unknown";
4362 } else if(gnssId == 6) {
4363 switch(sigId) {
4364 case 0: return "GLONASS L1 OF";
4365 case 2: return "GLONASS L2 OF";
4366 default: return "GLONASS Unknown";
4370 return "Unknown GNSS/SigId";
4373 static const char *_ubloxGetQuality(uint8_t quality)
4375 switch(quality) {
4376 case UBLOX_SIG_QUALITY_NOSIGNAL: return "No signal";
4377 case UBLOX_SIG_QUALITY_SEARCHING: return "Searching signal...";
4378 case UBLOX_SIG_QUALITY_ACQUIRED: return "Signal acquired";
4379 case UBLOX_SIG_QUALITY_UNUSABLE: return "Signal detected but unusable";
4380 case UBLOX_SIG_QUALITY_CODE_LOCK_TIME_SYNC: return "Code locked and time sync";
4381 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC:
4382 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC2:
4383 case UBLOX_SIG_QUALITY_CODE_CARRIER_LOCK_TIME_SYNC3:
4384 return "Code and carrier locked and time sync";
4385 default: return "Unknown";
4389 static void cliUbloxPrintSatelites(char *arg)
4391 UNUSED(arg);
4392 if(!isGpsUblox() /*|| !(gpsState.flags.sig || gpsState.flags.sat)*/) {
4393 cliPrint("GPS is not UBLOX or does not report satelites.");
4394 return;
4397 cliPrintLine("UBLOX Satelites");
4399 for(int i = 0; i < UBLOX_MAX_SIGNALS; ++i)
4401 const ubx_nav_sig_info *sat = gpsGetUbloxSatelite(i);
4402 if(sat == NULL) {
4403 continue;
4406 cliPrintLinef("satelite[%d]: %d:%d", i+1, sat->gnssId, sat->svId);
4407 cliPrintLinef("sigId: %d (%s)", sat->sigId, _ubloxGetSigId(sat->gnssId, sat->sigId));
4408 cliPrintLinef("signal strength: %i dbHz", sat->cno);
4409 cliPrintLinef("quality: %i (%s)", sat->quality, _ubloxGetQuality(sat->quality));
4410 //cliPrintLinef("Correlation: %i", sat->corrSource);
4411 //cliPrintLinef("Iono model: %i", sat->ionoModel);
4412 cliPrintLinef("signal flags: 0x%02X", sat->sigFlags);
4413 switch(sat->sigFlags & UBLOX_SIG_HEALTH_MASK) {
4414 case UBLOX_SIG_HEALTH_HEALTHY:
4415 cliPrintLine("signal: Healthy");
4416 break;
4417 case UBLOX_SIG_HEALTH_UNHEALTHY:
4418 cliPrintLine("signal: Unhealthy");
4419 break;
4420 case UBLOX_SIG_HEALTH_UNKNOWN:
4421 default:
4422 cliPrintLinef("signal: Unknown (0x%X)", sat->sigFlags & UBLOX_SIG_HEALTH_MASK);
4423 break;
4425 cliPrintLinefeed();
4428 #endif
4430 static void cliHelp(char *cmdline);
4432 // should be sorted a..z for bsearch()
4433 const clicmd_t cmdTable[] = {
4434 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
4435 #if defined(USE_ASSERT)
4436 CLI_COMMAND_DEF("assert", "", NULL, cliAssert),
4437 #endif
4438 CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
4439 #ifdef USE_CLI_BATCH
4440 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
4441 #endif
4442 #if defined(BEEPER) || defined(USE_DSHOT)
4443 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
4444 "\t<+|->[name]", cliBeeper),
4445 #endif
4446 #if defined (USE_SERIALRX_SRXL2)
4447 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
4448 #endif
4449 #if defined(USE_BOOTLOG)
4450 CLI_COMMAND_DEF("bootlog", "show boot events", NULL, cliBootlog),
4451 #endif
4452 #ifdef USE_LED_STRIP
4453 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
4454 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
4455 #endif
4456 CLI_COMMAND_DEF("cli_delay", "CLI Delay", "Delay in ms", cliDelay),
4457 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
4458 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL, cliDfu),
4459 CLI_COMMAND_DEF("diff", "list configuration changes from default",
4460 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDiff),
4461 CLI_COMMAND_DEF("dump", "dump configuration",
4462 "[master|battery_profile|control_profile|mixer_profile|rates|all] {showdefaults}", cliDump),
4463 #ifdef USE_RX_ELERES
4464 CLI_COMMAND_DEF("eleres_bind", NULL, NULL, cliEleresBind),
4465 #endif // USE_RX_ELERES
4466 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
4467 CLI_COMMAND_DEF("feature", "configure features",
4468 "list\r\n"
4469 "\t<+|->[name]", cliFeature),
4470 #ifdef USE_BLACKBOX
4471 CLI_COMMAND_DEF("blackbox", "configure blackbox fields",
4472 "list\r\n"
4473 "\t<+|->[name]", cliBlackbox),
4474 #endif
4475 #ifdef USE_FLASHFS
4476 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
4477 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
4478 #ifdef USE_FLASH_TOOLS
4479 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
4480 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
4481 #endif
4482 #endif
4483 #ifdef USE_FW_AUTOLAND
4484 CLI_COMMAND_DEF("fwapproach", "Fixed Wing Approach Settings", NULL, cliFwAutolandApproach),
4485 #endif
4486 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
4487 #ifdef USE_GPS
4488 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
4489 CLI_COMMAND_DEF("gpssats", "show GPS satellites", NULL, cliUbloxPrintSatelites),
4490 #endif
4491 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
4492 #ifdef USE_LED_STRIP
4493 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
4494 CLI_COMMAND_DEF("ledpinpwm", "start/stop PWM on LED pin, 0..100 duty ratio", "[<value>]\r\n", cliLedPinPWM),
4495 #endif
4496 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
4497 CLI_COMMAND_DEF("memory", "view memory usage", NULL, cliMemory),
4498 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
4499 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
4500 #ifdef USE_USB_MSC
4501 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
4502 #endif
4503 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]\r\n", cliPlaySound),
4504 CLI_COMMAND_DEF("control_profile", "change control profile", "[<index>]", cliControlProfile),
4505 CLI_COMMAND_DEF("mixer_profile", "change mixer profile", "[<index>]", cliMixerProfile),
4506 CLI_COMMAND_DEF("battery_profile", "change battery profile", "[<index>]", cliBatteryProfile),
4507 CLI_COMMAND_DEF("resource", "view currently used resources", NULL, cliResource),
4508 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
4509 #if defined(USE_SAFE_HOME)
4510 CLI_COMMAND_DEF("safehome", "safe home list", NULL, cliSafeHomes),
4511 #endif
4512 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
4513 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
4514 #ifdef USE_SERIAL_PASSTHROUGH
4515 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough),
4516 #endif
4517 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
4518 #ifdef USE_PROGRAMMING_FRAMEWORK
4519 CLI_COMMAND_DEF("logic", "configure logic conditions",
4520 "<rule> <enabled> <activatorId> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
4521 "\treset\r\n", cliLogic),
4523 CLI_COMMAND_DEF("gvar", "configure global variables",
4524 "<gvar> <default> <min> <max>\r\n"
4525 "\treset\r\n", cliGvar),
4527 CLI_COMMAND_DEF("pid", "configurable PID controllers",
4528 "<#> <enabled> <setpoint type> <setpoint value> <measurement type> <measurement value> <P gain> <I gain> <D gain> <FF gain>\r\n"
4529 "\treset\r\n", cliPid),
4531 CLI_COMMAND_DEF("osd_custom_elements", "configurable OSD custom elements",
4532 "<#> <part0 type> <part0 value> <part1 type> <part1 value> <part2 type> <part2 value> <visibility type> <visibility value> <text>\r\n"
4533 , osdCustom),
4534 #endif
4535 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
4536 CLI_COMMAND_DEF("smix", "servo mixer",
4537 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
4538 "\treset\r\n", cliServoMix),
4539 #ifdef USE_SDCARD
4540 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
4541 #endif
4542 CLI_COMMAND_DEF("showdebug", "Show debug fields.", NULL, cliCmdDebug),
4543 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
4544 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
4545 #ifdef USE_TEMPERATURE_SENSOR
4546 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL, cliTempSensor),
4547 #endif
4548 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
4549 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
4550 CLI_COMMAND_DEF("wp", "waypoint list", NULL, cliWaypoints),
4551 #endif
4552 #ifdef USE_OSD
4553 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout),
4554 #endif
4555 CLI_COMMAND_DEF("timer_output_mode", "get or set the outputmode for a given timer.", "[<timer> [<AUTO|MOTORS|SERVOS>]]", cliTimerOutputMode),
4558 static void cliHelp(char *cmdline)
4560 UNUSED(cmdline);
4562 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
4563 cliPrint(cmdTable[i].name);
4564 #ifndef SKIP_CLI_COMMAND_HELP
4565 if (cmdTable[i].description) {
4566 cliPrintf(" - %s", cmdTable[i].description);
4568 if (cmdTable[i].args) {
4569 cliPrintf("\r\n\t%s", cmdTable[i].args);
4571 #endif
4572 cliPrintLinefeed();
4576 void cliProcess(void)
4578 if (!cliWriter) {
4579 return;
4582 // Be a little bit tricky. Flush the last inputs buffer, if any.
4583 bufWriterFlush(cliWriter);
4585 while (serialRxBytesWaiting(cliPort)) {
4586 uint8_t c = serialRead(cliPort);
4587 if (c == '\t' || c == '?') {
4588 // do tab completion
4589 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
4590 uint32_t i = bufferIndex;
4591 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4592 if (bufferIndex && (sl_strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
4593 continue;
4594 if (!pstart)
4595 pstart = cmd;
4596 pend = cmd;
4598 if (pstart) { /* Buffer matches one or more commands */
4599 for (; ; bufferIndex++) {
4600 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
4601 break;
4602 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
4603 /* Unambiguous -- append a space */
4604 cliBuffer[bufferIndex++] = ' ';
4605 cliBuffer[bufferIndex] = '\0';
4606 break;
4608 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
4611 if (!bufferIndex || pstart != pend) {
4612 /* Print list of ambiguous matches */
4613 cliPrint("\r\033[K");
4614 for (cmd = pstart; cmd <= pend; cmd++) {
4615 cliPrint(cmd->name);
4616 cliWrite('\t');
4618 cliPrompt();
4619 i = 0; /* Redraw prompt */
4621 for (; i < bufferIndex; i++)
4622 cliWrite(cliBuffer[i]);
4623 } else if (!bufferIndex && c == 4) { // CTRL-D
4624 cliExit(cliBuffer);
4625 return;
4626 } else if (c == 12) { // NewPage / CTRL-L
4627 // clear screen
4628 cliPrint("\033[2J\033[1;1H");
4629 cliPrompt();
4630 } else if (bufferIndex && (c == '\n' || c == '\r')) {
4631 // enter pressed
4632 cliPrintLinefeed();
4634 // Strip comment starting with # from line
4635 char *p = cliBuffer;
4636 p = strchr(p, '#');
4637 if (NULL != p) {
4638 bufferIndex = (uint32_t)(p - cliBuffer);
4641 // Strip trailing whitespace
4642 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
4643 bufferIndex--;
4646 // Process non-empty lines
4647 if (bufferIndex > 0) {
4648 cliBuffer[bufferIndex] = 0; // null terminate
4650 const clicmd_t *cmd;
4651 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4652 if (!sl_strncasecmp(cliBuffer, cmd->name, strlen(cmd->name)) // command names match
4653 && !sl_isalnum((unsigned)cliBuffer[strlen(cmd->name)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
4654 break;
4656 if (cmd < cmdTable + ARRAYLEN(cmdTable))
4657 cmd->func(cliBuffer + strlen(cmd->name) + 1);
4658 else
4659 cliPrintError("Unknown command, try 'help'");
4660 bufferIndex = 0;
4663 ZERO_FARRAY(cliBuffer);
4665 // 'exit' will reset this flag, so we don't need to print prompt again
4666 if (!cliMode)
4667 return;
4669 cliPrompt();
4670 } else if (c == 127) {
4671 // backspace
4672 if (bufferIndex) {
4673 cliBuffer[--bufferIndex] = 0;
4674 cliPrint("\010 \010");
4676 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
4677 if (!bufferIndex && c == ' ')
4678 continue; // Ignore leading spaces
4679 cliBuffer[bufferIndex++] = c;
4680 cliWrite(c);
4685 void cliEnter(serialPort_t *serialPort)
4687 if (cliMode) {
4688 return;
4691 cliMode = true;
4692 cliPort = serialPort;
4693 setPrintfSerialPort(cliPort);
4694 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
4696 #ifndef CLI_MINIMAL_VERBOSITY
4697 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4698 #else
4699 cliPrintLine("\r\nCLI");
4700 #endif
4701 cliPrompt();
4703 #ifdef USE_CLI_BATCH
4704 resetCommandBatch();
4705 #endif
4707 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI);
4710 void cliInit(const serialConfig_t *serialConfig)
4712 UNUSED(serialConfig);