Merge remote-tracking branch 'origin/release_6.1.0'
[inav.git] / src / main / fc / cli.c
blobb1340b3c1e9fa587da3994471b115514d8c3e014
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"
72 #include "fc/fc_core.h"
73 #include "fc/cli.h"
74 #include "fc/config.h"
75 #include "fc/controlrate_profile.h"
76 #include "fc/rc_adjustments.h"
77 #include "fc/rc_controls.h"
78 #include "fc/rc_modes.h"
79 #include "fc/runtime_config.h"
80 #include "fc/settings.h"
82 #include "flight/failsafe.h"
83 #include "flight/imu.h"
84 #include "flight/mixer.h"
85 #include "flight/pid.h"
86 #include "flight/servos.h"
88 #include "io/asyncfatfs/asyncfatfs.h"
89 #include "io/beeper.h"
90 #include "io/flashfs.h"
91 #include "io/gps.h"
92 #include "io/ledstrip.h"
93 #include "io/osd.h"
94 #include "io/serial.h"
96 #include "fc/fc_msp_box.h"
98 #include "navigation/navigation.h"
99 #include "navigation/navigation_private.h"
101 #include "rx/rx.h"
102 #include "rx/spektrum.h"
103 #include "rx/srxl2.h"
105 #include "scheduler/scheduler.h"
107 #include "sensors/acceleration.h"
108 #include "sensors/barometer.h"
109 #include "sensors/battery.h"
110 #include "sensors/boardalignment.h"
111 #include "sensors/compass.h"
112 #include "sensors/diagnostics.h"
113 #include "sensors/gyro.h"
114 #include "sensors/pitotmeter.h"
115 #include "sensors/rangefinder.h"
116 #include "sensors/opflow.h"
117 #include "sensors/sensors.h"
118 #include "sensors/temperature.h"
119 #ifdef USE_ESC_SENSOR
120 #include "sensors/esc_sensor.h"
121 #endif
123 #include "telemetry/telemetry.h"
124 #include "build/debug.h"
126 extern timeDelta_t cycleTime; // FIXME dependency on mw.c
127 extern uint8_t detectedSensors[SENSOR_INDEX_COUNT];
129 static serialPort_t *cliPort;
131 static bufWriter_t *cliWriter;
132 static uint8_t cliWriteBuffer[sizeof(*cliWriter) + 128];
134 static char cliBuffer[64];
135 static uint32_t bufferIndex = 0;
136 static uint16_t cliDelayMs = 0;
138 #if defined(USE_ASSERT)
139 static void cliAssert(char *cmdline);
140 #endif
142 #ifdef USE_CLI_BATCH
143 static bool commandBatchActive = false;
144 static bool commandBatchError = false;
145 #endif
147 // sync this with features_e
148 static const char * const featureNames[] = {
149 "THR_VBAT_COMP", "VBAT", "TX_PROF_SEL", "BAT_PROF_AUTOSWITCH", "MOTOR_STOP",
150 "", "SOFTSERIAL", "GPS", "RPM_FILTERS",
151 "", "TELEMETRY", "CURRENT_METER", "REVERSIBLE_MOTORS", "",
152 "", "RSSI_ADC", "LED_STRIP", "DASHBOARD", "",
153 "BLACKBOX", "", "TRANSPONDER", "AIRMODE",
154 "SUPEREXPO", "VTX", "", "", "", "PWM_OUTPUT_ENABLE",
155 "OSD", "FW_LAUNCH", "FW_AUTOTRIM", NULL
158 #ifdef USE_BLACKBOX
159 static const char * const blackboxIncludeFlagNames[] = {
160 "NAV_ACC",
161 "NAV_POS",
162 "NAV_PID",
163 "MAG",
164 "ACC",
165 "ATTI",
166 "RC_DATA",
167 "RC_COMMAND",
168 "MOTORS",
169 "GYRO_RAW",
170 "PEAKS_R",
171 "PEAKS_P",
172 "PEAKS_Y",
173 NULL
175 #endif
177 /* Sensor names (used in lookup tables for *_hardware settings and in status command output) */
178 // sync with gyroSensor_e
179 static const char * const gyroNames[] = { "NONE", "AUTO", "MPU6000", "MPU6500", "MPU9250", "BMI160", "ICM20689", "BMI088", "ICM42605", "BMI270","LSM6DXX", "FAKE"};
181 // sync this with sensors_e
182 static const char * const sensorTypeNames[] = {
183 "GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "PITOT", "OPFLOW", "GPS", "GPS+MAG", NULL
186 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER | SENSOR_PITOT | SENSOR_OPFLOW)
188 static const char * const hardwareSensorStatusNames[] = {
189 "NONE", "OK", "UNAVAILABLE", "FAILING"
192 static const char * const *sensorHardwareNames[] = {
193 gyroNames,
194 table_acc_hardware,
195 #ifdef USE_BARO
196 table_baro_hardware,
197 #else
198 NULL,
199 #endif
200 #ifdef USE_MAG
201 table_mag_hardware,
202 #else
203 NULL,
204 #endif
205 #ifdef USE_RANGEFINDER
206 table_rangefinder_hardware,
207 #else
208 NULL,
209 #endif
210 #ifdef USE_PITOT
211 table_pitot_hardware,
212 #else
213 NULL,
214 #endif
215 #ifdef USE_OPFLOW
216 table_opflow_hardware,
217 #else
218 NULL,
219 #endif
222 static void cliPrint(const char *str)
224 while (*str) {
225 bufWriterAppend(cliWriter, *str++);
229 static void cliPrintLinefeed(void)
231 cliPrint("\r\n");
232 if (cliDelayMs) {
233 delay(cliDelayMs);
237 static void cliPrintLine(const char *str)
239 cliPrint(str);
240 cliPrintLinefeed();
243 static void cliPrintError(const char *str)
245 cliPrint("### ERROR: ");
246 cliPrint(str);
247 #ifdef USE_CLI_BATCH
248 if (commandBatchActive) {
249 commandBatchError = true;
251 #endif
254 static void cliPrintErrorLine(const char *str)
256 cliPrint("### ERROR: ");
257 cliPrintLine(str);
258 #ifdef USE_CLI_BATCH
259 if (commandBatchActive) {
260 commandBatchError = true;
262 #endif
265 #ifdef CLI_MINIMAL_VERBOSITY
266 #define cliPrintHashLine(str)
267 #else
268 static void cliPrintHashLine(const char *str)
270 cliPrint("\r\n# ");
271 cliPrintLine(str);
273 #endif
275 static void cliPutp(void *p, char ch)
277 bufWriterAppend(p, ch);
280 typedef enum {
281 DUMP_MASTER = (1 << 0),
282 DUMP_PROFILE = (1 << 1),
283 DUMP_BATTERY_PROFILE = (1 << 2),
284 DUMP_RATES = (1 << 3),
285 DUMP_ALL = (1 << 4),
286 DO_DIFF = (1 << 5),
287 SHOW_DEFAULTS = (1 << 6),
288 HIDE_UNUSED = (1 << 7)
289 } dumpFlags_e;
291 static void cliPrintfva(const char *format, va_list va)
293 tfp_format(cliWriter, cliPutp, format, va);
294 bufWriterFlush(cliWriter);
297 static void cliPrintLinefva(const char *format, va_list va)
299 tfp_format(cliWriter, cliPutp, format, va);
300 bufWriterFlush(cliWriter);
301 cliPrintLinefeed();
304 static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
306 if (!((dumpMask & DO_DIFF) && equalsDefault)) {
307 va_list va;
308 va_start(va, format);
309 cliPrintLinefva(format, va);
310 va_end(va);
311 return true;
312 } else {
313 return false;
317 static void cliWrite(uint8_t ch)
319 bufWriterAppend(cliWriter, ch);
322 static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
324 if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
325 cliWrite('#');
327 va_list va;
328 va_start(va, format);
329 cliPrintLinefva(format, va);
330 va_end(va);
331 return true;
332 } else {
333 return false;
337 static void cliPrintf(const char *format, ...)
339 va_list va;
340 va_start(va, format);
341 cliPrintfva(format, va);
342 va_end(va);
346 static void cliPrintLinef(const char *format, ...)
348 va_list va;
349 va_start(va, format);
350 cliPrintLinefva(format, va);
351 va_end(va);
354 static void cliPrintErrorVa(const char *format, va_list va)
356 cliPrint("### ERROR: ");
357 cliPrintfva(format, va);
358 va_end(va);
360 #ifdef USE_CLI_BATCH
361 if (commandBatchActive) {
362 commandBatchError = true;
364 #endif
367 static void cliPrintErrorLinef(const char *format, ...)
369 va_list va;
370 va_start(va, format);
371 cliPrintErrorVa(format, va);
372 cliPrintLinefeed();
375 static void printValuePointer(const setting_t *var, const void *valuePointer, uint32_t full)
377 int32_t value = 0;
378 char buf[SETTING_MAX_NAME_LENGTH];
380 switch (SETTING_TYPE(var)) {
381 case VAR_UINT8:
382 value = *(uint8_t *)valuePointer;
383 break;
385 case VAR_INT8:
386 value = *(int8_t *)valuePointer;
387 break;
389 case VAR_UINT16:
390 value = *(uint16_t *)valuePointer;
391 break;
393 case VAR_INT16:
394 value = *(int16_t *)valuePointer;
395 break;
397 case VAR_UINT32:
398 value = *(uint32_t *)valuePointer;
399 break;
401 case VAR_FLOAT:
402 cliPrintf("%s", ftoa(*(float *)valuePointer, buf));
403 if (full) {
404 if (SETTING_MODE(var) == MODE_DIRECT) {
405 cliPrintf(" %s", ftoa((float)settingGetMin(var), buf));
406 cliPrintf(" %s", ftoa((float)settingGetMax(var), buf));
409 return; // return from case for float only
411 case VAR_STRING:
412 cliPrintf("%s", (const char *)valuePointer);
413 return;
416 switch (SETTING_MODE(var)) {
417 case MODE_DIRECT:
418 if (SETTING_TYPE(var) == VAR_UINT32)
419 cliPrintf("%u", value);
420 else
421 cliPrintf("%d", value);
422 if (full) {
423 if (SETTING_MODE(var) == MODE_DIRECT) {
424 cliPrintf(" %d %u", settingGetMin(var), settingGetMax(var));
427 break;
428 case MODE_LOOKUP:
430 const char *name = settingLookupValueName(var, value);
431 if (name) {
432 cliPrintf(name);
433 } else {
434 settingGetName(var, buf);
435 cliPrintErrorLinef("VALUE %d OUT OF RANGE FOR %s", (int)value, buf);
437 break;
442 static bool valuePtrEqualsDefault(const setting_t *value, const void *ptr, const void *ptrDefault)
444 bool result = false;
445 switch (SETTING_TYPE(value)) {
446 case VAR_UINT8:
447 result = *(uint8_t *)ptr == *(uint8_t *)ptrDefault;
448 break;
450 case VAR_INT8:
451 result = *(int8_t *)ptr == *(int8_t *)ptrDefault;
452 break;
454 case VAR_UINT16:
455 result = *(uint16_t *)ptr == *(uint16_t *)ptrDefault;
456 break;
458 case VAR_INT16:
459 result = *(int16_t *)ptr == *(int16_t *)ptrDefault;
460 break;
462 case VAR_UINT32:
463 result = *(uint32_t *)ptr == *(uint32_t *)ptrDefault;
464 break;
466 case VAR_FLOAT:
467 result = *(float *)ptr == *(float *)ptrDefault;
468 break;
470 case VAR_STRING:
471 result = strncmp(ptr, ptrDefault, settingGetStringMaxLength(value) + 1) == 0;
472 break;
474 return result;
477 static void dumpPgValue(const setting_t *value, uint8_t dumpMask)
479 char name[SETTING_MAX_NAME_LENGTH];
480 const char *format = "set %s = ";
481 const char *defaultFormat = "#set %s = ";
482 // During a dump, the PGs have been backed up to their "copy"
483 // regions and the actual values have been reset to its
484 // defaults. This means that settingGetValuePointer() will
485 // return the default value while settingGetCopyValuePointer()
486 // will return the actual value.
487 const void *valuePointer = settingGetCopyValuePointer(value);
488 const void *defaultValuePointer = settingGetValuePointer(value);
489 const bool equalsDefault = valuePtrEqualsDefault(value, valuePointer, defaultValuePointer);
490 if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
491 settingGetName(value, name);
492 if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
493 cliPrintf(defaultFormat, name);
494 // if the craftname has a leading space, then enclose the name in quotes
495 if (strcmp(name, "name") == 0 && ((const char *)valuePointer)[0] == ' ') {
496 cliPrintf("\"%s\"", (const char *)valuePointer);
497 } else {
498 printValuePointer(value, valuePointer, 0);
500 cliPrintLinefeed();
502 cliPrintf(format, name);
503 printValuePointer(value, valuePointer, 0);
504 cliPrintLinefeed();
508 static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
510 for (unsigned i = 0; i < SETTINGS_TABLE_COUNT; i++) {
511 const setting_t *value = settingGet(i);
512 bufWriterFlush(cliWriter);
513 if (SETTING_SECTION(value) == valueSection) {
514 dumpPgValue(value, dumpMask);
519 static void cliPrintVar(const setting_t *var, uint32_t full)
521 const void *ptr = settingGetValuePointer(var);
523 printValuePointer(var, ptr, full);
526 static void cliPrintVarRange(const setting_t *var)
528 switch (SETTING_MODE(var)) {
529 case MODE_DIRECT:
530 if (SETTING_TYPE(var) == VAR_STRING) {
531 cliPrintLinef("Max. length: %u", settingGetStringMaxLength(var));
532 break;
534 cliPrintLinef("Allowed range: %d - %u", settingGetMin(var), settingGetMax(var));
535 break;
536 case MODE_LOOKUP:
538 const lookupTableEntry_t *tableEntry = settingLookupTable(var);
539 cliPrint("Allowed values:");
540 for (uint32_t i = 0; i < tableEntry->valueCount ; i++) {
541 if (i > 0)
542 cliPrint(",");
543 cliPrintf(" %s", tableEntry->values[i]);
545 cliPrintLinefeed();
547 break;
551 typedef union {
552 uint32_t uint_value;
553 int32_t int_value;
554 float float_value;
555 } int_float_value_t;
557 static void cliSetIntFloatVar(const setting_t *var, const int_float_value_t value)
559 void *ptr = settingGetValuePointer(var);
561 switch (SETTING_TYPE(var)) {
562 case VAR_UINT8:
563 case VAR_INT8:
564 *(int8_t *)ptr = value.int_value;
565 break;
567 case VAR_UINT16:
568 case VAR_INT16:
569 *(int16_t *)ptr = value.int_value;
570 break;
572 case VAR_UINT32:
573 *(uint32_t *)ptr = value.uint_value;
574 break;
576 case VAR_FLOAT:
577 *(float *)ptr = (float)value.float_value;
578 break;
580 case VAR_STRING:
581 // Handled by cliSet directly
582 break;
586 static void cliPrompt(void)
588 cliPrint("\r\n# ");
589 bufWriterFlush(cliWriter);
592 static void cliShowParseError(void)
594 cliPrintErrorLinef("Parse error");
597 static void cliShowArgumentRangeError(char *name, int min, int max)
599 cliPrintErrorLinef("%s must be between %d and %d", name, min, max);
602 static const char *nextArg(const char *currentArg)
604 const char *ptr = strchr(currentArg, ' ');
605 while (ptr && *ptr == ' ') {
606 ptr++;
609 return ptr;
612 static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
614 for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
615 ptr = nextArg(ptr);
616 if (ptr) {
617 int val = fastA2I(ptr);
618 val = CHANNEL_VALUE_TO_STEP(val);
619 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
620 if (argIndex == 0) {
621 range->startStep = val;
622 } else {
623 range->endStep = val;
625 (*validArgumentCount)++;
630 return ptr;
633 // Check if a string's length is zero
634 static bool isEmpty(const char *string)
636 return (string == NULL || *string == '\0') ? true : false;
639 #if defined(USE_ASSERT)
640 static void cliAssert(char *cmdline)
642 UNUSED(cmdline);
644 if (assertFailureLine) {
645 if (assertFailureFile) {
646 cliPrintErrorLinef("Assertion failed at line %d, file %s", assertFailureLine, assertFailureFile);
648 else {
649 cliPrintErrorLinef("Assertion failed at line %d", assertFailureLine);
651 #ifdef USE_CLI_BATCH
652 if (commandBatchActive) {
653 commandBatchError = true;
655 #endif
657 else {
658 cliPrintLine("No assert() failed");
661 #endif
663 static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
665 const char *format = "aux %u %u %u %u %u";
666 // print out aux channel settings
667 for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
668 const modeActivationCondition_t *mac = &modeActivationConditions[i];
669 bool equalsDefault = false;
670 if (defaultModeActivationConditions) {
671 const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
672 equalsDefault = mac->modeId == macDefault->modeId
673 && mac->auxChannelIndex == macDefault->auxChannelIndex
674 && mac->range.startStep == macDefault->range.startStep
675 && mac->range.endStep == macDefault->range.endStep;
676 const box_t *box = findBoxByActiveBoxId(macDefault->modeId);
677 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
679 box->permanentId,
680 macDefault->auxChannelIndex,
681 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
682 MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep)
685 const box_t *box = findBoxByActiveBoxId(mac->modeId);
686 cliDumpPrintLinef(dumpMask, equalsDefault, format,
688 box->permanentId,
689 mac->auxChannelIndex,
690 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
691 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep)
696 static void cliAux(char *cmdline)
698 int i, val = 0;
699 const char *ptr;
701 if (isEmpty(cmdline)) {
702 printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
703 } else {
704 ptr = cmdline;
705 i = fastA2I(ptr++);
706 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
707 modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
708 uint8_t validArgumentCount = 0;
709 ptr = nextArg(ptr);
710 if (ptr) {
711 val = fastA2I(ptr);
712 if (val >= 0) {
713 const box_t *box = findBoxByPermanentId(val);
714 if (box != NULL) {
715 mac->modeId = box->boxId;
716 validArgumentCount++;
720 ptr = nextArg(ptr);
721 if (ptr) {
722 val = fastA2I(ptr);
723 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
724 mac->auxChannelIndex = val;
725 validArgumentCount++;
728 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
730 if (validArgumentCount != 4) {
731 memset(mac, 0, sizeof(modeActivationCondition_t));
733 } else {
734 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
739 static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
741 const char *format = "serial %d %d %ld %ld %ld %ld";
742 for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
743 if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
744 continue;
746 bool equalsDefault = false;
747 if (serialConfigDefault) {
748 equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
749 && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
750 && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
751 && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
752 && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
753 && serialConfig->portConfigs[i].peripheral_baudrateIndex == serialConfigDefault->portConfigs[i].peripheral_baudrateIndex;
754 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
755 serialConfigDefault->portConfigs[i].identifier,
756 serialConfigDefault->portConfigs[i].functionMask,
757 baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
758 baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
759 baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
760 baudRates[serialConfigDefault->portConfigs[i].peripheral_baudrateIndex]
763 cliDumpPrintLinef(dumpMask, equalsDefault, format,
764 serialConfig->portConfigs[i].identifier,
765 serialConfig->portConfigs[i].functionMask,
766 baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
767 baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
768 baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
769 baudRates[serialConfig->portConfigs[i].peripheral_baudrateIndex]
774 static void cliSerial(char *cmdline)
776 if (isEmpty(cmdline)) {
777 printSerial(DUMP_MASTER, serialConfig(), NULL);
778 return;
780 serialPortConfig_t portConfig;
782 serialPortConfig_t *currentConfig;
784 uint8_t validArgumentCount = 0;
786 const char *ptr = cmdline;
788 int val = fastA2I(ptr++);
789 currentConfig = serialFindPortConfiguration(val);
790 if (!currentConfig) {
791 // Invalid port ID
792 cliPrintErrorLinef("Invalid port ID %d", val);
793 return;
795 memcpy(&portConfig, currentConfig, sizeof(portConfig));
796 validArgumentCount++;
798 ptr = nextArg(ptr);
799 if (ptr) {
800 switch (*ptr) {
801 case '+':
802 // Add function
803 ptr++;
804 val = fastA2I(ptr);
805 portConfig.functionMask |= (1 << val);
806 break;
807 case '-':
808 // Remove function
809 ptr++;
810 val = fastA2I(ptr);
811 portConfig.functionMask &= 0xFFFFFFFF ^ (1 << val);
812 break;
813 default:
814 // Set functions
815 val = fastA2I(ptr);
816 portConfig.functionMask = val & 0xFFFFFFFF;
817 break;
819 validArgumentCount++;
822 for (int i = 0; i < 4; i ++) {
823 ptr = nextArg(ptr);
824 if (!ptr) {
825 break;
828 val = fastA2I(ptr);
830 uint8_t baudRateIndex = lookupBaudRateIndex(val);
831 if (baudRates[baudRateIndex] != (uint32_t) val) {
832 break;
835 switch (i) {
836 case 0:
837 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
838 portConfig.msp_baudrateIndex = baudRateIndex;
839 break;
840 case 1:
841 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
842 portConfig.gps_baudrateIndex = baudRateIndex;
843 break;
844 case 2:
845 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
846 portConfig.telemetry_baudrateIndex = baudRateIndex;
847 break;
848 case 3:
849 baudRateIndex = constrain(baudRateIndex, BAUD_MIN, BAUD_MAX);
850 portConfig.peripheral_baudrateIndex = baudRateIndex;
851 break;
854 validArgumentCount++;
857 if (validArgumentCount < 2) {
858 cliShowParseError();
859 return;
862 memcpy(currentConfig, &portConfig, sizeof(portConfig));
865 #ifdef USE_SERIAL_PASSTHROUGH
866 static void cliSerialPassthrough(char *cmdline)
868 char * saveptr;
870 if (isEmpty(cmdline)) {
871 cliShowParseError();
872 return;
875 int id = -1;
876 uint32_t baud = 0;
877 unsigned mode = 0;
878 char* tok = strtok_r(cmdline, " ", &saveptr);
879 int index = 0;
881 while (tok != NULL) {
882 switch (index) {
883 case 0:
884 id = fastA2I(tok);
885 break;
886 case 1:
887 baud = fastA2I(tok);
888 break;
889 case 2:
890 if (strstr(tok, "rx") || strstr(tok, "RX"))
891 mode |= MODE_RX;
892 if (strstr(tok, "tx") || strstr(tok, "TX"))
893 mode |= MODE_TX;
894 break;
896 index++;
897 tok = strtok_r(NULL, " ", &saveptr);
900 serialPort_t *passThroughPort;
901 serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
902 if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
903 if (!baud) {
904 tfp_printf("Port %d is closed, must specify baud.\r\n", id);
905 return;
907 if (!mode)
908 mode = MODE_RXTX;
910 passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL, NULL,
911 baud, mode,
912 SERIAL_NOT_INVERTED);
913 if (!passThroughPort) {
914 tfp_printf("Port %d could not be opened.\r\n", id);
915 return;
917 tfp_printf("Port %d opened, baud = %u.\r\n", id, (unsigned)baud);
918 } else {
919 passThroughPort = passThroughPortUsage->serialPort;
920 // If the user supplied a mode, override the port's mode, otherwise
921 // leave the mode unchanged. serialPassthrough() handles one-way ports.
922 tfp_printf("Port %d already open.\r\n", id);
923 if (mode && passThroughPort->mode != mode) {
924 tfp_printf("Adjusting mode from %d to %d.\r\n",
925 passThroughPort->mode, mode);
926 serialSetMode(passThroughPort, mode);
928 // If this port has a rx callback associated we need to remove it now.
929 // Otherwise no data will be pushed in the serial port buffer!
930 if (passThroughPort->rxCallback) {
931 tfp_printf("Removing rxCallback\r\n");
932 passThroughPort->rxCallback = 0;
936 tfp_printf("Forwarding data to %d, power cycle to exit.\r\n", id);
938 serialPassthrough(cliPort, passThroughPort, NULL, NULL);
940 #endif
942 static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
944 const char *format = "adjrange %u %u %u %u %u %u %u";
945 // print out adjustment ranges channel settings
946 for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
947 const adjustmentRange_t *ar = &adjustmentRanges[i];
948 bool equalsDefault = false;
949 if (defaultAdjustmentRanges) {
950 const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
951 equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
952 && ar->range.startStep == arDefault->range.startStep
953 && ar->range.endStep == arDefault->range.endStep
954 && ar->adjustmentFunction == arDefault->adjustmentFunction
955 && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
956 && ar->adjustmentIndex == arDefault->adjustmentIndex;
957 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
959 arDefault->adjustmentIndex,
960 arDefault->auxChannelIndex,
961 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
962 MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
963 arDefault->adjustmentFunction,
964 arDefault->auxSwitchChannelIndex
967 cliDumpPrintLinef(dumpMask, equalsDefault, format,
969 ar->adjustmentIndex,
970 ar->auxChannelIndex,
971 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
972 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
973 ar->adjustmentFunction,
974 ar->auxSwitchChannelIndex
979 static void cliAdjustmentRange(char *cmdline)
981 int i, val = 0;
982 const char *ptr;
984 if (isEmpty(cmdline)) {
985 printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
986 } else {
987 ptr = cmdline;
988 i = fastA2I(ptr++);
989 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
990 adjustmentRange_t *ar = adjustmentRangesMutable(i);
991 uint8_t validArgumentCount = 0;
993 ptr = nextArg(ptr);
994 if (ptr) {
995 val = fastA2I(ptr);
996 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
997 ar->adjustmentIndex = val;
998 validArgumentCount++;
1001 ptr = nextArg(ptr);
1002 if (ptr) {
1003 val = fastA2I(ptr);
1004 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1005 ar->auxChannelIndex = val;
1006 validArgumentCount++;
1010 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1012 ptr = nextArg(ptr);
1013 if (ptr) {
1014 val = fastA2I(ptr);
1015 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1016 ar->adjustmentFunction = val;
1017 validArgumentCount++;
1020 ptr = nextArg(ptr);
1021 if (ptr) {
1022 val = fastA2I(ptr);
1023 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1024 ar->auxSwitchChannelIndex = val;
1025 validArgumentCount++;
1029 if (validArgumentCount != 6) {
1030 memset(ar, 0, sizeof(adjustmentRange_t));
1031 cliShowParseError();
1033 } else {
1034 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1039 static void printMotorMix(uint8_t dumpMask, const motorMixer_t *primaryMotorMixer, const motorMixer_t *defaultprimaryMotorMixer)
1041 const char *format = "mmix %d %s %s %s %s";
1042 char buf0[FTOA_BUFFER_SIZE];
1043 char buf1[FTOA_BUFFER_SIZE];
1044 char buf2[FTOA_BUFFER_SIZE];
1045 char buf3[FTOA_BUFFER_SIZE];
1046 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1047 if (primaryMotorMixer[i].throttle == 0.0f)
1048 break;
1049 const float thr = primaryMotorMixer[i].throttle;
1050 const float roll = primaryMotorMixer[i].roll;
1051 const float pitch = primaryMotorMixer[i].pitch;
1052 const float yaw = primaryMotorMixer[i].yaw;
1053 bool equalsDefault = false;
1054 if (defaultprimaryMotorMixer) {
1055 const float thrDefault = defaultprimaryMotorMixer[i].throttle;
1056 const float rollDefault = defaultprimaryMotorMixer[i].roll;
1057 const float pitchDefault = defaultprimaryMotorMixer[i].pitch;
1058 const float yawDefault = defaultprimaryMotorMixer[i].yaw;
1059 const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
1061 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1063 ftoa(thrDefault, buf0),
1064 ftoa(rollDefault, buf1),
1065 ftoa(pitchDefault, buf2),
1066 ftoa(yawDefault, buf3));
1068 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1070 ftoa(thr, buf0),
1071 ftoa(roll, buf1),
1072 ftoa(pitch, buf2),
1073 ftoa(yaw, buf3));
1077 static void cliMotorMix(char *cmdline)
1079 int check = 0;
1080 const char *ptr;
1082 if (isEmpty(cmdline)) {
1083 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1084 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1085 // erase custom mixer
1086 for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1087 primaryMotorMixerMutable(i)->throttle = 0.0f;
1089 } else {
1090 ptr = cmdline;
1091 uint32_t i = fastA2I(ptr); // get motor number
1092 if (i < MAX_SUPPORTED_MOTORS) {
1093 ptr = nextArg(ptr);
1094 if (ptr) {
1095 primaryMotorMixerMutable(i)->throttle = fastA2F(ptr);
1096 check++;
1098 ptr = nextArg(ptr);
1099 if (ptr) {
1100 primaryMotorMixerMutable(i)->roll = fastA2F(ptr);
1101 check++;
1103 ptr = nextArg(ptr);
1104 if (ptr) {
1105 primaryMotorMixerMutable(i)->pitch = fastA2F(ptr);
1106 check++;
1108 ptr = nextArg(ptr);
1109 if (ptr) {
1110 primaryMotorMixerMutable(i)->yaw = fastA2F(ptr);
1111 check++;
1113 if (check != 4) {
1114 cliShowParseError();
1115 } else {
1116 printMotorMix(DUMP_MASTER, primaryMotorMixer(0), NULL);
1118 } else {
1119 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
1124 static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
1126 const char *format = "rxrange %u %u %u";
1127 for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1128 bool equalsDefault = false;
1129 if (defaultChannelRangeConfigs) {
1130 equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
1131 && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
1132 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1134 defaultChannelRangeConfigs[i].min,
1135 defaultChannelRangeConfigs[i].max
1138 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1140 channelRangeConfigs[i].min,
1141 channelRangeConfigs[i].max
1146 static void cliRxRange(char *cmdline)
1148 int i, validArgumentCount = 0;
1149 const char *ptr;
1151 if (isEmpty(cmdline)) {
1152 printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
1153 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1154 resetAllRxChannelRangeConfigurations();
1155 } else {
1156 ptr = cmdline;
1157 i = fastA2I(ptr);
1158 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1159 int rangeMin, rangeMax;
1161 ptr = nextArg(ptr);
1162 if (ptr) {
1163 rangeMin = fastA2I(ptr);
1164 validArgumentCount++;
1167 ptr = nextArg(ptr);
1168 if (ptr) {
1169 rangeMax = fastA2I(ptr);
1170 validArgumentCount++;
1173 if (validArgumentCount != 2) {
1174 cliShowParseError();
1175 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1176 cliShowParseError();
1177 } else {
1178 rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
1179 channelRangeConfig->min = rangeMin;
1180 channelRangeConfig->max = rangeMax;
1182 } else {
1183 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1188 #ifdef USE_TEMPERATURE_SENSOR
1189 static void printTempSensor(uint8_t dumpMask, const tempSensorConfig_t *tempSensorConfigs, const tempSensorConfig_t *defaultTempSensorConfigs)
1191 const char *format = "temp_sensor %u %u %s %d %d %u %s";
1192 for (uint8_t i = 0; i < MAX_TEMP_SENSORS; i++) {
1193 bool equalsDefault = false;
1194 char label[5], hex_address[17];
1195 strncpy(label, tempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN);
1196 label[4] = '\0';
1197 tempSensorAddressToString(tempSensorConfigs[i].address, hex_address);
1198 if (defaultTempSensorConfigs) {
1199 equalsDefault = tempSensorConfigs[i].type == defaultTempSensorConfigs[i].type
1200 && tempSensorConfigs[i].address == defaultTempSensorConfigs[i].address
1201 && tempSensorConfigs[i].osdSymbol == defaultTempSensorConfigs[i].osdSymbol
1202 && !memcmp(tempSensorConfigs[i].label, defaultTempSensorConfigs[i].label, TEMPERATURE_LABEL_LEN)
1203 && tempSensorConfigs[i].alarm_min == defaultTempSensorConfigs[i].alarm_min
1204 && tempSensorConfigs[i].alarm_max == defaultTempSensorConfigs[i].alarm_max;
1205 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1207 defaultTempSensorConfigs[i].type,
1208 "0",
1209 defaultTempSensorConfigs[i].alarm_min,
1210 defaultTempSensorConfigs[i].alarm_max,
1215 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1217 tempSensorConfigs[i].type,
1218 hex_address,
1219 tempSensorConfigs[i].alarm_min,
1220 tempSensorConfigs[i].alarm_max,
1221 tempSensorConfigs[i].osdSymbol,
1222 label
1227 static void cliTempSensor(char *cmdline)
1229 if (isEmpty(cmdline)) {
1230 printTempSensor(DUMP_MASTER, tempSensorConfig(0), NULL);
1231 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1232 resetTempSensorConfig();
1233 } else {
1234 int16_t i;
1235 const char *ptr = cmdline, *label;
1236 int16_t type=0, alarm_min=0, alarm_max=0;
1237 bool addressValid = false;
1238 uint64_t address;
1239 int8_t osdSymbol=0;
1240 uint8_t validArgumentCount = 0;
1241 i = fastA2I(ptr);
1242 if (i >= 0 && i < MAX_TEMP_SENSORS) {
1244 ptr = nextArg(ptr);
1245 if (ptr) {
1246 type = fastA2I(ptr);
1247 validArgumentCount++;
1250 ptr = nextArg(ptr);
1251 if (ptr) {
1252 addressValid = tempSensorStringToAddress(ptr, &address);
1253 validArgumentCount++;
1256 ptr = nextArg(ptr);
1257 if (ptr) {
1258 alarm_min = fastA2I(ptr);
1259 validArgumentCount++;
1262 ptr = nextArg(ptr);
1263 if (ptr) {
1264 alarm_max = fastA2I(ptr);
1265 validArgumentCount++;
1268 ptr = nextArg(ptr);
1269 if (ptr) {
1270 osdSymbol = fastA2I(ptr);
1271 validArgumentCount++;
1274 label = nextArg(ptr);
1275 if (label)
1276 ++validArgumentCount;
1277 else
1278 label = "";
1280 if (validArgumentCount < 4) {
1281 cliShowParseError();
1282 } 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) {
1283 cliShowParseError();
1284 } else {
1285 tempSensorConfig_t *sensorConfig = tempSensorConfigMutable(i);
1286 sensorConfig->type = type;
1287 sensorConfig->address = address;
1288 sensorConfig->alarm_min = alarm_min;
1289 sensorConfig->alarm_max = alarm_max;
1290 sensorConfig->osdSymbol = osdSymbol;
1291 for (uint8_t index = 0; index < TEMPERATURE_LABEL_LEN; ++index) {
1292 sensorConfig->label[index] = toupper(label[index]);
1293 if (label[index] == '\0') break;
1296 } else {
1297 cliShowArgumentRangeError("sensor index", 0, MAX_TEMP_SENSORS - 1);
1301 #endif
1303 #if defined(USE_SAFE_HOME)
1304 static void printSafeHomes(uint8_t dumpMask, const navSafeHome_t *navSafeHome, const navSafeHome_t *defaultSafeHome)
1306 const char *format = "safehome %u %u %d %d"; // uint8_t enabled, int32_t lat; int32_t lon
1307 for (uint8_t i = 0; i < MAX_SAFE_HOMES; i++) {
1308 bool equalsDefault = false;
1309 if (defaultSafeHome) {
1310 equalsDefault = navSafeHome[i].enabled == defaultSafeHome[i].enabled
1311 && navSafeHome[i].lat == defaultSafeHome[i].lat
1312 && navSafeHome[i].lon == defaultSafeHome[i].lon;
1313 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,
1314 defaultSafeHome[i].enabled, defaultSafeHome[i].lat, defaultSafeHome[i].lon);
1316 cliDumpPrintLinef(dumpMask, equalsDefault, format, i,
1317 navSafeHome[i].enabled, navSafeHome[i].lat, navSafeHome[i].lon);
1321 static void cliSafeHomes(char *cmdline)
1323 if (isEmpty(cmdline)) {
1324 printSafeHomes(DUMP_MASTER, safeHomeConfig(0), NULL);
1325 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1326 resetSafeHomes();
1327 } else {
1328 int32_t lat=0, lon=0;
1329 bool enabled=false;
1330 uint8_t validArgumentCount = 0;
1331 const char *ptr = cmdline;
1332 int8_t i = fastA2I(ptr);
1333 if (i < 0 || i >= MAX_SAFE_HOMES) {
1334 cliShowArgumentRangeError("safehome index", 0, MAX_SAFE_HOMES - 1);
1335 } else {
1336 if ((ptr = nextArg(ptr))) {
1337 enabled = fastA2I(ptr);
1338 validArgumentCount++;
1340 if ((ptr = nextArg(ptr))) {
1341 lat = fastA2I(ptr);
1342 validArgumentCount++;
1344 if ((ptr = nextArg(ptr))) {
1345 lon = fastA2I(ptr);
1346 validArgumentCount++;
1348 if ((ptr = nextArg(ptr))) {
1349 // check for too many arguments
1350 validArgumentCount++;
1352 if (validArgumentCount != 3) {
1353 cliShowParseError();
1354 } else {
1355 safeHomeConfigMutable(i)->enabled = enabled;
1356 safeHomeConfigMutable(i)->lat = lat;
1357 safeHomeConfigMutable(i)->lon = lon;
1363 #endif
1364 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
1365 static void printWaypoints(uint8_t dumpMask, const navWaypoint_t *navWaypoint, const navWaypoint_t *defaultNavWaypoint)
1367 cliPrintLinef("#wp %d %svalid", posControl.waypointCount, posControl.waypointListValid ? "" : "in"); //int8_t bool
1368 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
1369 for (uint8_t i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1370 bool equalsDefault = false;
1371 if (defaultNavWaypoint) {
1372 equalsDefault = navWaypoint[i].action == defaultNavWaypoint[i].action
1373 && navWaypoint[i].lat == defaultNavWaypoint[i].lat
1374 && navWaypoint[i].lon == defaultNavWaypoint[i].lon
1375 && navWaypoint[i].alt == defaultNavWaypoint[i].alt
1376 && navWaypoint[i].p1 == defaultNavWaypoint[i].p1
1377 && navWaypoint[i].p2 == defaultNavWaypoint[i].p2
1378 && navWaypoint[i].p3 == defaultNavWaypoint[i].p3
1379 && navWaypoint[i].flag == defaultNavWaypoint[i].flag;
1380 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1382 defaultNavWaypoint[i].action,
1383 defaultNavWaypoint[i].lat,
1384 defaultNavWaypoint[i].lon,
1385 defaultNavWaypoint[i].alt,
1386 defaultNavWaypoint[i].p1,
1387 defaultNavWaypoint[i].p2,
1388 defaultNavWaypoint[i].p3,
1389 defaultNavWaypoint[i].flag
1392 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1394 navWaypoint[i].action,
1395 navWaypoint[i].lat,
1396 navWaypoint[i].lon,
1397 navWaypoint[i].alt,
1398 navWaypoint[i].p1,
1399 navWaypoint[i].p2,
1400 navWaypoint[i].p3,
1401 navWaypoint[i].flag
1406 static void cliWaypoints(char *cmdline)
1408 #ifdef USE_MULTI_MISSION
1409 static int8_t multiMissionWPCounter = 0;
1410 #endif
1411 if (isEmpty(cmdline)) {
1412 printWaypoints(DUMP_MASTER, posControl.waypointList, NULL);
1413 } else if (sl_strcasecmp(cmdline, "reset") == 0) {
1414 resetWaypointList();
1415 } else if (sl_strcasecmp(cmdline, "load") == 0) {
1416 loadNonVolatileWaypointList(true);
1417 } else if (sl_strcasecmp(cmdline, "save") == 0) {
1418 posControl.waypointListValid = false;
1419 for (int i = 0; i < NAV_MAX_WAYPOINTS; i++) {
1420 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;
1421 if (posControl.waypointList[i].flag == NAV_WP_FLAG_LAST) {
1422 #ifdef USE_MULTI_MISSION
1423 if (posControl.multiMissionCount == 1) {
1424 posControl.waypointCount = i + 1;
1425 posControl.waypointListValid = true;
1426 multiMissionWPCounter = 0;
1427 posControl.multiMissionCount = 0;
1428 break;
1429 } else {
1430 posControl.multiMissionCount -= 1;
1432 #else
1433 posControl.waypointCount = i + 1;
1434 posControl.waypointListValid = true;
1435 break;
1436 #endif
1439 if (posControl.waypointListValid) {
1440 saveNonVolatileWaypointList();
1441 } else {
1442 cliShowParseError();
1444 } else {
1445 int16_t i, p1=0,p2=0,p3=0,tmp=0;
1446 uint8_t action=0, flag=0;
1447 int32_t lat=0, lon=0, alt=0;
1448 uint8_t validArgumentCount = 0;
1449 const char *ptr = cmdline;
1450 i = fastA2I(ptr);
1451 #ifdef USE_MULTI_MISSION
1452 if (i + multiMissionWPCounter >= 0 && i + multiMissionWPCounter < NAV_MAX_WAYPOINTS) {
1453 #else
1454 if (i >= 0 && i < NAV_MAX_WAYPOINTS) {
1455 #endif
1456 ptr = nextArg(ptr);
1457 if (ptr) {
1458 action = fastA2I(ptr);
1459 validArgumentCount++;
1461 ptr = nextArg(ptr);
1462 if (ptr) {
1463 lat = fastA2I(ptr);
1464 validArgumentCount++;
1466 ptr = nextArg(ptr);
1467 if (ptr) {
1468 lon = fastA2I(ptr);
1469 validArgumentCount++;
1471 ptr = nextArg(ptr);
1472 if (ptr) {
1473 alt = fastA2I(ptr);
1474 validArgumentCount++;
1476 ptr = nextArg(ptr);
1477 if (ptr) {
1478 p1 = fastA2I(ptr);
1479 validArgumentCount++;
1481 ptr = nextArg(ptr);
1482 if (ptr) {
1483 tmp = fastA2I(ptr);
1484 validArgumentCount++;
1486 /* We support pre-2.5 6 values (... p1,flags) or
1487 * 2.5 and later, 8 values (... p1,p2,p3,flags)
1489 ptr = nextArg(ptr);
1490 if (ptr) {
1491 p2 = tmp;
1492 p3 = fastA2I(ptr);
1493 validArgumentCount++;
1494 ptr = nextArg(ptr);
1495 if (ptr) {
1496 flag = fastA2I(ptr);
1497 validArgumentCount++;
1499 } else {
1500 flag = tmp;
1503 if (!(validArgumentCount == 6 || validArgumentCount == 8)) {
1504 cliShowParseError();
1505 } 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)) {
1506 cliShowParseError();
1507 } else {
1508 #ifdef USE_MULTI_MISSION
1509 if (i + multiMissionWPCounter == 0) {
1510 posControl.multiMissionCount = 0;
1513 posControl.waypointList[i + multiMissionWPCounter].action = action;
1514 posControl.waypointList[i + multiMissionWPCounter].lat = lat;
1515 posControl.waypointList[i + multiMissionWPCounter].lon = lon;
1516 posControl.waypointList[i + multiMissionWPCounter].alt = alt;
1517 posControl.waypointList[i + multiMissionWPCounter].p1 = p1;
1518 posControl.waypointList[i + multiMissionWPCounter].p2 = p2;
1519 posControl.waypointList[i + multiMissionWPCounter].p3 = p3;
1520 posControl.waypointList[i + multiMissionWPCounter].flag = flag;
1522 // Process WP entries made up of multiple successive WP missions (multiple NAV_WP_FLAG_LAST entries)
1523 // Individial missions loaded at runtime, mission selected nav_waypoint_multi_mission_index
1524 if (flag == NAV_WP_FLAG_LAST) {
1525 multiMissionWPCounter += i + 1;
1526 posControl.multiMissionCount += 1;
1528 #else
1529 posControl.waypointList[i].action = action;
1530 posControl.waypointList[i].lat = lat;
1531 posControl.waypointList[i].lon = lon;
1532 posControl.waypointList[i].alt = alt;
1533 posControl.waypointList[i].p1 = p1;
1534 posControl.waypointList[i].p2 = p2;
1535 posControl.waypointList[i].p3 = p3;
1536 posControl.waypointList[i].flag = flag;
1537 #endif
1539 } else {
1540 cliShowArgumentRangeError("wp index", 0, NAV_MAX_WAYPOINTS - 1);
1545 #endif
1547 #ifdef USE_LED_STRIP
1548 static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
1550 const char *format = "led %u %s";
1551 char ledConfigBuffer[20];
1552 char ledConfigDefaultBuffer[20];
1553 for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
1554 ledConfig_t ledConfig = ledConfigs[i];
1555 generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
1556 bool equalsDefault = false;
1557 if (defaultLedConfigs) {
1558 ledConfig_t ledConfigDefault = defaultLedConfigs[i];
1559 equalsDefault = !memcmp(&ledConfig, &ledConfigDefault, sizeof(ledConfig_t));
1560 generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
1561 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
1563 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
1567 static void cliLed(char *cmdline)
1569 int i;
1570 const char *ptr;
1572 if (isEmpty(cmdline)) {
1573 printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
1574 } else {
1575 ptr = cmdline;
1576 i = fastA2I(ptr);
1577 if (i < LED_MAX_STRIP_LENGTH) {
1578 ptr = nextArg(cmdline);
1579 if (!parseLedStripConfig(i, ptr)) {
1580 cliShowParseError();
1582 } else {
1583 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
1588 static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
1590 const char *format = "color %u %d,%u,%u";
1591 for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
1592 const hsvColor_t *color = &colors[i];
1593 bool equalsDefault = false;
1594 if (defaultColors) {
1595 const hsvColor_t *colorDefault = &defaultColors[i];
1596 equalsDefault = color->h == colorDefault->h
1597 && color->s == colorDefault->s
1598 && color->v == colorDefault->v;
1599 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
1601 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
1605 static void cliColor(char *cmdline)
1607 if (isEmpty(cmdline)) {
1608 printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
1609 } else {
1610 const char *ptr = cmdline;
1611 const int i = fastA2I(ptr);
1612 if (i < LED_CONFIGURABLE_COLOR_COUNT) {
1613 ptr = nextArg(cmdline);
1614 if (!parseColor(i, ptr)) {
1615 cliShowParseError();
1617 } else {
1618 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
1623 static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
1625 const char *format = "mode_color %u %u %u";
1626 for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
1627 for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
1628 int colorIndex = ledStripConfig->modeColors[i].color[j];
1629 bool equalsDefault = false;
1630 if (defaultLedStripConfig) {
1631 int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
1632 equalsDefault = colorIndex == colorIndexDefault;
1633 cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
1635 cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
1639 for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
1640 const int colorIndex = ledStripConfig->specialColors.color[j];
1641 bool equalsDefault = false;
1642 if (defaultLedStripConfig) {
1643 const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
1644 equalsDefault = colorIndex == colorIndexDefault;
1645 cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
1647 cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
1651 static void cliModeColor(char *cmdline)
1653 char * saveptr;
1655 if (isEmpty(cmdline)) {
1656 printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
1657 } else {
1658 enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
1659 int args[ARGS_COUNT];
1660 int argNo = 0;
1661 const char* ptr = strtok_r(cmdline, " ", &saveptr);
1662 while (ptr && argNo < ARGS_COUNT) {
1663 args[argNo++] = fastA2I(ptr);
1664 ptr = strtok_r(NULL, " ", &saveptr);
1667 if (ptr != NULL || argNo != ARGS_COUNT) {
1668 cliShowParseError();
1669 return;
1672 int modeIdx = args[MODE];
1673 int funIdx = args[FUNCTION];
1674 int color = args[COLOR];
1675 if (!setModeColor(modeIdx, funIdx, color)) {
1676 cliShowParseError();
1677 return;
1679 // values are validated
1680 cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
1683 #endif
1685 static void cliDelay(char* cmdLine) {
1686 int ms = 0;
1687 if (isEmpty(cmdLine)) {
1688 cliDelayMs = 0;
1689 cliPrintLine("CLI delay deactivated");
1690 return;
1693 ms = fastA2I(cmdLine);
1694 if (ms) {
1695 cliDelayMs = ms;
1696 cliPrintLinef("CLI delay set to %d ms", ms);
1698 } else {
1699 cliShowParseError();
1704 static void printServo(uint8_t dumpMask, const servoParam_t *servoParam, const servoParam_t *defaultServoParam)
1706 // print out servo settings
1707 const char *format = "servo %u %d %d %d %d";
1708 for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1709 const servoParam_t *servoConf = &servoParam[i];
1710 bool equalsDefault = false;
1711 if (defaultServoParam) {
1712 const servoParam_t *servoConfDefault = &defaultServoParam[i];
1713 equalsDefault = servoConf->min == servoConfDefault->min
1714 && servoConf->max == servoConfDefault->max
1715 && servoConf->middle == servoConfDefault->middle
1716 && servoConf->rate == servoConfDefault->rate;
1717 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1719 servoConfDefault->min,
1720 servoConfDefault->max,
1721 servoConfDefault->middle,
1722 servoConfDefault->rate
1725 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1727 servoConf->min,
1728 servoConf->max,
1729 servoConf->middle,
1730 servoConf->rate
1735 static void cliServo(char *cmdline)
1737 enum { SERVO_ARGUMENT_COUNT = 5 };
1738 int16_t arguments[SERVO_ARGUMENT_COUNT];
1740 servoParam_t *servo;
1742 int i;
1743 const char *ptr;
1745 if (isEmpty(cmdline)) {
1746 printServo(DUMP_MASTER, servoParams(0), NULL);
1747 } else {
1748 int validArgumentCount = 0;
1750 ptr = cmdline;
1752 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1754 // If command line doesn't fit the format, don't modify the config
1755 while (*ptr) {
1756 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1757 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1758 cliShowParseError();
1759 return;
1762 arguments[validArgumentCount++] = fastA2I(ptr);
1764 do {
1765 ptr++;
1766 } while (*ptr >= '0' && *ptr <= '9');
1767 } else if (*ptr == ' ') {
1768 ptr++;
1769 } else {
1770 cliShowParseError();
1771 return;
1775 enum {INDEX = 0, MIN, MAX, MIDDLE, RATE};
1777 i = arguments[INDEX];
1779 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1780 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1781 cliShowParseError();
1782 return;
1785 servo = servoParamsMutable(i);
1787 if (
1788 arguments[MIN] < SERVO_OUTPUT_MIN || arguments[MIN] > SERVO_OUTPUT_MAX ||
1789 arguments[MAX] < SERVO_OUTPUT_MIN || arguments[MAX] > SERVO_OUTPUT_MAX ||
1790 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1791 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1792 arguments[RATE] < -125 || arguments[RATE] > 125
1794 cliShowParseError();
1795 return;
1798 servo->min = arguments[MIN];
1799 servo->max = arguments[MAX];
1800 servo->middle = arguments[MIDDLE];
1801 servo->rate = arguments[RATE];
1805 static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
1807 const char *format = "smix %d %d %d %d %d %d";
1808 for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
1809 const servoMixer_t customServoMixer = customServoMixers[i];
1810 if (customServoMixer.rate == 0) {
1811 break;
1814 bool equalsDefault = false;
1815 if (defaultCustomServoMixers) {
1816 servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
1817 equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
1818 && customServoMixer.inputSource == customServoMixerDefault.inputSource
1819 && customServoMixer.rate == customServoMixerDefault.rate
1820 && customServoMixer.speed == customServoMixerDefault.speed
1821 #ifdef USE_PROGRAMMING_FRAMEWORK
1822 && customServoMixer.conditionId == customServoMixerDefault.conditionId
1823 #endif
1826 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1828 customServoMixerDefault.targetChannel,
1829 customServoMixerDefault.inputSource,
1830 customServoMixerDefault.rate,
1831 customServoMixerDefault.speed,
1832 #ifdef USE_PROGRAMMING_FRAMEWORK
1833 customServoMixer.conditionId
1834 #else
1836 #endif
1839 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1841 customServoMixer.targetChannel,
1842 customServoMixer.inputSource,
1843 customServoMixer.rate,
1844 customServoMixer.speed,
1845 #ifdef USE_PROGRAMMING_FRAMEWORK
1846 customServoMixer.conditionId
1847 #else
1849 #endif
1854 static void cliServoMix(char *cmdline)
1856 char * saveptr;
1857 int args[6], check = 0;
1858 uint8_t len = strlen(cmdline);
1860 if (len == 0) {
1861 printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
1862 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1863 // erase custom mixer
1864 pgResetCopy(customServoMixersMutable(0), PG_SERVO_MIXER);
1865 } else {
1866 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, CONDITION, ARGS_COUNT};
1867 char *ptr = strtok_r(cmdline, " ", &saveptr);
1868 args[CONDITION] = -1;
1869 while (ptr != NULL && check < ARGS_COUNT) {
1870 args[check++] = fastA2I(ptr);
1871 ptr = strtok_r(NULL, " ", &saveptr);
1874 if (ptr != NULL || (check < ARGS_COUNT - 1)) {
1875 cliShowParseError();
1876 return;
1879 int32_t i = args[RULE];
1880 if (
1881 i >= 0 && i < MAX_SERVO_RULES &&
1882 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1883 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1884 args[RATE] >= -1000 && args[RATE] <= 1000 &&
1885 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1886 args[CONDITION] >= -1 && args[CONDITION] < MAX_LOGIC_CONDITIONS
1888 customServoMixersMutable(i)->targetChannel = args[TARGET];
1889 customServoMixersMutable(i)->inputSource = args[INPUT];
1890 customServoMixersMutable(i)->rate = args[RATE];
1891 customServoMixersMutable(i)->speed = args[SPEED];
1892 #ifdef USE_PROGRAMMING_FRAMEWORK
1893 customServoMixersMutable(i)->conditionId = args[CONDITION];
1894 #endif
1895 cliServoMix("");
1896 } else {
1897 cliShowParseError();
1902 #ifdef USE_PROGRAMMING_FRAMEWORK
1904 static void printLogic(uint8_t dumpMask, const logicCondition_t *logicConditions, const logicCondition_t *defaultLogicConditions, int16_t showLC)
1906 const char *format = "logic %d %d %d %d %d %d %d %d %d";
1907 for (uint8_t i = 0; i < MAX_LOGIC_CONDITIONS; i++) {
1908 if (showLC == -1 || showLC == i) {
1909 const logicCondition_t logic = logicConditions[i];
1911 bool equalsDefault = false;
1912 if (defaultLogicConditions) {
1913 logicCondition_t defaultValue = defaultLogicConditions[i];
1914 equalsDefault =
1915 logic.enabled == defaultValue.enabled &&
1916 logic.activatorId == defaultValue.activatorId &&
1917 logic.operation == defaultValue.operation &&
1918 logic.operandA.type == defaultValue.operandA.type &&
1919 logic.operandA.value == defaultValue.operandA.value &&
1920 logic.operandB.type == defaultValue.operandB.type &&
1921 logic.operandB.value == defaultValue.operandB.value &&
1922 logic.flags == defaultValue.flags;
1924 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
1926 logic.enabled,
1927 logic.activatorId,
1928 logic.operation,
1929 logic.operandA.type,
1930 logic.operandA.value,
1931 logic.operandB.type,
1932 logic.operandB.value,
1933 logic.flags
1936 cliDumpPrintLinef(dumpMask, equalsDefault, format,
1938 logic.enabled,
1939 logic.activatorId,
1940 logic.operation,
1941 logic.operandA.type,
1942 logic.operandA.value,
1943 logic.operandB.type,
1944 logic.operandB.value,
1945 logic.flags
1951 static void processCliLogic(char *cmdline, int16_t lcIndex) {
1952 char * saveptr;
1953 int args[9], check = 0;
1954 uint8_t len = strlen(cmdline);
1956 if (len == 0) {
1957 if (!commandBatchActive) {
1958 printLogic(DUMP_MASTER, logicConditions(0), NULL, -1);
1959 } else if (lcIndex >= 0) {
1960 printLogic(DUMP_MASTER, logicConditions(0), NULL, lcIndex);
1962 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
1963 pgResetCopy(logicConditionsMutable(0), PG_LOGIC_CONDITIONS);
1964 } else {
1965 enum {
1966 INDEX = 0,
1967 ENABLED,
1968 ACTIVATOR_ID,
1969 OPERATION,
1970 OPERAND_A_TYPE,
1971 OPERAND_A_VALUE,
1972 OPERAND_B_TYPE,
1973 OPERAND_B_VALUE,
1974 FLAGS,
1975 ARGS_COUNT
1977 char *ptr = strtok_r(cmdline, " ", &saveptr);
1978 while (ptr != NULL && check < ARGS_COUNT) {
1979 args[check++] = fastA2I(ptr);
1980 ptr = strtok_r(NULL, " ", &saveptr);
1983 if (ptr != NULL || check != ARGS_COUNT) {
1984 cliShowParseError();
1985 return;
1988 int32_t i = args[INDEX];
1989 if (
1990 i >= 0 && i < MAX_LOGIC_CONDITIONS &&
1991 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
1992 args[ACTIVATOR_ID] >= -1 && args[ACTIVATOR_ID] < MAX_LOGIC_CONDITIONS &&
1993 args[OPERATION] >= 0 && args[OPERATION] < LOGIC_CONDITION_LAST &&
1994 args[OPERAND_A_TYPE] >= 0 && args[OPERAND_A_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
1995 args[OPERAND_A_VALUE] >= -1000000 && args[OPERAND_A_VALUE] <= 1000000 &&
1996 args[OPERAND_B_TYPE] >= 0 && args[OPERAND_B_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
1997 args[OPERAND_B_VALUE] >= -1000000 && args[OPERAND_B_VALUE] <= 1000000 &&
1998 args[FLAGS] >= 0 && args[FLAGS] <= 255
2001 logicConditionsMutable(i)->enabled = args[ENABLED];
2002 logicConditionsMutable(i)->activatorId = args[ACTIVATOR_ID];
2003 logicConditionsMutable(i)->operation = args[OPERATION];
2004 logicConditionsMutable(i)->operandA.type = args[OPERAND_A_TYPE];
2005 logicConditionsMutable(i)->operandA.value = args[OPERAND_A_VALUE];
2006 logicConditionsMutable(i)->operandB.type = args[OPERAND_B_TYPE];
2007 logicConditionsMutable(i)->operandB.value = args[OPERAND_B_VALUE];
2008 logicConditionsMutable(i)->flags = args[FLAGS];
2010 processCliLogic("", i);
2011 } else {
2012 cliShowParseError();
2017 static void cliLogic(char *cmdline) {
2018 processCliLogic(cmdline, -1);
2021 static void printGvar(uint8_t dumpMask, const globalVariableConfig_t *gvars, const globalVariableConfig_t *defaultGvars)
2023 const char *format = "gvar %d %d %d %d";
2024 for (uint32_t i = 0; i < MAX_GLOBAL_VARIABLES; i++) {
2025 const globalVariableConfig_t gvar = gvars[i];
2027 bool equalsDefault = false;
2028 if (defaultGvars) {
2029 globalVariableConfig_t defaultValue = defaultGvars[i];
2030 equalsDefault =
2031 gvar.defaultValue == defaultValue.defaultValue &&
2032 gvar.min == defaultValue.min &&
2033 gvar.max == defaultValue.max;
2035 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2037 gvar.defaultValue,
2038 gvar.min,
2039 gvar.max
2042 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2044 gvar.defaultValue,
2045 gvar.min,
2046 gvar.max
2051 static void cliGvar(char *cmdline) {
2052 char * saveptr;
2053 int args[4], check = 0;
2054 uint8_t len = strlen(cmdline);
2056 if (len == 0) {
2057 printGvar(DUMP_MASTER, globalVariableConfigs(0), NULL);
2058 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2059 pgResetCopy(globalVariableConfigsMutable(0), PG_GLOBAL_VARIABLE_CONFIG);
2060 } else {
2061 enum {
2062 INDEX = 0,
2063 DEFAULT,
2064 MIN,
2065 MAX,
2066 ARGS_COUNT
2068 char *ptr = strtok_r(cmdline, " ", &saveptr);
2069 while (ptr != NULL && check < ARGS_COUNT) {
2070 args[check++] = fastA2I(ptr);
2071 ptr = strtok_r(NULL, " ", &saveptr);
2074 if (ptr != NULL || check != ARGS_COUNT) {
2075 cliShowParseError();
2076 return;
2079 int32_t i = args[INDEX];
2080 if (
2081 i >= 0 && i < MAX_GLOBAL_VARIABLES &&
2082 args[DEFAULT] >= INT32_MIN && args[DEFAULT] <= INT32_MAX &&
2083 args[MIN] >= INT32_MIN && args[MIN] <= INT32_MAX &&
2084 args[MAX] >= INT32_MIN && args[MAX] <= INT32_MAX
2086 globalVariableConfigsMutable(i)->defaultValue = args[DEFAULT];
2087 globalVariableConfigsMutable(i)->min = args[MIN];
2088 globalVariableConfigsMutable(i)->max = args[MAX];
2090 cliGvar("");
2091 } else {
2092 cliShowParseError();
2097 static void printPid(uint8_t dumpMask, const programmingPid_t *programmingPids, const programmingPid_t *defaultProgrammingPids)
2099 const char *format = "pid %d %d %d %d %d %d %d %d %d %d";
2100 for (uint32_t i = 0; i < MAX_PROGRAMMING_PID_COUNT; i++) {
2101 const programmingPid_t pid = programmingPids[i];
2103 bool equalsDefault = false;
2104 if (defaultProgrammingPids) {
2105 programmingPid_t defaultValue = defaultProgrammingPids[i];
2106 equalsDefault =
2107 pid.enabled == defaultValue.enabled &&
2108 pid.setpoint.type == defaultValue.setpoint.type &&
2109 pid.setpoint.value == defaultValue.setpoint.value &&
2110 pid.measurement.type == defaultValue.measurement.type &&
2111 pid.measurement.value == defaultValue.measurement.value &&
2112 pid.gains.P == defaultValue.gains.P &&
2113 pid.gains.I == defaultValue.gains.I &&
2114 pid.gains.D == defaultValue.gains.D &&
2115 pid.gains.FF == defaultValue.gains.FF;
2117 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2119 pid.enabled,
2120 pid.setpoint.type,
2121 pid.setpoint.value,
2122 pid.measurement.type,
2123 pid.measurement.value,
2124 pid.gains.P,
2125 pid.gains.I,
2126 pid.gains.D,
2127 pid.gains.FF
2130 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2132 pid.enabled,
2133 pid.setpoint.type,
2134 pid.setpoint.value,
2135 pid.measurement.type,
2136 pid.measurement.value,
2137 pid.gains.P,
2138 pid.gains.I,
2139 pid.gains.D,
2140 pid.gains.FF
2145 static void cliPid(char *cmdline) {
2146 char * saveptr;
2147 int args[10], check = 0;
2148 uint8_t len = strlen(cmdline);
2150 if (len == 0) {
2151 printPid(DUMP_MASTER, programmingPids(0), NULL);
2152 } else if (sl_strncasecmp(cmdline, "reset", 5) == 0) {
2153 pgResetCopy(programmingPidsMutable(0), PG_LOGIC_CONDITIONS);
2154 } else {
2155 enum {
2156 INDEX = 0,
2157 ENABLED,
2158 SETPOINT_TYPE,
2159 SETPOINT_VALUE,
2160 MEASUREMENT_TYPE,
2161 MEASUREMENT_VALUE,
2162 P_GAIN,
2163 I_GAIN,
2164 D_GAIN,
2165 FF_GAIN,
2166 ARGS_COUNT
2168 char *ptr = strtok_r(cmdline, " ", &saveptr);
2169 while (ptr != NULL && check < ARGS_COUNT) {
2170 args[check++] = fastA2I(ptr);
2171 ptr = strtok_r(NULL, " ", &saveptr);
2174 if (ptr != NULL || check != ARGS_COUNT) {
2175 cliShowParseError();
2176 return;
2179 int32_t i = args[INDEX];
2180 if (
2181 i >= 0 && i < MAX_PROGRAMMING_PID_COUNT &&
2182 args[ENABLED] >= 0 && args[ENABLED] <= 1 &&
2183 args[SETPOINT_TYPE] >= 0 && args[SETPOINT_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2184 args[SETPOINT_VALUE] >= -1000000 && args[SETPOINT_VALUE] <= 1000000 &&
2185 args[MEASUREMENT_TYPE] >= 0 && args[MEASUREMENT_TYPE] < LOGIC_CONDITION_OPERAND_TYPE_LAST &&
2186 args[MEASUREMENT_VALUE] >= -1000000 && args[MEASUREMENT_VALUE] <= 1000000 &&
2187 args[P_GAIN] >= 0 && args[P_GAIN] <= INT16_MAX &&
2188 args[I_GAIN] >= 0 && args[I_GAIN] <= INT16_MAX &&
2189 args[D_GAIN] >= 0 && args[D_GAIN] <= INT16_MAX &&
2190 args[FF_GAIN] >= 0 && args[FF_GAIN] <= INT16_MAX
2192 programmingPidsMutable(i)->enabled = args[ENABLED];
2193 programmingPidsMutable(i)->setpoint.type = args[SETPOINT_TYPE];
2194 programmingPidsMutable(i)->setpoint.value = args[SETPOINT_VALUE];
2195 programmingPidsMutable(i)->measurement.type = args[MEASUREMENT_TYPE];
2196 programmingPidsMutable(i)->measurement.value = args[MEASUREMENT_VALUE];
2197 programmingPidsMutable(i)->gains.P = args[P_GAIN];
2198 programmingPidsMutable(i)->gains.I = args[I_GAIN];
2199 programmingPidsMutable(i)->gains.D = args[D_GAIN];
2200 programmingPidsMutable(i)->gains.FF = args[FF_GAIN];
2202 cliPid("");
2203 } else {
2204 cliShowParseError();
2209 #endif
2211 #ifdef USE_SDCARD
2213 static void cliWriteBytes(const uint8_t *buffer, int count)
2215 while (count > 0) {
2216 cliWrite(*buffer);
2217 buffer++;
2218 count--;
2222 static void cliSdInfo(char *cmdline)
2224 UNUSED(cmdline);
2226 cliPrint("SD card: ");
2228 if (!sdcard_isInserted()) {
2229 cliPrintLine("None inserted");
2230 return;
2233 if (!sdcard_isInitialized()) {
2234 cliPrintLine("Startup failed");
2235 return;
2238 const sdcardMetadata_t *metadata = sdcard_getMetadata();
2240 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
2241 metadata->manufacturerID,
2242 metadata->numBlocks / 2, /* One block is half a kB */
2243 metadata->productionMonth,
2244 metadata->productionYear,
2245 metadata->productRevisionMajor,
2246 metadata->productRevisionMinor
2249 cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
2251 cliPrint("'\r\n" "Filesystem: ");
2253 switch (afatfs_getFilesystemState()) {
2254 case AFATFS_FILESYSTEM_STATE_READY:
2255 cliPrint("Ready");
2256 break;
2257 case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
2258 cliPrint("Initializing");
2259 break;
2260 case AFATFS_FILESYSTEM_STATE_UNKNOWN:
2261 case AFATFS_FILESYSTEM_STATE_FATAL:
2262 cliPrint("Fatal");
2264 switch (afatfs_getLastError()) {
2265 case AFATFS_ERROR_BAD_MBR:
2266 cliPrint(" - no FAT MBR partitions");
2267 break;
2268 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
2269 cliPrint(" - bad FAT header");
2270 break;
2271 case AFATFS_ERROR_GENERIC:
2272 case AFATFS_ERROR_NONE:
2273 ; // Nothing more detailed to print
2274 break;
2276 break;
2278 cliPrintLinefeed();
2281 #endif
2283 #ifdef USE_FLASHFS
2285 static void cliFlashInfo(char *cmdline)
2287 UNUSED(cmdline);
2289 const flashGeometry_t *layout = flashGetGeometry();
2291 if (layout->totalSize == 0) {
2292 cliPrintLine("Flash not available");
2293 return;
2296 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u",
2297 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize);
2299 for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
2300 const flashPartition_t *partition;
2301 if (index == 0) {
2302 cliPrintLine("Paritions:");
2304 partition = flashPartitionFindByIndex(index);
2305 if (!partition) {
2306 break;
2308 cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
2310 #ifdef USE_FLASHFS
2311 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
2313 cliPrintLinef("FlashFS size=%u, usedSize=%u",
2314 FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
2315 flashfsGetOffset()
2317 #endif
2320 static void cliFlashErase(char *cmdline)
2322 UNUSED(cmdline);
2324 const flashGeometry_t *layout = flashGetGeometry();
2326 if (layout->totalSize == 0) {
2327 cliPrintLine("Flash not available");
2328 return;
2331 cliPrintLine("Erasing...");
2332 flashfsEraseCompletely();
2334 while (!flashIsReady()) {
2335 delay(100);
2338 cliPrintLine("Done.");
2341 #ifdef USE_FLASH_TOOLS
2343 static void cliFlashWrite(char *cmdline)
2345 const uint32_t address = fastA2I(cmdline);
2346 const char *text = strchr(cmdline, ' ');
2348 if (!text) {
2349 cliShowParseError();
2350 } else {
2351 flashfsSeekAbs(address);
2352 flashfsWrite((uint8_t*)text, strlen(text), true);
2353 flashfsFlushSync();
2355 cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
2359 static void cliFlashRead(char *cmdline)
2361 uint32_t address = fastA2I(cmdline);
2363 const char *nextArg = strchr(cmdline, ' ');
2365 if (!nextArg) {
2366 cliShowParseError();
2367 } else {
2368 uint32_t length = fastA2I(nextArg);
2370 cliPrintLinef("Reading %u bytes at %u:", length, address);
2372 uint8_t buffer[32];
2373 while (length > 0) {
2374 int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
2376 for (int i = 0; i < bytesRead; i++) {
2377 cliWrite(buffer[i]);
2380 length -= bytesRead;
2381 address += bytesRead;
2383 if (bytesRead == 0) {
2384 //Assume we reached the end of the volume or something fatal happened
2385 break;
2388 cliPrintLinefeed();
2392 #endif
2393 #endif
2395 #ifdef USE_OSD
2396 static void printOsdLayout(uint8_t dumpMask, const osdLayoutsConfig_t *config, const osdLayoutsConfig_t *configDefault, int layout, int item)
2398 // "<layout> <item> <col> <row> <visible>"
2399 const char *format = "osd_layout %d %d %d %d %c";
2400 for (int ii = 0; ii < OSD_LAYOUT_COUNT; ii++) {
2401 if (layout >= 0 && layout != ii) {
2402 continue;
2404 const uint16_t *layoutItems = config->item_pos[ii];
2405 const uint16_t *defaultLayoutItems = configDefault->item_pos[ii];
2406 for (int jj = 0; jj < OSD_ITEM_COUNT; jj++) {
2407 if (item >= 0 && item != jj) {
2408 continue;
2410 bool equalsDefault = layoutItems[jj] == defaultLayoutItems[jj];
2411 cliDefaultPrintLinef(dumpMask, equalsDefault, format,
2412 ii, jj,
2413 OSD_X(defaultLayoutItems[jj]),
2414 OSD_Y(defaultLayoutItems[jj]),
2415 OSD_VISIBLE(defaultLayoutItems[jj]) ? 'V' : 'H');
2417 cliDumpPrintLinef(dumpMask, equalsDefault, format,
2418 ii, jj,
2419 OSD_X(layoutItems[jj]),
2420 OSD_Y(layoutItems[jj]),
2421 OSD_VISIBLE(layoutItems[jj]) ? 'V' : 'H');
2426 static void cliOsdLayout(char *cmdline)
2428 char * saveptr;
2430 int layout = -1;
2431 int item = -1;
2432 int col = 0;
2433 int row = 0;
2434 bool visible = false;
2435 char *tok = strtok_r(cmdline, " ", &saveptr);
2437 int ii;
2439 for (ii = 0; tok != NULL; ii++, tok = strtok_r(NULL, " ", &saveptr)) {
2440 switch (ii) {
2441 case 0:
2442 layout = fastA2I(tok);
2443 if (layout < 0 || layout >= OSD_LAYOUT_COUNT) {
2444 cliShowParseError();
2445 return;
2447 break;
2448 case 1:
2449 item = fastA2I(tok);
2450 if (item < 0 || item >= OSD_ITEM_COUNT) {
2451 cliShowParseError();
2452 return;
2454 break;
2455 case 2:
2456 col = fastA2I(tok);
2457 if (col < 0 || col > OSD_X(OSD_POS_MAX)) {
2458 cliShowParseError();
2459 return;
2461 break;
2462 case 3:
2463 row = fastA2I(tok);
2464 if (row < 0 || row > OSD_Y(OSD_POS_MAX)) {
2465 cliShowParseError();
2466 return;
2468 break;
2469 case 4:
2470 switch (*tok) {
2471 case 'H':
2472 visible = false;
2473 break;
2474 case 'V':
2475 visible = true;
2476 break;
2477 default:
2478 cliShowParseError();
2479 return;
2481 break;
2482 default:
2483 cliShowParseError();
2484 return;
2488 switch (ii) {
2489 case 0:
2490 FALLTHROUGH;
2491 case 1:
2492 FALLTHROUGH;
2493 case 2:
2494 // No args, or just layout or layout and item. If any of them not provided,
2495 // it will be the -1 that we used during initialization, so printOsdLayout()
2496 // won't use them for filtering.
2497 printOsdLayout(DUMP_MASTER, osdLayoutsConfig(), osdLayoutsConfig(), layout, item);
2498 break;
2499 case 4:
2500 // No visibility provided. Keep the previous one.
2501 visible = OSD_VISIBLE(osdLayoutsConfig()->item_pos[layout][item]);
2502 FALLTHROUGH;
2503 case 5:
2504 // Layout, item, pos and visibility. Set the item.
2505 osdLayoutsConfigMutable()->item_pos[layout][item] = OSD_POS(col, row) | (visible ? OSD_VISIBLE_FLAG : 0);
2506 break;
2507 default:
2508 // Unhandled
2509 cliShowParseError();
2510 return;
2514 #endif
2516 static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
2518 uint32_t mask = featureConfig->enabledFeatures;
2519 uint32_t defaultMask = featureConfigDefault->enabledFeatures;
2520 for (uint32_t i = 0; ; i++) { // disable all feature first
2521 if (featureNames[i] == NULL)
2522 break;
2523 if (featureNames[i][0] == '\0')
2524 continue;
2525 const char *format = "feature -%s";
2526 cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2527 cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2529 for (uint32_t i = 0; ; i++) { // reenable what we want.
2530 if (featureNames[i] == NULL)
2531 break;
2532 if (featureNames[i][0] == '\0')
2533 continue;
2534 const char *format = "feature %s";
2535 if (defaultMask & (1 << i)) {
2536 cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
2538 if (mask & (1 << i)) {
2539 cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
2544 static void cliFeature(char *cmdline)
2546 uint32_t len = strlen(cmdline);
2547 uint32_t mask = featureMask();
2549 if (len == 0) {
2550 cliPrint("Enabled: ");
2551 for (uint32_t i = 0; ; i++) {
2552 if (featureNames[i] == NULL)
2553 break;
2554 if (featureNames[i][0] == '\0')
2555 continue;
2556 if (mask & (1 << i))
2557 cliPrintf("%s ", featureNames[i]);
2559 cliPrintLinefeed();
2560 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
2561 cliPrint("Available: ");
2562 for (uint32_t i = 0; ; i++) {
2563 if (featureNames[i] == NULL)
2564 break;
2565 if (featureNames[i][0] == '\0')
2566 continue;
2567 cliPrintf("%s ", featureNames[i]);
2569 cliPrintLinefeed();
2570 return;
2571 } else {
2572 bool remove = false;
2573 if (cmdline[0] == '-') {
2574 // remove feature
2575 remove = true;
2576 cmdline++; // skip over -
2577 len--;
2580 for (uint32_t i = 0; ; i++) {
2581 if (featureNames[i] == NULL) {
2582 cliPrintErrorLine("Invalid name");
2583 break;
2586 if (sl_strncasecmp(cmdline, featureNames[i], len) == 0) {
2588 mask = 1 << i;
2589 #ifndef USE_GPS
2590 if (mask & FEATURE_GPS) {
2591 cliPrintErrorLine("unavailable");
2592 break;
2594 #endif
2595 if (remove) {
2596 featureClear(mask);
2597 cliPrint("Disabled");
2598 } else {
2599 featureSet(mask);
2600 cliPrint("Enabled");
2602 cliPrintLinef(" %s", featureNames[i]);
2603 break;
2609 #ifdef USE_BLACKBOX
2610 static void printBlackbox(uint8_t dumpMask, const blackboxConfig_t *config, const blackboxConfig_t *configDefault)
2613 UNUSED(configDefault);
2615 uint32_t mask = config->includeFlags;
2617 for (uint8_t i = 0; ; i++) { // reenable what we want.
2618 if (blackboxIncludeFlagNames[i] == NULL) {
2619 break;
2622 const char *formatOn = "blackbox %s";
2623 const char *formatOff = "blackbox -%s";
2625 if (mask & (1 << i)) {
2626 cliDumpPrintLinef(dumpMask, false, formatOn, blackboxIncludeFlagNames[i]);
2627 cliDefaultPrintLinef(dumpMask, false, formatOn, blackboxIncludeFlagNames[i]);
2628 } else {
2629 cliDumpPrintLinef(dumpMask, false, formatOff, blackboxIncludeFlagNames[i]);
2630 cliDefaultPrintLinef(dumpMask, false, formatOff, blackboxIncludeFlagNames[i]);
2636 static void cliBlackbox(char *cmdline)
2638 uint32_t len = strlen(cmdline);
2639 uint32_t mask = blackboxConfig()->includeFlags;
2641 if (len == 0) {
2642 cliPrint("Enabled: ");
2643 for (uint8_t i = 0; ; i++) {
2644 if (blackboxIncludeFlagNames[i] == NULL) {
2645 break;
2648 if (mask & (1 << i))
2649 cliPrintf("%s ", blackboxIncludeFlagNames[i]);
2651 cliPrintLinefeed();
2652 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
2653 cliPrint("Available: ");
2654 for (uint32_t i = 0; ; i++) {
2655 if (blackboxIncludeFlagNames[i] == NULL) {
2656 break;
2659 cliPrintf("%s ", blackboxIncludeFlagNames[i]);
2661 cliPrintLinefeed();
2662 return;
2663 } else {
2664 bool remove = false;
2665 if (cmdline[0] == '-') {
2666 // remove feature
2667 remove = true;
2668 cmdline++; // skip over -
2669 len--;
2672 for (uint32_t i = 0; ; i++) {
2673 if (blackboxIncludeFlagNames[i] == NULL) {
2674 cliPrintErrorLine("Invalid name");
2675 break;
2678 if (sl_strncasecmp(cmdline, blackboxIncludeFlagNames[i], len) == 0) {
2680 mask = 1 << i;
2682 if (remove) {
2683 blackboxIncludeFlagClear(mask);
2684 cliPrint("Disabled");
2685 } else {
2686 blackboxIncludeFlagSet(mask);
2687 cliPrint("Enabled");
2689 cliPrintLinef(" %s", blackboxIncludeFlagNames[i]);
2690 break;
2695 #endif
2697 #if defined(BEEPER) || defined(USE_DSHOT)
2698 static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
2700 const uint8_t beeperCount = beeperTableEntryCount();
2701 const uint32_t mask = beeperConfig->beeper_off_flags;
2702 const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
2703 for (int i = 0; i < beeperCount - 2; i++) {
2704 const char *formatOff = "beeper -%s";
2705 const char *formatOn = "beeper %s";
2706 cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOn : formatOff, beeperNameForTableIndex(i));
2707 cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOff : formatOn, beeperNameForTableIndex(i));
2711 static void cliBeeper(char *cmdline)
2713 uint32_t len = strlen(cmdline);
2714 uint8_t beeperCount = beeperTableEntryCount();
2715 uint32_t mask = getBeeperOffMask();
2717 if (len == 0) {
2718 cliPrintf("Disabled:");
2719 for (int32_t i = 0; ; i++) {
2720 if (i == beeperCount - 2){
2721 if (mask == 0)
2722 cliPrint(" none");
2723 break;
2725 if (mask & (1 << (beeperModeForTableIndex(i) - 1)))
2726 cliPrintf(" %s", beeperNameForTableIndex(i));
2728 cliPrintLinefeed();
2729 } else if (sl_strncasecmp(cmdline, "list", len) == 0) {
2730 cliPrint("Available:");
2731 for (uint32_t i = 0; i < beeperCount; i++)
2732 cliPrintf(" %s", beeperNameForTableIndex(i));
2733 cliPrintLinefeed();
2734 return;
2735 } else {
2736 bool remove = false;
2737 if (cmdline[0] == '-') {
2738 remove = true; // this is for beeper OFF condition
2739 cmdline++;
2740 len--;
2743 for (uint32_t i = 0; ; i++) {
2744 if (i == beeperCount) {
2745 cliPrintErrorLine("Invalid name");
2746 break;
2748 if (sl_strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
2749 if (remove) { // beeper off
2750 if (i == BEEPER_ALL-1)
2751 beeperOffSetAll(beeperCount-2);
2752 else
2753 if (i == BEEPER_PREFERENCE-1)
2754 setBeeperOffMask(getPreferredBeeperOffMask());
2755 else {
2756 mask = 1 << (beeperModeForTableIndex(i) - 1);
2757 beeperOffSet(mask);
2759 cliPrint("Disabled");
2761 else { // beeper on
2762 if (i == BEEPER_ALL-1)
2763 beeperOffClearAll();
2764 else
2765 if (i == BEEPER_PREFERENCE-1)
2766 setPreferredBeeperOffMask(getBeeperOffMask());
2767 else {
2768 mask = 1 << (beeperModeForTableIndex(i) - 1);
2769 beeperOffClear(mask);
2771 cliPrint("Enabled");
2773 cliPrintLinef(" %s", beeperNameForTableIndex(i));
2774 break;
2779 #endif
2781 static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
2783 bool equalsDefault = true;
2784 char buf[16];
2785 char bufDefault[16];
2786 uint32_t i;
2788 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
2789 buf[i] = bufDefault[i] = 0;
2792 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
2793 buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
2794 if (defaultRxConfig) {
2795 bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
2796 equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
2799 buf[i] = '\0';
2801 const char *formatMap = "map %s";
2802 cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
2803 cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
2806 static void cliMap(char *cmdline)
2808 uint32_t len;
2809 char out[MAX_MAPPABLE_RX_INPUTS + 1];
2811 len = strlen(cmdline);
2813 if (len == MAX_MAPPABLE_RX_INPUTS) {
2814 // uppercase it
2815 for (uint32_t i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
2816 cmdline[i] = sl_toupper((unsigned char)cmdline[i]);
2818 for (uint32_t i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
2819 if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i])) {
2820 continue;
2822 cliShowParseError();
2823 return;
2825 parseRcChannels(cmdline);
2826 } else if (len != 0) {
2827 cliShowParseError();
2829 cliPrint("Map: ");
2830 uint32_t i;
2831 for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++){
2832 out[rxConfig()->rcmap[i]] = rcChannelLetters[i];
2834 out[i] = '\0';
2835 cliPrintLinef("%s", out);
2838 static const char *checkCommand(const char *cmdLine, const char *command)
2840 if (!sl_strncasecmp(cmdLine, command, strlen(command)) // command names match
2841 && !sl_isalnum((unsigned)cmdLine[strlen(command)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
2842 return cmdLine + strlen(command) + 1;
2843 } else {
2844 return 0;
2848 static void cliRebootEx(bool bootLoader)
2850 cliPrint("\r\nRebooting");
2851 bufWriterFlush(cliWriter);
2852 waitForSerialPortToFinishTransmitting(cliPort);
2854 fcReboot(bootLoader);
2857 static void cliReboot(void)
2859 cliRebootEx(false);
2862 static void cliDfu(char *cmdline)
2864 UNUSED(cmdline);
2865 #ifndef CLI_MINIMAL_VERBOSITY
2866 cliPrint("\r\nRestarting in DFU mode");
2867 #endif
2868 cliRebootEx(true);
2871 #if defined (USE_SERIALRX_SRXL2)
2872 void cliRxBind(char *cmdline){
2873 UNUSED(cmdline);
2874 if (rxConfig()->receiverType == RX_TYPE_SERIAL) {
2875 switch (rxConfig()->serialrx_provider) {
2876 default:
2877 cliPrint("Not supported.");
2878 break;
2879 #if defined(USE_SERIALRX_SRXL2)
2880 case SERIALRX_SRXL2:
2881 srxl2Bind();
2882 cliPrint("Binding SRXL2 receiver...");
2883 break;
2884 #endif
2888 #endif
2890 static void cliExit(char *cmdline)
2892 UNUSED(cmdline);
2894 #ifndef CLI_MINIMAL_VERBOSITY
2895 cliPrintLine("\r\nLeaving CLI mode, unsaved changes lost.");
2896 #endif
2897 bufWriterFlush(cliWriter);
2899 *cliBuffer = '\0';
2900 bufferIndex = 0;
2901 cliMode = false;
2902 // incase a motor was left running during motortest, clear it here
2903 mixerResetDisarmedMotors();
2904 cliReboot();
2906 cliWriter = NULL;
2909 #ifdef USE_GPS
2910 static void cliGpsPassthrough(char *cmdline)
2912 UNUSED(cmdline);
2914 gpsEnablePassthrough(cliPort);
2916 #endif
2918 static void cliMotor(char *cmdline)
2920 int motor_index = 0;
2921 int motor_value = 0;
2922 int index = 0;
2923 char *pch = NULL;
2924 char *saveptr;
2926 if (isEmpty(cmdline)) {
2927 cliShowParseError();
2929 return;
2932 pch = strtok_r(cmdline, " ", &saveptr);
2933 while (pch != NULL) {
2934 switch (index) {
2935 case 0:
2936 motor_index = fastA2I(pch);
2937 break;
2938 case 1:
2939 motor_value = fastA2I(pch);
2940 break;
2942 index++;
2943 pch = strtok_r(NULL, " ", &saveptr);
2946 if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
2947 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
2948 return;
2951 if (index == 2) {
2952 if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
2953 cliShowArgumentRangeError("value", 1000, 2000);
2954 return;
2955 } else {
2956 motor_disarmed[motor_index] = motor_value;
2960 cliPrintLinef("motor %d: %d", motor_index, motor_disarmed[motor_index]);
2963 static void cliPlaySound(char *cmdline)
2965 int i;
2966 const char *name;
2967 static int lastSoundIdx = -1;
2969 if (isEmpty(cmdline)) {
2970 i = lastSoundIdx + 1; //next sound index
2971 if ((name=beeperNameForTableIndex(i)) == NULL) {
2972 while (true) { //no name for index; try next one
2973 if (++i >= beeperTableEntryCount())
2974 i = 0; //if end then wrap around to first entry
2975 if ((name=beeperNameForTableIndex(i)) != NULL)
2976 break; //if name OK then play sound below
2977 if (i == lastSoundIdx + 1) { //prevent infinite loop
2978 cliPrintLine("Error playing sound");
2979 return;
2983 } else { //index value was given
2984 i = fastA2I(cmdline);
2985 if ((name=beeperNameForTableIndex(i)) == NULL) {
2986 cliPrintLinef("No sound for index %d", i);
2987 return;
2990 lastSoundIdx = i;
2991 beeperSilence();
2992 cliPrintLinef("Playing sound %d: %s", i, name);
2993 beeper(beeperModeForTableIndex(i));
2996 static void cliProfile(char *cmdline)
2998 // CLI profile index is 1-based
2999 if (isEmpty(cmdline)) {
3000 cliPrintLinef("profile %d", getConfigProfile() + 1);
3001 return;
3002 } else {
3003 const int i = fastA2I(cmdline) - 1;
3004 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3005 setConfigProfileAndWriteEEPROM(i);
3006 cliProfile("");
3011 static void cliDumpProfile(uint8_t profileIndex, uint8_t dumpMask)
3013 if (profileIndex >= MAX_PROFILE_COUNT) {
3014 // Faulty values
3015 return;
3017 setConfigProfile(profileIndex);
3018 cliPrintHashLine("profile");
3019 cliPrintLinef("profile %d\r\n", getConfigProfile() + 1);
3020 dumpAllValues(PROFILE_VALUE, dumpMask);
3021 dumpAllValues(CONTROL_RATE_VALUE, dumpMask);
3024 static void cliBatteryProfile(char *cmdline)
3026 // CLI profile index is 1-based
3027 if (isEmpty(cmdline)) {
3028 cliPrintLinef("battery_profile %d", getConfigBatteryProfile() + 1);
3029 return;
3030 } else {
3031 const int i = fastA2I(cmdline) - 1;
3032 if (i >= 0 && i < MAX_PROFILE_COUNT) {
3033 setConfigBatteryProfileAndWriteEEPROM(i);
3034 cliBatteryProfile("");
3039 static void cliDumpBatteryProfile(uint8_t profileIndex, uint8_t dumpMask)
3041 if (profileIndex >= MAX_BATTERY_PROFILE_COUNT) {
3042 // Faulty values
3043 return;
3045 setConfigBatteryProfile(profileIndex);
3046 cliPrintHashLine("battery_profile");
3047 cliPrintLinef("battery_profile %d\r\n", getConfigBatteryProfile() + 1);
3048 dumpAllValues(BATTERY_CONFIG_VALUE, dumpMask);
3051 #ifdef USE_CLI_BATCH
3052 static void cliPrintCommandBatchWarning(const char *warning)
3054 cliPrintErrorLinef("ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
3055 if (warning) {
3056 cliPrintErrorLinef(warning);
3060 static void resetCommandBatch(void)
3062 commandBatchActive = false;
3063 commandBatchError = false;
3066 static void cliBatch(char *cmdline)
3068 if (strncasecmp(cmdline, "start", 5) == 0) {
3069 if (!commandBatchActive) {
3070 commandBatchActive = true;
3071 commandBatchError = false;
3073 cliPrintLine("Command batch started");
3074 } else if (strncasecmp(cmdline, "end", 3) == 0) {
3075 if (commandBatchActive && commandBatchError) {
3076 cliPrintCommandBatchWarning(NULL);
3077 } else {
3078 cliPrintLine("Command batch ended");
3080 resetCommandBatch();
3081 } else {
3082 cliPrintErrorLinef("Invalid option");
3085 #endif
3087 static void cliSave(char *cmdline)
3089 UNUSED(cmdline);
3091 #ifdef USE_CLI_BATCH
3092 if (commandBatchActive && commandBatchError) {
3093 cliPrintCommandBatchWarning("PLEASE FIX ERRORS THEN 'SAVE'");
3094 resetCommandBatch();
3095 return;
3097 #endif
3099 cliPrint("Saving");
3100 //copyCurrentProfileToProfileSlot(getConfigProfile();
3101 writeEEPROM();
3102 cliReboot();
3105 static void cliDefaults(char *cmdline)
3107 UNUSED(cmdline);
3109 cliPrint("Resetting to defaults");
3110 resetEEPROM();
3112 #ifdef USE_CLI_BATCH
3113 commandBatchError = false;
3114 #endif
3116 if (!checkCommand(cmdline, "noreboot"))
3117 cliReboot();
3120 static void cliGet(char *cmdline)
3122 const setting_t *val;
3123 int matchedCommands = 0;
3124 char name[SETTING_MAX_NAME_LENGTH];
3126 while(*cmdline == ' ') ++cmdline; // ignore spaces
3128 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3129 val = settingGet(i);
3130 if (settingNameContains(val, name, cmdline)) {
3131 cliPrintf("%s = ", name);
3132 if (strcmp(name, "name") == 0) {
3133 // if the craftname has a leading space, then enclose the name in quotes
3134 const char * v = (const char *)settingGetValuePointer(val);
3135 cliPrintf(v[0] == ' ' ? "\"%s\"" : "%s", v);
3136 } else {
3137 cliPrintVar(val, 0);
3139 cliPrintLinefeed();
3140 cliPrintVarRange(val);
3141 cliPrintLinefeed();
3143 matchedCommands++;
3148 if (matchedCommands) {
3149 return;
3152 cliPrintErrorLine("Invalid name");
3155 static void cliSet(char *cmdline)
3157 uint32_t len;
3158 const setting_t *val;
3159 char *eqptr = NULL;
3160 char name[SETTING_MAX_NAME_LENGTH];
3162 while(*cmdline == ' ') ++cmdline; // ignore spaces
3164 len = strlen(cmdline);
3166 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
3167 cliPrintLine("Current settings:");
3168 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3169 val = settingGet(i);
3170 settingGetName(val, name);
3171 cliPrintf("%s = ", name);
3172 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
3173 cliPrintLinefeed();
3175 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
3176 // has equals
3178 char *lastNonSpaceCharacter = eqptr;
3179 while (*(lastNonSpaceCharacter - 1) == ' ') {
3180 lastNonSpaceCharacter--;
3182 uint8_t variableNameLength = lastNonSpaceCharacter - cmdline;
3184 // skip the '=' and any ' ' characters
3185 eqptr++;
3186 while (*(eqptr) == ' ') {
3187 eqptr++;
3190 for (uint32_t i = 0; i < SETTINGS_TABLE_COUNT; i++) {
3191 val = settingGet(i);
3192 // ensure exact match when setting to prevent setting variables with shorter names
3193 if (settingNameIsExactMatch(val, name, cmdline, variableNameLength)) {
3194 const setting_type_e type = SETTING_TYPE(val);
3195 if (type == VAR_STRING) {
3196 // Convert strings to uppercase. Lower case is not supported by the OSD.
3197 sl_toupperptr(eqptr);
3198 // if setting the craftname, remove any quotes around the name. This allows leading spaces in the name
3199 if ((strcmp(name, "name") == 0 || strcmp(name, "pilot_name") == 0) && (eqptr[0] == '"' && eqptr[strlen(eqptr)-1] == '"')) {
3200 settingSetString(val, eqptr + 1, strlen(eqptr)-2);
3201 } else {
3202 settingSetString(val, eqptr, strlen(eqptr));
3204 return;
3206 const setting_mode_e mode = SETTING_MODE(val);
3207 bool changeValue = false;
3208 int_float_value_t tmp = {0};
3209 switch (mode) {
3210 case MODE_DIRECT: {
3211 if (*eqptr != 0 && strspn(eqptr, "0123456789.+-") == strlen(eqptr)) {
3212 float valuef = fastA2F(eqptr);
3213 // note: compare float values
3214 if (valuef >= (float)settingGetMin(val) && valuef <= (float)settingGetMax(val)) {
3216 if (type == VAR_FLOAT)
3217 tmp.float_value = valuef;
3218 else if (type == VAR_UINT32)
3219 tmp.uint_value = fastA2UL(eqptr);
3220 else
3221 tmp.int_value = fastA2I(eqptr);
3223 changeValue = true;
3227 break;
3228 case MODE_LOOKUP: {
3229 const lookupTableEntry_t *tableEntry = settingLookupTable(val);
3230 bool matched = false;
3231 for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
3232 matched = sl_strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
3234 if (matched) {
3235 tmp.int_value = tableValueIndex;
3236 changeValue = true;
3240 break;
3243 if (changeValue) {
3244 cliSetIntFloatVar(val, tmp);
3246 cliPrintf("%s set to ", name);
3247 cliPrintVar(val, 0);
3248 } else {
3249 cliPrintError("Invalid value. ");
3250 cliPrintVarRange(val);
3251 cliPrintLinefeed();
3254 return;
3257 cliPrintErrorLine("Invalid name");
3258 } else {
3259 // no equals, check for matching variables.
3260 cliGet(cmdline);
3264 static const char * getBatteryStateString(void)
3266 static const char * const batteryStateStrings[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
3268 return batteryStateStrings[getBatteryState()];
3271 static void cliStatus(char *cmdline)
3273 UNUSED(cmdline);
3275 char buf[MAX(FORMATTED_DATE_TIME_BUFSIZE, SETTING_MAX_NAME_LENGTH)];
3276 dateTime_t dt;
3278 cliPrintLinef("%s/%s %s %s / %s (%s)",
3279 FC_FIRMWARE_NAME,
3280 targetName,
3281 FC_VERSION_STRING,
3282 buildDate,
3283 buildTime,
3284 shortGitRevision
3286 cliPrintLinef("GCC-%s",
3287 compilerVersion
3289 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
3290 rtcGetDateTime(&dt);
3291 dateTimeFormatLocal(buf, &dt);
3292 cliPrintLinef("Current Time: %s", buf);
3293 cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
3294 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
3296 const uint32_t detectedSensorsMask = sensorsMask();
3298 for (int i = 0; i < SENSOR_INDEX_COUNT; i++) {
3300 const uint32_t mask = (1 << i);
3301 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
3302 const int sensorHardwareIndex = detectedSensors[i];
3303 if (sensorHardwareNames[i]) {
3304 const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
3305 cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
3309 cliPrintLinefeed();
3310 #if !defined(SITL_BUILD)
3311 #if defined(AT32F43x)
3312 cliPrintLine("AT32 system clocks:");
3313 crm_clocks_freq_type clocks;
3314 crm_clocks_freq_get(&clocks);
3316 cliPrintLinef(" SYSCLK = %d MHz", clocks.sclk_freq / 1000000);
3317 cliPrintLinef(" ABH = %d MHz", clocks.ahb_freq / 1000000);
3318 cliPrintLinef(" ABP1 = %d MHz", clocks.apb1_freq / 1000000);
3319 cliPrintLinef(" ABP2 = %d MHz", clocks.apb2_freq / 1000000);
3320 #else
3321 cliPrintLine("STM32 system clocks:");
3322 #if defined(USE_HAL_DRIVER)
3323 cliPrintLinef(" SYSCLK = %d MHz", HAL_RCC_GetSysClockFreq() / 1000000);
3324 cliPrintLinef(" HCLK = %d MHz", HAL_RCC_GetHCLKFreq() / 1000000);
3325 cliPrintLinef(" PCLK1 = %d MHz", HAL_RCC_GetPCLK1Freq() / 1000000);
3326 cliPrintLinef(" PCLK2 = %d MHz", HAL_RCC_GetPCLK2Freq() / 1000000);
3327 #else
3328 RCC_ClocksTypeDef clocks;
3329 RCC_GetClocksFreq(&clocks);
3330 cliPrintLinef(" SYSCLK = %d MHz", clocks.SYSCLK_Frequency / 1000000);
3331 cliPrintLinef(" HCLK = %d MHz", clocks.HCLK_Frequency / 1000000);
3332 cliPrintLinef(" PCLK1 = %d MHz", clocks.PCLK1_Frequency / 1000000);
3333 cliPrintLinef(" PCLK2 = %d MHz", clocks.PCLK2_Frequency / 1000000);
3334 #endif
3335 #endif // for if at32
3336 #endif // for SITL
3338 cliPrintLinef("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, RANGEFINDER=%s, OPFLOW=%s, GPS=%s",
3339 hardwareSensorStatusNames[getHwGyroStatus()],
3340 hardwareSensorStatusNames[getHwAccelerometerStatus()],
3341 hardwareSensorStatusNames[getHwCompassStatus()],
3342 hardwareSensorStatusNames[getHwBarometerStatus()],
3343 hardwareSensorStatusNames[getHwRangefinderStatus()],
3344 hardwareSensorStatusNames[getHwOpticalFlowStatus()],
3345 hardwareSensorStatusNames[getHwGPSStatus()]
3348 #ifdef USE_ESC_SENSOR
3349 uint8_t motorCount = getMotorCount();
3350 if (STATE(ESC_SENSOR_ENABLED) && motorCount > 0) {
3351 cliPrintLinef("ESC Temperature(s): Motor Count = %d", motorCount);
3352 for (uint8_t i = 0; i < motorCount; i++) {
3353 const escSensorData_t *escState = getEscTelemetry(i); //Get ESC telemetry
3354 cliPrintf("ESC %d: %d\260C, ", i, escState->temperature);
3356 cliPrintLinefeed();
3358 #endif
3360 #ifdef USE_SDCARD
3361 cliSdInfo(NULL);
3362 #endif
3363 #ifdef USE_I2C
3364 const uint16_t i2cErrorCounter = i2cGetErrorCounter();
3365 #elif !defined(SITL_BUILD)
3366 const uint16_t i2cErrorCounter = 0;
3367 #endif
3369 #ifdef STACK_CHECK
3370 cliPrintf("Stack used: %d, ", stackUsedSize());
3371 #endif
3372 #if !defined(SITL_BUILD)
3373 cliPrintLinef("Stack size: %d, Stack address: 0x%x, Heap available: %d", stackTotalSize(), stackHighMem(), memGetAvailableBytes());
3375 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
3376 #endif
3377 #if defined(USE_ADC) && !defined(SITL_BUILD)
3378 static char * adcFunctions[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
3379 cliPrintLine("ADC channel usage:");
3380 for (int i = 0; i < ADC_FUNCTION_COUNT; i++) {
3381 cliPrintf(" %8s :", adcFunctions[i]);
3383 cliPrint(" configured = ");
3384 if (adcChannelConfig()->adcFunctionChannel[i] == ADC_CHN_NONE) {
3385 cliPrint("none");
3387 else {
3388 cliPrintf("ADC %d", adcChannelConfig()->adcFunctionChannel[i]);
3391 cliPrint(", used = ");
3392 if (adcGetFunctionChannelAllocation(i) == ADC_CHN_NONE) {
3393 cliPrintLine("none");
3395 else {
3396 cliPrintLinef("ADC %d", adcGetFunctionChannelAllocation(i));
3399 #endif
3401 cliPrintf("System load: %d", averageSystemLoadPercent);
3402 const timeDelta_t pidTaskDeltaTime = getTaskDeltaTime(TASK_PID);
3403 const int pidRate = pidTaskDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)pidTaskDeltaTime));
3404 const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
3405 const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
3406 cliPrintLinef(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d", (uint16_t)cycleTime, pidRate, rxRate, systemRate);
3407 #if !defined(CLI_MINIMAL_VERBOSITY)
3408 cliPrint("Arming disabled flags:");
3409 uint32_t flags = armingFlags & ARMING_DISABLED_ALL_FLAGS;
3410 while (flags) {
3411 int bitpos = ffs(flags) - 1;
3412 flags &= ~(1 << bitpos);
3413 if (bitpos > 6) cliPrintf(" %s", armingDisableFlagNames[bitpos - 7]);
3415 cliPrintLinefeed();
3416 if (armingFlags & ARMING_DISABLED_INVALID_SETTING) {
3417 unsigned invalidIndex;
3418 if (!settingsValidate(&invalidIndex)) {
3419 settingGetName(settingGet(invalidIndex), buf);
3420 cliPrintErrorLinef("Invalid setting: %s", buf);
3423 #else
3424 cliPrintLinef("Arming disabled flags: 0x%lx", armingFlags & ARMING_DISABLED_ALL_FLAGS);
3425 #endif
3427 #if !defined(CLI_MINIMAL_VERBOSITY)
3428 cliPrint("OSD: ");
3429 #if defined(USE_OSD)
3430 displayPort_t *osdDisplayPort = osdGetDisplayPort();
3431 if (osdDisplayPort != NULL) {
3432 cliPrintf("%s [%u x %u]", osdDisplayPort->displayPortType, osdDisplayPort->cols, osdDisplayPort->rows);
3433 } else {
3434 cliPrint("not enabled");
3436 #else
3437 cliPrint("not used");
3438 #endif
3439 cliPrintLinefeed();
3441 cliPrint("VTX: ");
3442 #if defined(USE_VTX_CONTROL)
3443 if (vtxCommonDeviceIsReady(vtxCommonDevice())) {
3444 vtxDeviceOsdInfo_t osdInfo;
3445 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo);
3446 cliPrintf("band: %c, chan: %s, power: %c", osdInfo.bandLetter, osdInfo.channelName, osdInfo.powerIndexLetter);
3448 if (osdInfo.powerMilliwatt) {
3449 cliPrintf(" (%d mW)", osdInfo.powerMilliwatt);
3452 if (osdInfo.frequency) {
3453 cliPrintf(", freq: %d MHz", osdInfo.frequency);
3456 else {
3457 cliPrint("not detected");
3459 #else
3460 cliPrint("no VTX control");
3461 #endif
3463 cliPrintLinefeed();
3464 #endif
3466 // If we are blocked by PWM init - provide more information
3467 if (getPwmInitError() != PWM_INIT_ERROR_NONE) {
3468 cliPrintLinef("PWM output init error: %s", getPwmInitErrorMessage());
3472 static void cliTasks(char *cmdline)
3474 UNUSED(cmdline);
3475 int maxLoadSum = 0;
3476 int averageLoadSum = 0;
3477 cfCheckFuncInfo_t checkFuncInfo;
3479 cliPrintLinef("Task list rate/hz max/us avg/us maxload avgload total/ms");
3480 for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
3481 cfTaskInfo_t taskInfo;
3482 getTaskInfo(taskId, &taskInfo);
3483 if (taskInfo.isEnabled) {
3484 const int taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
3485 const int maxLoad = (taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
3486 const int averageLoad = (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
3487 if (taskId != TASK_SERIAL) {
3488 maxLoadSum += maxLoad;
3489 averageLoadSum += averageLoad;
3491 cliPrintLinef("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d",
3492 taskId, taskInfo.taskName, taskFrequency, (uint32_t)taskInfo.maxExecutionTime, (uint32_t)taskInfo.averageExecutionTime,
3493 maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, (uint32_t)taskInfo.totalExecutionTime / 1000);
3496 getCheckFuncInfo(&checkFuncInfo);
3497 cliPrintLinef("Task check function %13d %7d %25d", (uint32_t)checkFuncInfo.maxExecutionTime, (uint32_t)checkFuncInfo.averageExecutionTime, (uint32_t)checkFuncInfo.totalExecutionTime / 1000);
3498 cliPrintLinef("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
3501 static void cliVersion(char *cmdline)
3503 UNUSED(cmdline);
3505 cliPrintLinef("# %s/%s %s %s / %s (%s)",
3506 FC_FIRMWARE_NAME,
3507 targetName,
3508 FC_VERSION_STRING,
3509 buildDate,
3510 buildTime,
3511 shortGitRevision
3513 cliPrintLinef("# GCC-%s",
3514 compilerVersion
3518 static void cliMemory(char *cmdline)
3520 UNUSED(cmdline);
3521 cliPrintLinef("Dynamic memory usage:");
3522 for (unsigned i = 0; i < OWNER_TOTAL_COUNT; i++) {
3523 const char * owner = ownerNames[i];
3524 const uint32_t memUsed = memGetUsedBytesByOwner(i);
3526 if (memUsed) {
3527 cliPrintLinef("%s : %d bytes", owner, memUsed);
3532 static void cliResource(char *cmdline)
3534 UNUSED(cmdline);
3535 cliPrintLinef("IO:\r\n----------------------");
3536 for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
3537 const char* owner;
3538 owner = ownerNames[ioRecs[i].owner];
3540 const char* resource;
3541 resource = resourceNames[ioRecs[i].resource];
3543 if (ioRecs[i].index > 0) {
3544 cliPrintLinef("%c%02d: %s%d %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, ioRecs[i].index, resource);
3545 } else {
3546 cliPrintLinef("%c%02d: %s %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, resource);
3551 static void backupConfigs(void)
3553 // make copies of configs to do differencing
3554 PG_FOREACH(pg) {
3555 if (pgIsProfile(pg)) {
3556 memcpy(pg->copy, pg->address, pgSize(pg) * MAX_PROFILE_COUNT);
3557 } else {
3558 memcpy(pg->copy, pg->address, pgSize(pg));
3563 static void restoreConfigs(void)
3565 PG_FOREACH(pg) {
3566 if (pgIsProfile(pg)) {
3567 memcpy(pg->address, pg->copy, pgSize(pg) * MAX_PROFILE_COUNT);
3568 } else {
3569 memcpy(pg->address, pg->copy, pgSize(pg));
3574 static void printConfig(const char *cmdline, bool doDiff)
3576 uint8_t dumpMask = DUMP_MASTER;
3577 const char *options;
3578 if ((options = checkCommand(cmdline, "master"))) {
3579 dumpMask = DUMP_MASTER; // only
3580 } else if ((options = checkCommand(cmdline, "profile"))) {
3581 dumpMask = DUMP_PROFILE; // only
3582 } else if ((options = checkCommand(cmdline, "battery_profile"))) {
3583 dumpMask = DUMP_BATTERY_PROFILE; // only
3584 } else if ((options = checkCommand(cmdline, "all"))) {
3585 dumpMask = DUMP_ALL; // all profiles and rates
3586 } else {
3587 options = cmdline;
3590 if (doDiff) {
3591 dumpMask = dumpMask | DO_DIFF;
3594 const int currentProfileIndexSave = getConfigProfile();
3595 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
3596 backupConfigs();
3597 // reset all configs to defaults to do differencing
3598 resetConfigs();
3599 // restore the profile indices, since they should not be reset for proper comparison
3600 setConfigProfile(currentProfileIndexSave);
3601 setConfigBatteryProfile(currentBatteryProfileIndexSave);
3603 if (checkCommand(options, "showdefaults")) {
3604 dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
3607 #ifdef USE_CLI_BATCH
3608 bool batchModeEnabled = false;
3609 #endif
3611 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
3612 cliPrintHashLine("version");
3613 cliVersion(NULL);
3615 #ifdef USE_CLI_BATCH
3616 cliPrintHashLine("start the command batch");
3617 cliPrintLine("batch start");
3618 batchModeEnabled = true;
3619 #endif
3621 if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
3622 #ifndef CLI_MINIMAL_VERBOSITY
3623 cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
3624 #else
3625 cliPrintLinef("defaults noreboot");
3626 #endif
3629 cliPrintHashLine("resources");
3630 //printResource(dumpMask, &defaultConfig);
3632 cliPrintHashLine("Mixer: motor mixer");
3633 cliDumpPrintLinef(dumpMask, primaryMotorMixer_CopyArray[0].throttle == 0.0f, "\r\nmmix reset\r\n");
3635 printMotorMix(dumpMask, primaryMotorMixer_CopyArray, primaryMotorMixer(0));
3637 // print custom servo mixer if exists
3638 cliPrintHashLine("Mixer: servo mixer");
3639 cliDumpPrintLinef(dumpMask, customServoMixers_CopyArray[0].rate == 0, "smix reset\r\n");
3640 printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
3642 // print servo parameters
3643 cliPrintHashLine("Outputs [servo]");
3644 printServo(dumpMask, servoParams_CopyArray, servoParams(0));
3646 #if defined(USE_SAFE_HOME)
3647 cliPrintHashLine("safehome");
3648 printSafeHomes(dumpMask, safeHomeConfig_CopyArray, safeHomeConfig(0));
3649 #endif
3651 cliPrintHashLine("features");
3652 printFeature(dumpMask, &featureConfig_Copy, featureConfig());
3654 #if defined(BEEPER) || defined(USE_DSHOT)
3655 cliPrintHashLine("beeper");
3656 printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
3657 #endif
3659 #ifdef USE_BLACKBOX
3660 cliPrintHashLine("blackbox");
3661 printBlackbox(dumpMask, &blackboxConfig_Copy, blackboxConfig());
3662 #endif
3664 cliPrintHashLine("Receiver: Channel map");
3665 printMap(dumpMask, &rxConfig_Copy, rxConfig());
3667 cliPrintHashLine("Ports");
3668 printSerial(dumpMask, &serialConfig_Copy, serialConfig());
3670 #ifdef USE_LED_STRIP
3671 cliPrintHashLine("LEDs");
3672 printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
3674 cliPrintHashLine("LED color");
3675 printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
3677 cliPrintHashLine("LED mode_color");
3678 printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
3679 #endif
3681 cliPrintHashLine("Modes [aux]");
3682 printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
3684 cliPrintHashLine("Adjustments [adjrange]");
3685 printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
3687 cliPrintHashLine("Receiver rxrange");
3688 printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
3690 #ifdef USE_TEMPERATURE_SENSOR
3691 cliPrintHashLine("temp_sensor");
3692 printTempSensor(dumpMask, tempSensorConfig_CopyArray, tempSensorConfig(0));
3693 #endif
3695 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
3696 cliPrintHashLine("Mission Control Waypoints [wp]");
3697 printWaypoints(dumpMask, posControl.waypointList, nonVolatileWaypointList(0));
3698 #endif
3700 #ifdef USE_OSD
3701 cliPrintHashLine("OSD [osd_layout]");
3702 printOsdLayout(dumpMask, &osdLayoutsConfig_Copy, osdLayoutsConfig(), -1, -1);
3703 #endif
3705 #ifdef USE_PROGRAMMING_FRAMEWORK
3706 cliPrintHashLine("Programming: logic");
3707 printLogic(dumpMask, logicConditions_CopyArray, logicConditions(0), -1);
3709 cliPrintHashLine("Programming: global variables");
3710 printGvar(dumpMask, globalVariableConfigs_CopyArray, globalVariableConfigs(0));
3712 cliPrintHashLine("Programming: PID controllers");
3713 printPid(dumpMask, programmingPids_CopyArray, programmingPids(0));
3714 #endif
3716 cliPrintHashLine("master");
3717 dumpAllValues(MASTER_VALUE, dumpMask);
3719 if (dumpMask & DUMP_ALL) {
3720 // dump all profiles
3721 const int currentProfileIndexSave = getConfigProfile();
3722 const int currentBatteryProfileIndexSave = getConfigBatteryProfile();
3723 for (int ii = 0; ii < MAX_PROFILE_COUNT; ++ii) {
3724 cliDumpProfile(ii, dumpMask);
3726 for (int ii = 0; ii < MAX_BATTERY_PROFILE_COUNT; ++ii) {
3727 cliDumpBatteryProfile(ii, dumpMask);
3729 setConfigProfile(currentProfileIndexSave);
3730 setConfigBatteryProfile(currentBatteryProfileIndexSave);
3732 cliPrintHashLine("restore original profile selection");
3733 cliPrintLinef("profile %d", currentProfileIndexSave + 1);
3734 cliPrintLinef("battery_profile %d", currentBatteryProfileIndexSave + 1);
3736 #ifdef USE_CLI_BATCH
3737 batchModeEnabled = false;
3738 #endif
3739 } else {
3740 // dump just the current profiles
3741 cliDumpProfile(getConfigProfile(), dumpMask);
3742 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
3746 if (dumpMask & DUMP_PROFILE) {
3747 cliDumpProfile(getConfigProfile(), dumpMask);
3750 if (dumpMask & DUMP_BATTERY_PROFILE) {
3751 cliDumpBatteryProfile(getConfigBatteryProfile(), dumpMask);
3754 if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
3755 cliPrintHashLine("save configuration\r\nsave");
3758 #ifdef USE_CLI_BATCH
3759 if (batchModeEnabled) {
3760 cliPrintHashLine("end the command batch");
3761 cliPrintLine("batch end");
3763 #endif
3765 // restore configs from copies
3766 restoreConfigs();
3769 static void cliDump(char *cmdline)
3771 printConfig(cmdline, false);
3774 static void cliDiff(char *cmdline)
3776 printConfig(cmdline, true);
3779 #ifdef USE_USB_MSC
3780 static void cliMsc(char *cmdline)
3782 UNUSED(cmdline);
3784 if (false
3785 #ifdef USE_SDCARD
3786 || sdcard_isFunctional()
3787 #endif
3788 #ifdef USE_FLASHFS
3789 || flashfsGetSize() > 0
3790 #endif
3792 cliPrintHashLine("restarting in mass storage mode");
3793 cliPrint("\r\nRebooting");
3794 bufWriterFlush(cliWriter);
3795 delay(1000);
3796 waitForSerialPortToFinishTransmitting(cliPort);
3797 stopPwmAllMotors();
3798 systemResetRequest(RESET_MSC_REQUEST);
3799 } else {
3800 cliPrint("\r\nStorage not present or failed to initialize!");
3801 bufWriterFlush(cliWriter);
3804 #endif
3807 typedef struct {
3808 const char *name;
3809 #ifndef SKIP_CLI_COMMAND_HELP
3810 const char *description;
3811 const char *args;
3812 #endif
3813 void (*func)(char *cmdline);
3814 } clicmd_t;
3816 #ifndef SKIP_CLI_COMMAND_HELP
3817 #define CLI_COMMAND_DEF(name, description, args, method) \
3819 name , \
3820 description , \
3821 args , \
3822 method \
3824 #else
3825 #define CLI_COMMAND_DEF(name, description, args, method) \
3827 name, \
3828 method \
3830 #endif
3832 static void cliHelp(char *cmdline);
3834 // should be sorted a..z for bsearch()
3835 const clicmd_t cmdTable[] = {
3836 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
3837 #if defined(USE_ASSERT)
3838 CLI_COMMAND_DEF("assert", "", NULL, cliAssert),
3839 #endif
3840 CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
3841 #ifdef USE_CLI_BATCH
3842 CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
3843 #endif
3844 #if defined(BEEPER) || defined(USE_DSHOT)
3845 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3846 "\t<+|->[name]", cliBeeper),
3847 #endif
3848 #if defined (USE_SERIALRX_SRXL2)
3849 CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI or SRXL2", NULL, cliRxBind),
3850 #endif
3851 #if defined(USE_BOOTLOG)
3852 CLI_COMMAND_DEF("bootlog", "show boot events", NULL, cliBootlog),
3853 #endif
3854 #ifdef USE_LED_STRIP
3855 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
3856 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
3857 #endif
3858 CLI_COMMAND_DEF("cli_delay", "CLI Delay", "Delay in ms", cliDelay),
3859 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
3860 CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL, cliDfu),
3861 CLI_COMMAND_DEF("diff", "list configuration changes from default",
3862 "[master|battery_profile|profile|rates|all] {showdefaults}", cliDiff),
3863 CLI_COMMAND_DEF("dump", "dump configuration",
3864 "[master|battery_profile|profile|rates|all] {showdefaults}", cliDump),
3865 #ifdef USE_RX_ELERES
3866 CLI_COMMAND_DEF("eleres_bind", NULL, NULL, cliEleresBind),
3867 #endif // USE_RX_ELERES
3868 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
3869 CLI_COMMAND_DEF("feature", "configure features",
3870 "list\r\n"
3871 "\t<+|->[name]", cliFeature),
3872 #ifdef USE_BLACKBOX
3873 CLI_COMMAND_DEF("blackbox", "configure blackbox fields",
3874 "list\r\n"
3875 "\t<+|->[name]", cliBlackbox),
3876 #endif
3877 #ifdef USE_FLASHFS
3878 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
3879 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
3880 #ifdef USE_FLASH_TOOLS
3881 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
3882 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
3883 #endif
3884 #endif
3885 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
3886 #ifdef USE_GPS
3887 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
3888 #endif
3889 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
3890 #ifdef USE_LED_STRIP
3891 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
3892 #endif
3893 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
3894 CLI_COMMAND_DEF("memory", "view memory usage", NULL, cliMemory),
3895 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
3896 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
3897 #ifdef USE_USB_MSC
3898 CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
3899 #endif
3900 CLI_COMMAND_DEF("play_sound", NULL, "[<index>]\r\n", cliPlaySound),
3901 CLI_COMMAND_DEF("profile", "change profile",
3902 "[<index>]", cliProfile),
3903 CLI_COMMAND_DEF("battery_profile", "change battery profile",
3904 "[<index>]", cliBatteryProfile),
3905 CLI_COMMAND_DEF("resource", "view currently used resources", NULL, cliResource),
3906 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
3907 #if defined(USE_SAFE_HOME)
3908 CLI_COMMAND_DEF("safehome", "safe home list", NULL, cliSafeHomes),
3909 #endif
3910 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
3911 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
3912 #ifdef USE_SERIAL_PASSTHROUGH
3913 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough),
3914 #endif
3915 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
3916 #ifdef USE_PROGRAMMING_FRAMEWORK
3917 CLI_COMMAND_DEF("logic", "configure logic conditions",
3918 "<rule> <enabled> <activatorId> <operation> <operand A type> <operand A value> <operand B type> <operand B value> <flags>\r\n"
3919 "\treset\r\n", cliLogic),
3921 CLI_COMMAND_DEF("gvar", "configure global variables",
3922 "<gvar> <default> <min> <max>\r\n"
3923 "\treset\r\n", cliGvar),
3925 CLI_COMMAND_DEF("pid", "configurable PID controllers",
3926 "<#> <enabled> <setpoint type> <setpoint value> <measurement type> <measurement value> <P gain> <I gain> <D gain> <FF gain>\r\n"
3927 "\treset\r\n", cliPid),
3928 #endif
3929 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
3930 CLI_COMMAND_DEF("smix", "servo mixer",
3931 "<rule> <servo> <source> <rate> <speed> <conditionId>\r\n"
3932 "\treset\r\n", cliServoMix),
3933 #ifdef USE_SDCARD
3934 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
3935 #endif
3936 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
3937 CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
3938 #ifdef USE_TEMPERATURE_SENSOR
3939 CLI_COMMAND_DEF("temp_sensor", "change temp sensor settings", NULL, cliTempSensor),
3940 #endif
3941 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
3942 #if defined(NAV_NON_VOLATILE_WAYPOINT_STORAGE) && defined(NAV_NON_VOLATILE_WAYPOINT_CLI)
3943 CLI_COMMAND_DEF("wp", "waypoint list", NULL, cliWaypoints),
3944 #endif
3945 #ifdef USE_OSD
3946 CLI_COMMAND_DEF("osd_layout", "get or set the layout of OSD items", "[<layout> [<item> [<col> <row> [<visible>]]]]", cliOsdLayout),
3947 #endif
3950 static void cliHelp(char *cmdline)
3952 UNUSED(cmdline);
3954 for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
3955 cliPrint(cmdTable[i].name);
3956 #ifndef SKIP_CLI_COMMAND_HELP
3957 if (cmdTable[i].description) {
3958 cliPrintf(" - %s", cmdTable[i].description);
3960 if (cmdTable[i].args) {
3961 cliPrintf("\r\n\t%s", cmdTable[i].args);
3963 #endif
3964 cliPrintLinefeed();
3968 void cliProcess(void)
3970 if (!cliWriter) {
3971 return;
3974 // Be a little bit tricky. Flush the last inputs buffer, if any.
3975 bufWriterFlush(cliWriter);
3977 while (serialRxBytesWaiting(cliPort)) {
3978 uint8_t c = serialRead(cliPort);
3979 if (c == '\t' || c == '?') {
3980 // do tab completion
3981 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
3982 uint32_t i = bufferIndex;
3983 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
3984 if (bufferIndex && (sl_strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
3985 continue;
3986 if (!pstart)
3987 pstart = cmd;
3988 pend = cmd;
3990 if (pstart) { /* Buffer matches one or more commands */
3991 for (; ; bufferIndex++) {
3992 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
3993 break;
3994 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
3995 /* Unambiguous -- append a space */
3996 cliBuffer[bufferIndex++] = ' ';
3997 cliBuffer[bufferIndex] = '\0';
3998 break;
4000 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
4003 if (!bufferIndex || pstart != pend) {
4004 /* Print list of ambiguous matches */
4005 cliPrint("\r\033[K");
4006 for (cmd = pstart; cmd <= pend; cmd++) {
4007 cliPrint(cmd->name);
4008 cliWrite('\t');
4010 cliPrompt();
4011 i = 0; /* Redraw prompt */
4013 for (; i < bufferIndex; i++)
4014 cliWrite(cliBuffer[i]);
4015 } else if (!bufferIndex && c == 4) { // CTRL-D
4016 cliExit(cliBuffer);
4017 return;
4018 } else if (c == 12) { // NewPage / CTRL-L
4019 // clear screen
4020 cliPrint("\033[2J\033[1;1H");
4021 cliPrompt();
4022 } else if (bufferIndex && (c == '\n' || c == '\r')) {
4023 // enter pressed
4024 cliPrintLinefeed();
4026 // Strip comment starting with # from line
4027 char *p = cliBuffer;
4028 p = strchr(p, '#');
4029 if (NULL != p) {
4030 bufferIndex = (uint32_t)(p - cliBuffer);
4033 // Strip trailing whitespace
4034 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
4035 bufferIndex--;
4038 // Process non-empty lines
4039 if (bufferIndex > 0) {
4040 cliBuffer[bufferIndex] = 0; // null terminate
4042 const clicmd_t *cmd;
4043 for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
4044 if (!sl_strncasecmp(cliBuffer, cmd->name, strlen(cmd->name)) // command names match
4045 && !sl_isalnum((unsigned)cliBuffer[strlen(cmd->name)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
4046 break;
4048 if (cmd < cmdTable + ARRAYLEN(cmdTable))
4049 cmd->func(cliBuffer + strlen(cmd->name) + 1);
4050 else
4051 cliPrintError("Unknown command, try 'help'");
4052 bufferIndex = 0;
4055 ZERO_FARRAY(cliBuffer);
4057 // 'exit' will reset this flag, so we don't need to print prompt again
4058 if (!cliMode)
4059 return;
4061 cliPrompt();
4062 } else if (c == 127) {
4063 // backspace
4064 if (bufferIndex) {
4065 cliBuffer[--bufferIndex] = 0;
4066 cliPrint("\010 \010");
4068 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
4069 if (!bufferIndex && c == ' ')
4070 continue; // Ignore leading spaces
4071 cliBuffer[bufferIndex++] = c;
4072 cliWrite(c);
4077 void cliEnter(serialPort_t *serialPort)
4079 if (cliMode) {
4080 return;
4083 cliMode = true;
4084 cliPort = serialPort;
4085 setPrintfSerialPort(cliPort);
4086 cliWriter = bufWriterInit(cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufShim, serialPort);
4088 #ifndef CLI_MINIMAL_VERBOSITY
4089 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
4090 #else
4091 cliPrintLine("\r\nCLI");
4092 #endif
4093 cliPrompt();
4095 #ifdef USE_CLI_BATCH
4096 resetCommandBatch();
4097 #endif
4099 ENABLE_ARMING_FLAG(ARMING_DISABLED_CLI);
4102 void cliInit(const serialConfig_t *serialConfig)
4104 UNUSED(serialConfig);