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/>.
28 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
29 // signal that we're in cli mode
31 extern uint8_t __config_start
; // configured via linker script when building binaries.
32 extern uint8_t __config_end
;
36 #include "blackbox/blackbox.h"
38 #include "build/build_config.h"
39 #include "build/debug.h"
40 #include "build/version.h"
44 #include "common/axis.h"
45 #include "common/color.h"
46 #include "common/maths.h"
47 #include "common/printf.h"
48 #include "common/typeconversion.h"
49 #include "common/utils.h"
51 #include "config/config_eeprom.h"
52 #include "config/feature.h"
53 #include "config/parameter_group.h"
54 #include "config/parameter_group_ids.h"
56 #include "drivers/accgyro/accgyro.h"
57 #include "drivers/buf_writer.h"
58 #include "drivers/bus_i2c.h"
59 #include "drivers/compass/compass.h"
60 #include "drivers/display.h"
61 #include "drivers/dma.h"
62 #include "drivers/flash.h"
63 #include "drivers/io.h"
64 #include "drivers/io_impl.h"
65 #include "drivers/inverter.h"
66 #include "drivers/rx_pwm.h"
67 #include "drivers/sdcard.h"
68 #include "drivers/sensor.h"
69 #include "drivers/serial.h"
70 #include "drivers/serial_escserial.h"
71 #include "drivers/sonar_hcsr04.h"
72 #include "drivers/stack_check.h"
73 #include "drivers/system.h"
74 #include "drivers/time.h"
75 #include "drivers/timer.h"
76 #include "drivers/vcd.h"
77 #include "drivers/light_led.h"
79 #include "fc/settings.h"
81 #include "fc/config.h"
82 #include "fc/controlrate_profile.h"
83 #include "fc/fc_core.h"
84 #include "fc/rc_adjustments.h"
85 #include "fc/rc_controls.h"
86 #include "fc/runtime_config.h"
87 #include "fc/fc_msp.h"
89 #include "flight/altitude.h"
90 #include "flight/failsafe.h"
91 #include "flight/imu.h"
92 #include "flight/mixer.h"
93 #include "flight/navigation.h"
94 #include "flight/pid.h"
95 #include "flight/servos.h"
97 #include "io/asyncfatfs/asyncfatfs.h"
98 #include "io/beeper.h"
99 #include "io/flashfs.h"
100 #include "io/displayport_max7456.h"
101 #include "io/displayport_msp.h"
102 #include "io/gimbal.h"
104 #include "io/ledstrip.h"
106 #include "io/serial.h"
107 #include "io/vtx_rtc6705.h"
108 #include "io/vtx_control.h"
111 #include "rx/spektrum.h"
113 #include "scheduler/scheduler.h"
115 #include "sensors/acceleration.h"
116 #include "sensors/barometer.h"
117 #include "sensors/battery.h"
118 #include "sensors/boardalignment.h"
119 #include "sensors/compass.h"
120 #include "sensors/gyro.h"
121 #include "sensors/sensors.h"
123 #include "telemetry/frsky.h"
124 #include "telemetry/telemetry.h"
127 static serialPort_t
*cliPort
;
130 #define CLI_IN_BUFFER_SIZE 128
132 // Space required to set array parameters
133 #define CLI_IN_BUFFER_SIZE 256
135 #define CLI_OUT_BUFFER_SIZE 64
137 static bufWriter_t
*cliWriter
;
138 static uint8_t cliWriteBuffer
[sizeof(*cliWriter
) + CLI_OUT_BUFFER_SIZE
];
140 static char cliBuffer
[CLI_IN_BUFFER_SIZE
];
141 static uint32_t bufferIndex
= 0;
143 static bool configIsInCopy
= false;
145 static const char* const emptyName
= "-";
147 #ifndef USE_QUAD_MIXER_ONLY
148 // sync this with mixerMode_e
149 static const char * const mixerNames
[] = {
150 "TRI", "QUADP", "QUADX", "BI",
151 "GIMBAL", "Y6", "HEX6",
152 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
153 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
154 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
155 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
159 // sync this with features_e
160 static const char * const featureNames
[] = {
161 "RX_PPM", "VBAT", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
162 "SERVO_TILT", "SOFTSERIAL", "GPS", "FAILSAFE",
163 "SONAR", "TELEMETRY", "CURRENT_METER", "3D", "RX_PARALLEL_PWM",
164 "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
165 "UNUSED", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
166 "SDCARD", "VTX", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
169 // sync this with rxFailsafeChannelMode_e
170 static const char rxFailsafeModeCharacters
[] = "ahs";
172 static const rxFailsafeChannelMode_e rxFailsafeModesTable
[RX_FAILSAFE_TYPE_COUNT
][RX_FAILSAFE_MODE_COUNT
] = {
173 { RX_FAILSAFE_MODE_AUTO
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_INVALID
},
174 { RX_FAILSAFE_MODE_INVALID
, RX_FAILSAFE_MODE_HOLD
, RX_FAILSAFE_MODE_SET
}
177 #if defined(USE_SENSOR_NAMES)
178 // sync this with sensors_e
179 static const char * const sensorTypeNames
[] = {
180 "GYRO", "ACC", "BARO", "MAG", "SONAR", "GPS", "GPS+MAG", NULL
183 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG)
185 static const char * const *sensorHardwareNames
[] = {
186 lookupTableGyroHardware
, lookupTableAccHardware
, lookupTableBaroHardware
, lookupTableMagHardware
188 #endif // USE_SENSOR_NAMES
191 static const char *armingDisableFlagNames
[] = {
192 "NOGYRO", "FAILSAFE", "BOXFAILSAFE", "THROTTLE",
193 "ANGLE", "LOAD", "CALIB", "CLI", "CMS", "OSD", "BST"
197 static void cliPrint(const char *str
)
200 bufWriterAppend(cliWriter
, *str
++);
202 bufWriterFlush(cliWriter
);
205 static void cliPrintLinefeed()
210 static void cliPrintLine(const char *str
)
217 #define cliPrintHashLine(str)
219 static void cliPrintHashLine(const char *str
)
226 static void cliPutp(void *p
, char ch
)
228 bufWriterAppend(p
, ch
);
232 DUMP_MASTER
= (1 << 0),
233 DUMP_PROFILE
= (1 << 1),
234 DUMP_RATES
= (1 << 2),
237 SHOW_DEFAULTS
= (1 << 5),
238 HIDE_UNUSED
= (1 << 6)
241 static void cliPrintfva(const char *format
, va_list va
)
243 tfp_format(cliWriter
, cliPutp
, format
, va
);
244 bufWriterFlush(cliWriter
);
247 static void cliPrintLinefva(const char *format
, va_list va
)
249 tfp_format(cliWriter
, cliPutp
, format
, va
);
250 bufWriterFlush(cliWriter
);
254 static bool cliDumpPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
256 if (!((dumpMask
& DO_DIFF
) && equalsDefault
)) {
258 va_start(va
, format
);
259 cliPrintLinefva(format
, va
);
267 static void cliWrite(uint8_t ch
)
269 bufWriterAppend(cliWriter
, ch
);
272 static bool cliDefaultPrintLinef(uint8_t dumpMask
, bool equalsDefault
, const char *format
, ...)
274 if ((dumpMask
& SHOW_DEFAULTS
) && !equalsDefault
) {
278 va_start(va
, format
);
279 cliPrintLinefva(format
, va
);
287 static void cliPrintf(const char *format
, ...)
290 va_start(va
, format
);
291 cliPrintfva(format
, va
);
296 static void cliPrintLinef(const char *format
, ...)
299 va_start(va
, format
);
300 cliPrintLinefva(format
, va
);
304 static void printValuePointer(const clivalue_t
*var
, const void *valuePointer
, bool full
)
306 if ((var
->type
& VALUE_MODE_MASK
) == MODE_ARRAY
) {
307 for (int i
= 0; i
< var
->config
.array
.length
; i
++) {
308 uint8_t value
= ((uint8_t *)valuePointer
)[i
];
310 cliPrintf("%d", value
);
311 if (i
< var
->config
.array
.length
- 1) {
318 switch (var
->type
& VALUE_TYPE_MASK
) {
320 value
= *(uint8_t *)valuePointer
;
324 value
= *(int8_t *)valuePointer
;
329 value
= *(int16_t *)valuePointer
;
333 switch(var
->type
& VALUE_MODE_MASK
) {
335 cliPrintf("%d", value
);
337 cliPrintf(" %d %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
341 cliPrint(lookupTables
[var
->config
.lookup
.tableIndex
].values
[value
]);
347 static bool valuePtrEqualsDefault(uint8_t type
, const void *ptr
, const void *ptrDefault
)
350 switch (type
& VALUE_TYPE_MASK
) {
352 result
= *(uint8_t *)ptr
== *(uint8_t *)ptrDefault
;
356 result
= *(int8_t *)ptr
== *(int8_t *)ptrDefault
;
361 result
= *(int16_t *)ptr
== *(int16_t *)ptrDefault
;
368 static uint16_t getValueOffset(const clivalue_t
*value
)
370 switch (value
->type
& VALUE_SECTION_MASK
) {
372 return value
->offset
;
374 return value
->offset
+ sizeof(pidProfile_t
) * getCurrentPidProfileIndex();
375 case PROFILE_RATE_VALUE
:
376 return value
->offset
+ sizeof(controlRateConfig_t
) * getCurrentControlRateProfileIndex();
381 static void *getValuePointer(const clivalue_t
*value
)
383 const pgRegistry_t
* rec
= pgFind(value
->pgn
);
384 return CONST_CAST(void *, rec
->address
+ getValueOffset(value
));
387 static void dumpPgValue(const clivalue_t
*value
, uint8_t dumpMask
)
389 const pgRegistry_t
*pg
= pgFind(value
->pgn
);
392 cliPrintLinef("VALUE %s ERROR", value
->name
);
393 return; // if it's not found, the pgn shouldn't be in the value table!
397 const char *format
= "set %s = ";
398 const char *defaultFormat
= "#set %s = ";
399 const int valueOffset
= getValueOffset(value
);
400 const bool equalsDefault
= valuePtrEqualsDefault(value
->type
, pg
->copy
+ valueOffset
, pg
->address
+ valueOffset
);
402 if (((dumpMask
& DO_DIFF
) == 0) || !equalsDefault
) {
403 if (dumpMask
& SHOW_DEFAULTS
&& !equalsDefault
) {
404 cliPrintf(defaultFormat
, value
->name
);
405 printValuePointer(value
, (uint8_t*)pg
->address
+ valueOffset
, false);
408 cliPrintf(format
, value
->name
);
409 printValuePointer(value
, pg
->copy
+ valueOffset
, false);
414 static void dumpAllValues(uint16_t valueSection
, uint8_t dumpMask
)
416 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
417 const clivalue_t
*value
= &valueTable
[i
];
418 bufWriterFlush(cliWriter
);
419 if ((value
->type
& VALUE_SECTION_MASK
) == valueSection
) {
420 dumpPgValue(value
, dumpMask
);
425 static void cliPrintVar(const clivalue_t
*var
, bool full
)
427 const void *ptr
= getValuePointer(var
);
429 printValuePointer(var
, ptr
, full
);
432 static void cliPrintVarRange(const clivalue_t
*var
)
434 switch (var
->type
& VALUE_MODE_MASK
) {
435 case (MODE_DIRECT
): {
436 cliPrintLinef("Allowed range: %d - %d", var
->config
.minmax
.min
, var
->config
.minmax
.max
);
439 case (MODE_LOOKUP
): {
440 const lookupTableEntry_t
*tableEntry
= &lookupTables
[var
->config
.lookup
.tableIndex
];
441 cliPrint("Allowed values:");
442 for (uint32_t i
= 0; i
< tableEntry
->valueCount
; i
++) {
445 cliPrintf(" %s", tableEntry
->values
[i
]);
451 cliPrintLinef("Array length: %d", var
->config
.array
.length
);
458 static void cliSetVar(const clivalue_t
*var
, const int16_t value
)
460 void *ptr
= getValuePointer(var
);
462 switch (var
->type
& VALUE_TYPE_MASK
) {
464 *(uint8_t *)ptr
= value
;
468 *(int8_t *)ptr
= value
;
473 *(int16_t *)ptr
= value
;
478 #if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
479 static void cliRepeat(char ch
, uint8_t len
)
481 for (int i
= 0; i
< len
; i
++) {
482 bufWriterAppend(cliWriter
, ch
);
488 static void cliPrompt(void)
493 static void cliShowParseError(void)
495 cliPrintLine("Parse error");
498 static void cliShowArgumentRangeError(char *name
, int min
, int max
)
500 cliPrintLinef("%s not between %d and %d", name
, min
, max
);
503 static const char *nextArg(const char *currentArg
)
505 const char *ptr
= strchr(currentArg
, ' ');
506 while (ptr
&& *ptr
== ' ') {
513 static const char *processChannelRangeArgs(const char *ptr
, channelRange_t
*range
, uint8_t *validArgumentCount
)
515 for (uint32_t argIndex
= 0; argIndex
< 2; argIndex
++) {
519 val
= CHANNEL_VALUE_TO_STEP(val
);
520 if (val
>= MIN_MODE_RANGE_STEP
&& val
<= MAX_MODE_RANGE_STEP
) {
522 range
->startStep
= val
;
524 range
->endStep
= val
;
526 (*validArgumentCount
)++;
534 // Check if a string's length is zero
535 static bool isEmpty(const char *string
)
537 return (string
== NULL
|| *string
== '\0') ? true : false;
540 static void printRxFailsafe(uint8_t dumpMask
, const rxFailsafeChannelConfig_t
*rxFailsafeChannelConfigs
, const rxFailsafeChannelConfig_t
*defaultRxFailsafeChannelConfigs
)
542 // print out rxConfig failsafe settings
543 for (uint32_t channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
544 const rxFailsafeChannelConfig_t
*channelFailsafeConfig
= &rxFailsafeChannelConfigs
[channel
];
545 const rxFailsafeChannelConfig_t
*defaultChannelFailsafeConfig
= &defaultRxFailsafeChannelConfigs
[channel
];
546 const bool equalsDefault
= channelFailsafeConfig
->mode
== defaultChannelFailsafeConfig
->mode
547 && channelFailsafeConfig
->step
== defaultChannelFailsafeConfig
->step
;
548 const bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
550 const char *format
= "rxfail %u %c %d";
551 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
553 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
],
554 RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig
->step
)
556 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
558 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
],
559 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
562 const char *format
= "rxfail %u %c";
563 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
565 rxFailsafeModeCharacters
[defaultChannelFailsafeConfig
->mode
]
567 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
569 rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
]
575 static void cliRxFailsafe(char *cmdline
)
580 if (isEmpty(cmdline
)) {
581 // print out rxConfig failsafe settings
582 for (channel
= 0; channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; channel
++) {
583 cliRxFailsafe(itoa(channel
, buf
, 10));
586 const char *ptr
= cmdline
;
587 channel
= atoi(ptr
++);
588 if ((channel
< MAX_SUPPORTED_RC_CHANNEL_COUNT
)) {
590 rxFailsafeChannelConfig_t
*channelFailsafeConfig
= rxFailsafeChannelConfigsMutable(channel
);
592 const rxFailsafeChannelType_e type
= (channel
< NON_AUX_CHANNEL_COUNT
) ? RX_FAILSAFE_TYPE_FLIGHT
: RX_FAILSAFE_TYPE_AUX
;
593 rxFailsafeChannelMode_e mode
= channelFailsafeConfig
->mode
;
594 bool requireValue
= channelFailsafeConfig
->mode
== RX_FAILSAFE_MODE_SET
;
598 const char *p
= strchr(rxFailsafeModeCharacters
, *(ptr
));
600 const uint8_t requestedMode
= p
- rxFailsafeModeCharacters
;
601 mode
= rxFailsafeModesTable
[type
][requestedMode
];
603 mode
= RX_FAILSAFE_MODE_INVALID
;
605 if (mode
== RX_FAILSAFE_MODE_INVALID
) {
610 requireValue
= mode
== RX_FAILSAFE_MODE_SET
;
618 uint16_t value
= atoi(ptr
);
619 value
= CHANNEL_VALUE_TO_RXFAIL_STEP(value
);
620 if (value
> MAX_RXFAIL_RANGE_STEP
) {
621 cliPrintLine("Value out of range");
625 channelFailsafeConfig
->step
= value
;
626 } else if (requireValue
) {
630 channelFailsafeConfig
->mode
= mode
;
633 char modeCharacter
= rxFailsafeModeCharacters
[channelFailsafeConfig
->mode
];
635 // double use of cliPrintf below
636 // 1. acknowledge interpretation on command,
637 // 2. query current setting on single item,
640 cliPrintLinef("rxfail %u %c %d",
643 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig
->step
)
646 cliPrintLinef("rxfail %u %c",
652 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT
- 1);
657 static void printAux(uint8_t dumpMask
, const modeActivationCondition_t
*modeActivationConditions
, const modeActivationCondition_t
*defaultModeActivationConditions
)
659 const char *format
= "aux %u %u %u %u %u";
660 // print out aux channel settings
661 for (uint32_t i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
662 const modeActivationCondition_t
*mac
= &modeActivationConditions
[i
];
663 bool equalsDefault
= false;
664 if (defaultModeActivationConditions
) {
665 const modeActivationCondition_t
*macDefault
= &defaultModeActivationConditions
[i
];
666 equalsDefault
= mac
->modeId
== macDefault
->modeId
667 && mac
->auxChannelIndex
== macDefault
->auxChannelIndex
668 && mac
->range
.startStep
== macDefault
->range
.startStep
669 && mac
->range
.endStep
== macDefault
->range
.endStep
;
670 const box_t
*box
= findBoxByBoxId(macDefault
->modeId
);
672 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
675 macDefault
->auxChannelIndex
,
676 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.startStep
),
677 MODE_STEP_TO_CHANNEL_VALUE(macDefault
->range
.endStep
)
681 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
683 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
686 mac
->auxChannelIndex
,
687 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.startStep
),
688 MODE_STEP_TO_CHANNEL_VALUE(mac
->range
.endStep
)
694 static void cliAux(char *cmdline
)
699 if (isEmpty(cmdline
)) {
700 printAux(DUMP_MASTER
, modeActivationConditions(0), NULL
);
704 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
705 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
706 uint8_t validArgumentCount
= 0;
710 const box_t
*box
= findBoxByPermanentId(val
);
712 mac
->modeId
= box
->boxId
;
713 validArgumentCount
++;
719 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
720 mac
->auxChannelIndex
= val
;
721 validArgumentCount
++;
724 ptr
= processChannelRangeArgs(ptr
, &mac
->range
, &validArgumentCount
);
726 if (validArgumentCount
!= 4) {
727 memset(mac
, 0, sizeof(modeActivationCondition_t
));
730 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT
- 1);
735 static void printSerial(uint8_t dumpMask
, const serialConfig_t
*serialConfig
, const serialConfig_t
*serialConfigDefault
)
737 const char *format
= "serial %d %d %ld %ld %ld %ld";
738 for (uint32_t i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
739 if (!serialIsPortAvailable(serialConfig
->portConfigs
[i
].identifier
)) {
742 bool equalsDefault
= false;
743 if (serialConfigDefault
) {
744 equalsDefault
= serialConfig
->portConfigs
[i
].identifier
== serialConfigDefault
->portConfigs
[i
].identifier
745 && serialConfig
->portConfigs
[i
].functionMask
== serialConfigDefault
->portConfigs
[i
].functionMask
746 && serialConfig
->portConfigs
[i
].msp_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
747 && serialConfig
->portConfigs
[i
].gps_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
748 && serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
749 && serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
== serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
;
750 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
751 serialConfigDefault
->portConfigs
[i
].identifier
,
752 serialConfigDefault
->portConfigs
[i
].functionMask
,
753 baudRates
[serialConfigDefault
->portConfigs
[i
].msp_baudrateIndex
],
754 baudRates
[serialConfigDefault
->portConfigs
[i
].gps_baudrateIndex
],
755 baudRates
[serialConfigDefault
->portConfigs
[i
].telemetry_baudrateIndex
],
756 baudRates
[serialConfigDefault
->portConfigs
[i
].blackbox_baudrateIndex
]
759 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
760 serialConfig
->portConfigs
[i
].identifier
,
761 serialConfig
->portConfigs
[i
].functionMask
,
762 baudRates
[serialConfig
->portConfigs
[i
].msp_baudrateIndex
],
763 baudRates
[serialConfig
->portConfigs
[i
].gps_baudrateIndex
],
764 baudRates
[serialConfig
->portConfigs
[i
].telemetry_baudrateIndex
],
765 baudRates
[serialConfig
->portConfigs
[i
].blackbox_baudrateIndex
]
770 static void cliSerial(char *cmdline
)
772 if (isEmpty(cmdline
)) {
773 printSerial(DUMP_MASTER
, serialConfig(), NULL
);
776 serialPortConfig_t portConfig
;
777 memset(&portConfig
, 0 , sizeof(portConfig
));
779 serialPortConfig_t
*currentConfig
;
781 uint8_t validArgumentCount
= 0;
783 const char *ptr
= cmdline
;
785 int val
= atoi(ptr
++);
786 currentConfig
= serialFindPortConfiguration(val
);
788 portConfig
.identifier
= val
;
789 validArgumentCount
++;
795 portConfig
.functionMask
= val
& 0xFFFF;
796 validArgumentCount
++;
799 for (int i
= 0; i
< 4; i
++) {
807 uint8_t baudRateIndex
= lookupBaudRateIndex(val
);
808 if (baudRates
[baudRateIndex
] != (uint32_t) val
) {
814 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_1000000
) {
817 portConfig
.msp_baudrateIndex
= baudRateIndex
;
820 if (baudRateIndex
< BAUD_9600
|| baudRateIndex
> BAUD_115200
) {
823 portConfig
.gps_baudrateIndex
= baudRateIndex
;
826 if (baudRateIndex
!= BAUD_AUTO
&& baudRateIndex
> BAUD_115200
) {
829 portConfig
.telemetry_baudrateIndex
= baudRateIndex
;
832 if (baudRateIndex
< BAUD_19200
|| baudRateIndex
> BAUD_2470000
) {
835 portConfig
.blackbox_baudrateIndex
= baudRateIndex
;
839 validArgumentCount
++;
842 if (validArgumentCount
< 6) {
847 memcpy(currentConfig
, &portConfig
, sizeof(portConfig
));
850 #ifndef SKIP_SERIAL_PASSTHROUGH
851 static void cliSerialPassthrough(char *cmdline
)
853 if (isEmpty(cmdline
)) {
862 char* tok
= strtok_r(cmdline
, " ", &saveptr
);
865 while (tok
!= NULL
) {
874 if (strstr(tok
, "rx") || strstr(tok
, "RX"))
876 if (strstr(tok
, "tx") || strstr(tok
, "TX"))
881 tok
= strtok_r(NULL
, " ", &saveptr
);
884 tfp_printf("Port %d ", id
);
885 serialPort_t
*passThroughPort
;
886 serialPortUsage_t
*passThroughPortUsage
= findSerialPortUsageByIdentifier(id
);
887 if (!passThroughPortUsage
|| passThroughPortUsage
->serialPort
== NULL
) {
889 tfp_printf("closed, specify baud.\r\n");
895 passThroughPort
= openSerialPort(id
, FUNCTION_NONE
, NULL
,
897 SERIAL_NOT_INVERTED
);
898 if (!passThroughPort
) {
899 tfp_printf("could not be opened.\r\n");
902 tfp_printf("opened, baud = %d.\r\n", baud
);
904 passThroughPort
= passThroughPortUsage
->serialPort
;
905 // If the user supplied a mode, override the port's mode, otherwise
906 // leave the mode unchanged. serialPassthrough() handles one-way ports.
907 tfp_printf("already open.\r\n");
908 if (mode
&& passThroughPort
->mode
!= mode
) {
909 tfp_printf("mode changed from %d to %d.\r\n",
910 passThroughPort
->mode
, mode
);
911 serialSetMode(passThroughPort
, mode
);
913 // If this port has a rx callback associated we need to remove it now.
914 // Otherwise no data will be pushed in the serial port buffer!
915 if (passThroughPort
->rxCallback
) {
916 passThroughPort
->rxCallback
= 0;
920 tfp_printf("forwarding, power cycle to exit.\r\n");
922 serialPassthrough(cliPort
, passThroughPort
, NULL
, NULL
);
926 static void printAdjustmentRange(uint8_t dumpMask
, const adjustmentRange_t
*adjustmentRanges
, const adjustmentRange_t
*defaultAdjustmentRanges
)
928 const char *format
= "adjrange %u %u %u %u %u %u %u";
929 // print out adjustment ranges channel settings
930 for (uint32_t i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
931 const adjustmentRange_t
*ar
= &adjustmentRanges
[i
];
932 bool equalsDefault
= false;
933 if (defaultAdjustmentRanges
) {
934 const adjustmentRange_t
*arDefault
= &defaultAdjustmentRanges
[i
];
935 equalsDefault
= ar
->auxChannelIndex
== arDefault
->auxChannelIndex
936 && ar
->range
.startStep
== arDefault
->range
.startStep
937 && ar
->range
.endStep
== arDefault
->range
.endStep
938 && ar
->adjustmentFunction
== arDefault
->adjustmentFunction
939 && ar
->auxSwitchChannelIndex
== arDefault
->auxSwitchChannelIndex
940 && ar
->adjustmentIndex
== arDefault
->adjustmentIndex
;
941 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
943 arDefault
->adjustmentIndex
,
944 arDefault
->auxChannelIndex
,
945 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.startStep
),
946 MODE_STEP_TO_CHANNEL_VALUE(arDefault
->range
.endStep
),
947 arDefault
->adjustmentFunction
,
948 arDefault
->auxSwitchChannelIndex
951 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
955 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.startStep
),
956 MODE_STEP_TO_CHANNEL_VALUE(ar
->range
.endStep
),
957 ar
->adjustmentFunction
,
958 ar
->auxSwitchChannelIndex
963 static void cliAdjustmentRange(char *cmdline
)
968 if (isEmpty(cmdline
)) {
969 printAdjustmentRange(DUMP_MASTER
, adjustmentRanges(0), NULL
);
973 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
974 adjustmentRange_t
*ar
= adjustmentRangesMutable(i
);
975 uint8_t validArgumentCount
= 0;
980 if (val
>= 0 && val
< MAX_SIMULTANEOUS_ADJUSTMENT_COUNT
) {
981 ar
->adjustmentIndex
= val
;
982 validArgumentCount
++;
988 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
989 ar
->auxChannelIndex
= val
;
990 validArgumentCount
++;
994 ptr
= processChannelRangeArgs(ptr
, &ar
->range
, &validArgumentCount
);
999 if (val
>= 0 && val
< ADJUSTMENT_FUNCTION_COUNT
) {
1000 ar
->adjustmentFunction
= val
;
1001 validArgumentCount
++;
1007 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1008 ar
->auxSwitchChannelIndex
= val
;
1009 validArgumentCount
++;
1013 if (validArgumentCount
!= 6) {
1014 memset(ar
, 0, sizeof(adjustmentRange_t
));
1015 cliShowParseError();
1018 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT
- 1);
1023 #ifndef USE_QUAD_MIXER_ONLY
1024 static void printMotorMix(uint8_t dumpMask
, const motorMixer_t
*customMotorMixer
, const motorMixer_t
*defaultCustomMotorMixer
)
1026 const char *format
= "mmix %d %s %s %s %s";
1027 char buf0
[FTOA_BUFFER_LENGTH
];
1028 char buf1
[FTOA_BUFFER_LENGTH
];
1029 char buf2
[FTOA_BUFFER_LENGTH
];
1030 char buf3
[FTOA_BUFFER_LENGTH
];
1031 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1032 if (customMotorMixer
[i
].throttle
== 0.0f
)
1034 const float thr
= customMotorMixer
[i
].throttle
;
1035 const float roll
= customMotorMixer
[i
].roll
;
1036 const float pitch
= customMotorMixer
[i
].pitch
;
1037 const float yaw
= customMotorMixer
[i
].yaw
;
1038 bool equalsDefault
= false;
1039 if (defaultCustomMotorMixer
) {
1040 const float thrDefault
= defaultCustomMotorMixer
[i
].throttle
;
1041 const float rollDefault
= defaultCustomMotorMixer
[i
].roll
;
1042 const float pitchDefault
= defaultCustomMotorMixer
[i
].pitch
;
1043 const float yawDefault
= defaultCustomMotorMixer
[i
].yaw
;
1044 const bool equalsDefault
= thr
== thrDefault
&& roll
== rollDefault
&& pitch
== pitchDefault
&& yaw
== yawDefault
;
1046 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1048 ftoa(thrDefault
, buf0
),
1049 ftoa(rollDefault
, buf1
),
1050 ftoa(pitchDefault
, buf2
),
1051 ftoa(yawDefault
, buf3
));
1053 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1061 #endif // USE_QUAD_MIXER_ONLY
1063 static void cliMotorMix(char *cmdline
)
1065 #ifdef USE_QUAD_MIXER_ONLY
1072 if (isEmpty(cmdline
)) {
1073 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
);
1074 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1075 // erase custom mixer
1076 for (uint32_t i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1077 customMotorMixerMutable(i
)->throttle
= 0.0f
;
1079 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1080 ptr
= nextArg(cmdline
);
1083 for (uint32_t i
= 0; ; i
++) {
1084 if (mixerNames
[i
] == NULL
) {
1085 cliPrintLine("Invalid name");
1088 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1089 mixerLoadMix(i
, customMotorMixerMutable(0));
1090 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1098 uint32_t i
= atoi(ptr
); // get motor number
1099 if (i
< MAX_SUPPORTED_MOTORS
) {
1102 customMotorMixerMutable(i
)->throttle
= fastA2F(ptr
);
1107 customMotorMixerMutable(i
)->roll
= fastA2F(ptr
);
1112 customMotorMixerMutable(i
)->pitch
= fastA2F(ptr
);
1117 customMotorMixerMutable(i
)->yaw
= fastA2F(ptr
);
1121 cliShowParseError();
1123 printMotorMix(DUMP_MASTER
, customMotorMixer(0), NULL
);
1126 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
1132 static void printRxRange(uint8_t dumpMask
, const rxChannelRangeConfig_t
*channelRangeConfigs
, const rxChannelRangeConfig_t
*defaultChannelRangeConfigs
)
1134 const char *format
= "rxrange %u %u %u";
1135 for (uint32_t i
= 0; i
< NON_AUX_CHANNEL_COUNT
; i
++) {
1136 bool equalsDefault
= false;
1137 if (defaultChannelRangeConfigs
) {
1138 equalsDefault
= channelRangeConfigs
[i
].min
== defaultChannelRangeConfigs
[i
].min
1139 && channelRangeConfigs
[i
].max
== defaultChannelRangeConfigs
[i
].max
;
1140 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1142 defaultChannelRangeConfigs
[i
].min
,
1143 defaultChannelRangeConfigs
[i
].max
1146 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1148 channelRangeConfigs
[i
].min
,
1149 channelRangeConfigs
[i
].max
1154 static void cliRxRange(char *cmdline
)
1156 int i
, validArgumentCount
= 0;
1159 if (isEmpty(cmdline
)) {
1160 printRxRange(DUMP_MASTER
, rxChannelRangeConfigs(0), NULL
);
1161 } else if (strcasecmp(cmdline
, "reset") == 0) {
1162 resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
1166 if (i
>= 0 && i
< NON_AUX_CHANNEL_COUNT
) {
1167 int rangeMin
= 0, rangeMax
= 0;
1171 rangeMin
= atoi(ptr
);
1172 validArgumentCount
++;
1177 rangeMax
= atoi(ptr
);
1178 validArgumentCount
++;
1181 if (validArgumentCount
!= 2) {
1182 cliShowParseError();
1183 } else if (rangeMin
< PWM_PULSE_MIN
|| rangeMin
> PWM_PULSE_MAX
|| rangeMax
< PWM_PULSE_MIN
|| rangeMax
> PWM_PULSE_MAX
) {
1184 cliShowParseError();
1186 rxChannelRangeConfig_t
*channelRangeConfig
= rxChannelRangeConfigsMutable(i
);
1187 channelRangeConfig
->min
= rangeMin
;
1188 channelRangeConfig
->max
= rangeMax
;
1191 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT
- 1);
1197 static void printLed(uint8_t dumpMask
, const ledConfig_t
*ledConfigs
, const ledConfig_t
*defaultLedConfigs
)
1199 const char *format
= "led %u %s";
1200 char ledConfigBuffer
[20];
1201 char ledConfigDefaultBuffer
[20];
1202 for (uint32_t i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1203 ledConfig_t ledConfig
= ledConfigs
[i
];
1204 generateLedConfig(&ledConfig
, ledConfigBuffer
, sizeof(ledConfigBuffer
));
1205 bool equalsDefault
= false;
1206 if (defaultLedConfigs
) {
1207 ledConfig_t ledConfigDefault
= defaultLedConfigs
[i
];
1208 equalsDefault
= ledConfig
== ledConfigDefault
;
1209 generateLedConfig(&ledConfigDefault
, ledConfigDefaultBuffer
, sizeof(ledConfigDefaultBuffer
));
1210 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigDefaultBuffer
);
1212 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, ledConfigBuffer
);
1216 static void cliLed(char *cmdline
)
1221 if (isEmpty(cmdline
)) {
1222 printLed(DUMP_MASTER
, ledStripConfig()->ledConfigs
, NULL
);
1226 if (i
< LED_MAX_STRIP_LENGTH
) {
1227 ptr
= nextArg(cmdline
);
1228 if (!parseLedStripConfig(i
, ptr
)) {
1229 cliShowParseError();
1232 cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH
- 1);
1237 static void printColor(uint8_t dumpMask
, const hsvColor_t
*colors
, const hsvColor_t
*defaultColors
)
1239 const char *format
= "color %u %d,%u,%u";
1240 for (uint32_t i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1241 const hsvColor_t
*color
= &colors
[i
];
1242 bool equalsDefault
= false;
1243 if (defaultColors
) {
1244 const hsvColor_t
*colorDefault
= &defaultColors
[i
];
1245 equalsDefault
= color
->h
== colorDefault
->h
1246 && color
->s
== colorDefault
->s
1247 && color
->v
== colorDefault
->v
;
1248 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
,colorDefault
->h
, colorDefault
->s
, colorDefault
->v
);
1250 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, color
->h
, color
->s
, color
->v
);
1254 static void cliColor(char *cmdline
)
1256 if (isEmpty(cmdline
)) {
1257 printColor(DUMP_MASTER
, ledStripConfig()->colors
, NULL
);
1259 const char *ptr
= cmdline
;
1260 const int i
= atoi(ptr
);
1261 if (i
< LED_CONFIGURABLE_COLOR_COUNT
) {
1262 ptr
= nextArg(cmdline
);
1263 if (!parseColor(i
, ptr
)) {
1264 cliShowParseError();
1267 cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT
- 1);
1272 static void printModeColor(uint8_t dumpMask
, const ledStripConfig_t
*ledStripConfig
, const ledStripConfig_t
*defaultLedStripConfig
)
1274 const char *format
= "mode_color %u %u %u";
1275 for (uint32_t i
= 0; i
< LED_MODE_COUNT
; i
++) {
1276 for (uint32_t j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1277 int colorIndex
= ledStripConfig
->modeColors
[i
].color
[j
];
1278 bool equalsDefault
= false;
1279 if (defaultLedStripConfig
) {
1280 int colorIndexDefault
= defaultLedStripConfig
->modeColors
[i
].color
[j
];
1281 equalsDefault
= colorIndex
== colorIndexDefault
;
1282 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndexDefault
);
1284 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, j
, colorIndex
);
1288 for (uint32_t j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1289 const int colorIndex
= ledStripConfig
->specialColors
.color
[j
];
1290 bool equalsDefault
= false;
1291 if (defaultLedStripConfig
) {
1292 const int colorIndexDefault
= defaultLedStripConfig
->specialColors
.color
[j
];
1293 equalsDefault
= colorIndex
== colorIndexDefault
;
1294 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndexDefault
);
1296 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_SPECIAL
, j
, colorIndex
);
1299 const int ledStripAuxChannel
= ledStripConfig
->ledstrip_aux_channel
;
1300 bool equalsDefault
= false;
1301 if (defaultLedStripConfig
) {
1302 const int ledStripAuxChannelDefault
= defaultLedStripConfig
->ledstrip_aux_channel
;
1303 equalsDefault
= ledStripAuxChannel
== ledStripAuxChannelDefault
;
1304 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannelDefault
);
1306 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, LED_AUX_CHANNEL
, 0, ledStripAuxChannel
);
1309 static void cliModeColor(char *cmdline
)
1311 if (isEmpty(cmdline
)) {
1312 printModeColor(DUMP_MASTER
, ledStripConfig(), NULL
);
1314 enum {MODE
= 0, FUNCTION
, COLOR
, ARGS_COUNT
};
1315 int args
[ARGS_COUNT
];
1318 const char* ptr
= strtok_r(cmdline
, " ", &saveptr
);
1319 while (ptr
&& argNo
< ARGS_COUNT
) {
1320 args
[argNo
++] = atoi(ptr
);
1321 ptr
= strtok_r(NULL
, " ", &saveptr
);
1324 if (ptr
!= NULL
|| argNo
!= ARGS_COUNT
) {
1325 cliShowParseError();
1329 int modeIdx
= args
[MODE
];
1330 int funIdx
= args
[FUNCTION
];
1331 int color
= args
[COLOR
];
1332 if(!setModeColor(modeIdx
, funIdx
, color
)) {
1333 cliShowParseError();
1336 // values are validated
1337 cliPrintLinef("mode_color %u %u %u", modeIdx
, funIdx
, color
);
1343 static void printServo(uint8_t dumpMask
, const servoParam_t
*servoParams
, const servoParam_t
*defaultServoParams
)
1345 // print out servo settings
1346 const char *format
= "servo %u %d %d %d %d %d";
1347 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1348 const servoParam_t
*servoConf
= &servoParams
[i
];
1349 bool equalsDefault
= false;
1350 if (defaultServoParams
) {
1351 const servoParam_t
*defaultServoConf
= &defaultServoParams
[i
];
1352 equalsDefault
= servoConf
->min
== defaultServoConf
->min
1353 && servoConf
->max
== defaultServoConf
->max
1354 && servoConf
->middle
== defaultServoConf
->middle
1355 && servoConf
->rate
== defaultServoConf
->rate
1356 && servoConf
->forwardFromChannel
== defaultServoConf
->forwardFromChannel
;
1357 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1359 defaultServoConf
->min
,
1360 defaultServoConf
->max
,
1361 defaultServoConf
->middle
,
1362 defaultServoConf
->rate
,
1363 defaultServoConf
->forwardFromChannel
1366 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1372 servoConf
->forwardFromChannel
1375 // print servo directions
1376 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1377 const char *format
= "smix reverse %d %d r";
1378 const servoParam_t
*servoConf
= &servoParams
[i
];
1379 const servoParam_t
*servoConfDefault
= &defaultServoParams
[i
];
1380 if (defaultServoParams
) {
1381 bool equalsDefault
= servoConf
->reversedSources
== servoConfDefault
->reversedSources
;
1382 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1383 equalsDefault
= ~(servoConf
->reversedSources
^ servoConfDefault
->reversedSources
) & (1 << channel
);
1384 if (servoConfDefault
->reversedSources
& (1 << channel
)) {
1385 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1387 if (servoConf
->reversedSources
& (1 << channel
)) {
1388 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, i
, channel
);
1392 for (uint32_t channel
= 0; channel
< INPUT_SOURCE_COUNT
; channel
++) {
1393 if (servoConf
->reversedSources
& (1 << channel
)) {
1394 cliDumpPrintLinef(dumpMask
, true, format
, i
, channel
);
1401 static void cliServo(char *cmdline
)
1403 enum { SERVO_ARGUMENT_COUNT
= 6 };
1404 int16_t arguments
[SERVO_ARGUMENT_COUNT
];
1406 servoParam_t
*servo
;
1411 if (isEmpty(cmdline
)) {
1412 printServo(DUMP_MASTER
, servoParams(0), NULL
);
1414 int validArgumentCount
= 0;
1418 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1420 // If command line doesn't fit the format, don't modify the config
1422 if (*ptr
== '-' || (*ptr
>= '0' && *ptr
<= '9')) {
1423 if (validArgumentCount
>= SERVO_ARGUMENT_COUNT
) {
1424 cliShowParseError();
1428 arguments
[validArgumentCount
++] = atoi(ptr
);
1432 } while (*ptr
>= '0' && *ptr
<= '9');
1433 } else if (*ptr
== ' ') {
1436 cliShowParseError();
1441 enum {INDEX
= 0, MIN
, MAX
, MIDDLE
, RATE
, FORWARD
};
1443 i
= arguments
[INDEX
];
1445 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1446 if (validArgumentCount
!= SERVO_ARGUMENT_COUNT
|| i
< 0 || i
>= MAX_SUPPORTED_SERVOS
) {
1447 cliShowParseError();
1451 servo
= servoParamsMutable(i
);
1454 arguments
[MIN
] < PWM_PULSE_MIN
|| arguments
[MIN
] > PWM_PULSE_MAX
||
1455 arguments
[MAX
] < PWM_PULSE_MIN
|| arguments
[MAX
] > PWM_PULSE_MAX
||
1456 arguments
[MIDDLE
] < arguments
[MIN
] || arguments
[MIDDLE
] > arguments
[MAX
] ||
1457 arguments
[MIN
] > arguments
[MAX
] || arguments
[MAX
] < arguments
[MIN
] ||
1458 arguments
[RATE
] < -100 || arguments
[RATE
] > 100 ||
1459 arguments
[FORWARD
] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
1461 cliShowParseError();
1465 servo
->min
= arguments
[MIN
];
1466 servo
->max
= arguments
[MAX
];
1467 servo
->middle
= arguments
[MIDDLE
];
1468 servo
->rate
= arguments
[RATE
];
1469 servo
->forwardFromChannel
= arguments
[FORWARD
];
1475 static void printServoMix(uint8_t dumpMask
, const servoMixer_t
*customServoMixers
, const servoMixer_t
*defaultCustomServoMixers
)
1477 const char *format
= "smix %d %d %d %d %d %d %d %d";
1478 for (uint32_t i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1479 const servoMixer_t customServoMixer
= customServoMixers
[i
];
1480 if (customServoMixer
.rate
== 0) {
1484 bool equalsDefault
= false;
1485 if (defaultCustomServoMixers
) {
1486 servoMixer_t customServoMixerDefault
= defaultCustomServoMixers
[i
];
1487 equalsDefault
= customServoMixer
.targetChannel
== customServoMixerDefault
.targetChannel
1488 && customServoMixer
.inputSource
== customServoMixerDefault
.inputSource
1489 && customServoMixer
.rate
== customServoMixerDefault
.rate
1490 && customServoMixer
.speed
== customServoMixerDefault
.speed
1491 && customServoMixer
.min
== customServoMixerDefault
.min
1492 && customServoMixer
.max
== customServoMixerDefault
.max
1493 && customServoMixer
.box
== customServoMixerDefault
.box
;
1495 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1497 customServoMixerDefault
.targetChannel
,
1498 customServoMixerDefault
.inputSource
,
1499 customServoMixerDefault
.rate
,
1500 customServoMixerDefault
.speed
,
1501 customServoMixerDefault
.min
,
1502 customServoMixerDefault
.max
,
1503 customServoMixerDefault
.box
1506 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1508 customServoMixer
.targetChannel
,
1509 customServoMixer
.inputSource
,
1510 customServoMixer
.rate
,
1511 customServoMixer
.speed
,
1512 customServoMixer
.min
,
1513 customServoMixer
.max
,
1514 customServoMixer
.box
1521 static void cliServoMix(char *cmdline
)
1523 int args
[8], check
= 0;
1524 int len
= strlen(cmdline
);
1527 printServoMix(DUMP_MASTER
, customServoMixers(0), NULL
);
1528 } else if (strncasecmp(cmdline
, "reset", 5) == 0) {
1529 // erase custom mixer
1530 memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
1531 for (uint32_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1532 servoParamsMutable(i
)->reversedSources
= 0;
1534 } else if (strncasecmp(cmdline
, "load", 4) == 0) {
1535 const char *ptr
= nextArg(cmdline
);
1538 for (uint32_t i
= 0; ; i
++) {
1539 if (mixerNames
[i
] == NULL
) {
1540 cliPrintLine("Invalid name");
1543 if (strncasecmp(ptr
, mixerNames
[i
], len
) == 0) {
1544 servoMixerLoadMix(i
);
1545 cliPrintLinef("Loaded %s", mixerNames
[i
]);
1551 } else if (strncasecmp(cmdline
, "reverse", 7) == 0) {
1552 enum {SERVO
= 0, INPUT
, REVERSE
, ARGS_COUNT
};
1553 char *ptr
= strchr(cmdline
, ' ');
1558 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
1559 cliPrintf("\ti%d", inputSource
);
1562 for (uint32_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
1563 cliPrintf("%d", servoIndex
);
1564 for (uint32_t inputSource
= 0; inputSource
< INPUT_SOURCE_COUNT
; inputSource
++)
1565 cliPrintf("\t%s ", (servoParams(servoIndex
)->reversedSources
& (1 << inputSource
)) ? "r" : "n");
1572 ptr
= strtok_r(ptr
, " ", &saveptr
);
1573 while (ptr
!= NULL
&& check
< ARGS_COUNT
- 1) {
1574 args
[check
++] = atoi(ptr
);
1575 ptr
= strtok_r(NULL
, " ", &saveptr
);
1578 if (ptr
== NULL
|| check
!= ARGS_COUNT
- 1) {
1579 cliShowParseError();
1583 if (args
[SERVO
] >= 0 && args
[SERVO
] < MAX_SUPPORTED_SERVOS
1584 && args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
1585 && (*ptr
== 'r' || *ptr
== 'n')) {
1587 servoParamsMutable(args
[SERVO
])->reversedSources
|= 1 << args
[INPUT
];
1589 servoParamsMutable(args
[SERVO
])->reversedSources
&= ~(1 << args
[INPUT
]);
1591 cliShowParseError();
1593 cliServoMix("reverse");
1595 enum {RULE
= 0, TARGET
, INPUT
, RATE
, SPEED
, MIN
, MAX
, BOX
, ARGS_COUNT
};
1597 char *ptr
= strtok_r(cmdline
, " ", &saveptr
);
1598 while (ptr
!= NULL
&& check
< ARGS_COUNT
) {
1599 args
[check
++] = atoi(ptr
);
1600 ptr
= strtok_r(NULL
, " ", &saveptr
);
1603 if (ptr
!= NULL
|| check
!= ARGS_COUNT
) {
1604 cliShowParseError();
1608 int32_t i
= args
[RULE
];
1609 if (i
>= 0 && i
< MAX_SERVO_RULES
&&
1610 args
[TARGET
] >= 0 && args
[TARGET
] < MAX_SUPPORTED_SERVOS
&&
1611 args
[INPUT
] >= 0 && args
[INPUT
] < INPUT_SOURCE_COUNT
&&
1612 args
[RATE
] >= -100 && args
[RATE
] <= 100 &&
1613 args
[SPEED
] >= 0 && args
[SPEED
] <= MAX_SERVO_SPEED
&&
1614 args
[MIN
] >= 0 && args
[MIN
] <= 100 &&
1615 args
[MAX
] >= 0 && args
[MAX
] <= 100 && args
[MIN
] < args
[MAX
] &&
1616 args
[BOX
] >= 0 && args
[BOX
] <= MAX_SERVO_BOXES
) {
1617 customServoMixersMutable(i
)->targetChannel
= args
[TARGET
];
1618 customServoMixersMutable(i
)->inputSource
= args
[INPUT
];
1619 customServoMixersMutable(i
)->rate
= args
[RATE
];
1620 customServoMixersMutable(i
)->speed
= args
[SPEED
];
1621 customServoMixersMutable(i
)->min
= args
[MIN
];
1622 customServoMixersMutable(i
)->max
= args
[MAX
];
1623 customServoMixersMutable(i
)->box
= args
[BOX
];
1626 cliShowParseError();
1634 static void cliWriteBytes(const uint8_t *buffer
, int count
)
1643 static void cliSdInfo(char *cmdline
)
1647 cliPrint("SD card: ");
1649 if (!sdcard_isInserted()) {
1650 cliPrintLine("None inserted");
1654 if (!sdcard_isInitialized()) {
1655 cliPrintLine("Startup failed");
1659 const sdcardMetadata_t
*metadata
= sdcard_getMetadata();
1661 cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
1662 metadata
->manufacturerID
,
1663 metadata
->numBlocks
/ 2, /* One block is half a kB */
1664 metadata
->productionMonth
,
1665 metadata
->productionYear
,
1666 metadata
->productRevisionMajor
,
1667 metadata
->productRevisionMinor
1670 cliWriteBytes((uint8_t*)metadata
->productName
, sizeof(metadata
->productName
));
1672 cliPrint("'\r\n" "Filesystem: ");
1674 switch (afatfs_getFilesystemState()) {
1675 case AFATFS_FILESYSTEM_STATE_READY
:
1678 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
1679 cliPrint("Initializing");
1681 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
1682 case AFATFS_FILESYSTEM_STATE_FATAL
:
1685 switch (afatfs_getLastError()) {
1686 case AFATFS_ERROR_BAD_MBR
:
1687 cliPrint(" - no FAT MBR partitions");
1689 case AFATFS_ERROR_BAD_FILESYSTEM_HEADER
:
1690 cliPrint(" - bad FAT header");
1692 case AFATFS_ERROR_GENERIC
:
1693 case AFATFS_ERROR_NONE
:
1694 ; // Nothing more detailed to print
1706 static void cliFlashInfo(char *cmdline
)
1708 const flashGeometry_t
*layout
= flashfsGetGeometry();
1712 cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
1713 layout
->sectors
, layout
->sectorSize
, layout
->pagesPerSector
, layout
->pageSize
, layout
->totalSize
, flashfsGetOffset());
1717 static void cliFlashErase(char *cmdline
)
1723 cliPrintLine("Erasing, please wait ... ");
1725 cliPrintLine("Erasing,");
1728 bufWriterFlush(cliWriter
);
1729 flashfsEraseCompletely();
1731 while (!flashfsIsReady()) {
1739 bufWriterFlush(cliWriter
);
1743 beeper(BEEPER_BLACKBOX_ERASE
);
1745 cliPrintLine("Done.");
1748 #ifdef USE_FLASH_TOOLS
1750 static void cliFlashWrite(char *cmdline
)
1752 const uint32_t address
= atoi(cmdline
);
1753 const char *text
= strchr(cmdline
, ' ');
1756 cliShowParseError();
1758 flashfsSeekAbs(address
);
1759 flashfsWrite((uint8_t*)text
, strlen(text
), true);
1762 cliPrintLinef("Wrote %u bytes at %u.", strlen(text
), address
);
1766 static void cliFlashRead(char *cmdline
)
1768 uint32_t address
= atoi(cmdline
);
1770 const char *nextArg
= strchr(cmdline
, ' ');
1773 cliShowParseError();
1775 uint32_t length
= atoi(nextArg
);
1777 cliPrintLinef("Reading %u bytes at %u:", length
, address
);
1780 while (length
> 0) {
1781 int bytesRead
= flashfsReadAbs(address
, buffer
, length
< sizeof(buffer
) ? length
: sizeof(buffer
));
1783 for (int i
= 0; i
< bytesRead
; i
++) {
1784 cliWrite(buffer
[i
]);
1787 length
-= bytesRead
;
1788 address
+= bytesRead
;
1790 if (bytesRead
== 0) {
1791 //Assume we reached the end of the volume or something fatal happened
1803 static void printVtx(uint8_t dumpMask
, const vtxConfig_t
*vtxConfig
, const vtxConfig_t
*vtxConfigDefault
)
1805 // print out vtx channel settings
1806 const char *format
= "vtx %u %u %u %u %u %u";
1807 bool equalsDefault
= false;
1808 for (uint32_t i
= 0; i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
; i
++) {
1809 const vtxChannelActivationCondition_t
*cac
= &vtxConfig
->vtxChannelActivationConditions
[i
];
1810 if (vtxConfigDefault
) {
1811 const vtxChannelActivationCondition_t
*cacDefault
= &vtxConfigDefault
->vtxChannelActivationConditions
[i
];
1812 equalsDefault
= cac
->auxChannelIndex
== cacDefault
->auxChannelIndex
1813 && cac
->band
== cacDefault
->band
1814 && cac
->channel
== cacDefault
->channel
1815 && cac
->range
.startStep
== cacDefault
->range
.startStep
1816 && cac
->range
.endStep
== cacDefault
->range
.endStep
;
1817 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
,
1819 cacDefault
->auxChannelIndex
,
1821 cacDefault
->channel
,
1822 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.startStep
),
1823 MODE_STEP_TO_CHANNEL_VALUE(cacDefault
->range
.endStep
)
1826 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
,
1828 cac
->auxChannelIndex
,
1831 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.startStep
),
1832 MODE_STEP_TO_CHANNEL_VALUE(cac
->range
.endStep
)
1837 // FIXME remove these and use the VTX API
1838 #define VTX_BAND_MIN 1
1839 #define VTX_BAND_MAX 5
1840 #define VTX_CHANNEL_MIN 1
1841 #define VTX_CHANNEL_MAX 8
1843 static void cliVtx(char *cmdline
)
1848 if (isEmpty(cmdline
)) {
1849 printVtx(DUMP_MASTER
, vtxConfig(), NULL
);
1853 if (i
< MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
) {
1854 vtxChannelActivationCondition_t
*cac
= &vtxConfigMutable()->vtxChannelActivationConditions
[i
];
1855 uint8_t validArgumentCount
= 0;
1859 if (val
>= 0 && val
< MAX_AUX_CHANNEL_COUNT
) {
1860 cac
->auxChannelIndex
= val
;
1861 validArgumentCount
++;
1867 // FIXME Use VTX API to get min/max
1868 if (val
>= VTX_BAND_MIN
&& val
<= VTX_BAND_MAX
) {
1870 validArgumentCount
++;
1876 // FIXME Use VTX API to get min/max
1877 if (val
>= VTX_CHANNEL_MIN
&& val
<= VTX_CHANNEL_MAX
) {
1879 validArgumentCount
++;
1882 ptr
= processChannelRangeArgs(ptr
, &cac
->range
, &validArgumentCount
);
1884 if (validArgumentCount
!= 5) {
1885 memset(cac
, 0, sizeof(vtxChannelActivationCondition_t
));
1888 cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT
- 1);
1893 #endif // VTX_CONTROL
1895 static void printName(uint8_t dumpMask
, const systemConfig_t
*systemConfig
)
1897 const bool equalsDefault
= strlen(systemConfig
->name
) == 0;
1898 cliDumpPrintLinef(dumpMask
, equalsDefault
, "name %s", equalsDefault
? emptyName
: systemConfig
->name
);
1901 static void cliName(char *cmdline
)
1903 const uint32_t len
= strlen(cmdline
);
1905 memset(systemConfigMutable()->name
, 0, ARRAYLEN(systemConfig()->name
));
1906 if (strncmp(cmdline
, emptyName
, len
)) {
1907 strncpy(systemConfigMutable()->name
, cmdline
, MIN(len
, MAX_NAME_LENGTH
));
1910 printName(DUMP_MASTER
, systemConfig());
1913 static void printFeature(uint8_t dumpMask
, const featureConfig_t
*featureConfig
, const featureConfig_t
*featureConfigDefault
)
1915 const uint32_t mask
= featureConfig
->enabledFeatures
;
1916 const uint32_t defaultMask
= featureConfigDefault
->enabledFeatures
;
1917 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // disable all feature first
1918 const char *format
= "feature -%s";
1919 cliDefaultPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
1920 cliDumpPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
1922 for (uint32_t i
= 0; featureNames
[i
]; i
++) { // reenable what we want.
1923 const char *format
= "feature %s";
1924 if (defaultMask
& (1 << i
)) {
1925 cliDefaultPrintLinef(dumpMask
, (~defaultMask
| mask
) & (1 << i
), format
, featureNames
[i
]);
1927 if (mask
& (1 << i
)) {
1928 cliDumpPrintLinef(dumpMask
, (defaultMask
| ~mask
) & (1 << i
), format
, featureNames
[i
]);
1933 static void cliFeature(char *cmdline
)
1935 uint32_t len
= strlen(cmdline
);
1936 uint32_t mask
= featureMask();
1939 cliPrint("Enabled: ");
1940 for (uint32_t i
= 0; ; i
++) {
1941 if (featureNames
[i
] == NULL
)
1943 if (mask
& (1 << i
))
1944 cliPrintf("%s ", featureNames
[i
]);
1947 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
1948 cliPrint("Available:");
1949 for (uint32_t i
= 0; ; i
++) {
1950 if (featureNames
[i
] == NULL
)
1952 cliPrintf(" %s", featureNames
[i
]);
1957 bool remove
= false;
1958 if (cmdline
[0] == '-') {
1961 cmdline
++; // skip over -
1965 for (uint32_t i
= 0; ; i
++) {
1966 if (featureNames
[i
] == NULL
) {
1967 cliPrintLine("Invalid name");
1971 if (strncasecmp(cmdline
, featureNames
[i
], len
) == 0) {
1975 if (mask
& FEATURE_GPS
) {
1976 cliPrintLine("unavailable");
1981 if (mask
& FEATURE_SONAR
) {
1982 cliPrintLine("unavailable");
1988 cliPrint("Disabled");
1991 cliPrint("Enabled");
1993 cliPrintLinef(" %s", featureNames
[i
]);
2001 static void printBeeper(uint8_t dumpMask
, const beeperConfig_t
*beeperConfig
, const beeperConfig_t
*beeperConfigDefault
)
2003 const uint8_t beeperCount
= beeperTableEntryCount();
2004 const uint32_t mask
= beeperConfig
->beeper_off_flags
;
2005 const uint32_t defaultMask
= beeperConfigDefault
->beeper_off_flags
;
2006 for (int32_t i
= 0; i
< beeperCount
- 2; i
++) {
2007 const char *formatOff
= "beeper -%s";
2008 const char *formatOn
= "beeper %s";
2009 cliDefaultPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOn
: formatOff
, beeperNameForTableIndex(i
));
2010 cliDumpPrintLinef(dumpMask
, ~(mask
^ defaultMask
) & (1 << i
), mask
& (1 << i
) ? formatOff
: formatOn
, beeperNameForTableIndex(i
));
2014 static void cliBeeper(char *cmdline
)
2016 uint32_t len
= strlen(cmdline
);
2017 uint8_t beeperCount
= beeperTableEntryCount();
2018 uint32_t mask
= getBeeperOffMask();
2021 cliPrintf("Disabled:");
2022 for (int32_t i
= 0; ; i
++) {
2023 if (i
== beeperCount
- 2){
2028 if (mask
& (1 << i
))
2029 cliPrintf(" %s", beeperNameForTableIndex(i
));
2032 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2033 cliPrint("Available:");
2034 for (uint32_t i
= 0; i
< beeperCount
; i
++)
2035 cliPrintf(" %s", beeperNameForTableIndex(i
));
2039 bool remove
= false;
2040 if (cmdline
[0] == '-') {
2041 remove
= true; // this is for beeper OFF condition
2046 for (uint32_t i
= 0; ; i
++) {
2047 if (i
== beeperCount
) {
2048 cliPrintLine("Invalid name");
2051 if (strncasecmp(cmdline
, beeperNameForTableIndex(i
), len
) == 0) {
2052 if (remove
) { // beeper off
2053 if (i
== BEEPER_ALL
-1)
2054 beeperOffSetAll(beeperCount
-2);
2056 if (i
== BEEPER_PREFERENCE
-1)
2057 setBeeperOffMask(getPreferredBeeperOffMask());
2062 cliPrint("Disabled");
2065 if (i
== BEEPER_ALL
-1)
2066 beeperOffClearAll();
2068 if (i
== BEEPER_PREFERENCE
-1)
2069 setPreferredBeeperOffMask(getBeeperOffMask());
2072 beeperOffClear(mask
);
2074 cliPrint("Enabled");
2076 cliPrintLinef(" %s", beeperNameForTableIndex(i
));
2084 static void printMap(uint8_t dumpMask
, const rxConfig_t
*rxConfig
, const rxConfig_t
*defaultRxConfig
)
2086 bool equalsDefault
= true;
2088 char bufDefault
[16];
2090 for (i
= 0; i
< MAX_MAPPABLE_RX_INPUTS
; i
++) {
2091 buf
[rxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2092 if (defaultRxConfig
) {
2093 bufDefault
[defaultRxConfig
->rcmap
[i
]] = rcChannelLetters
[i
];
2094 equalsDefault
= equalsDefault
&& (rxConfig
->rcmap
[i
] == defaultRxConfig
->rcmap
[i
]);
2099 const char *formatMap
= "map %s";
2100 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMap
, bufDefault
);
2101 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMap
, buf
);
2104 static void cliMap(char *cmdline
)
2109 len
= strlen(cmdline
);
2113 for (uint32_t i
= 0; i
< 8; i
++)
2114 cmdline
[i
] = toupper((unsigned char)cmdline
[i
]);
2115 for (uint32_t i
= 0; i
< 8; i
++) {
2116 if (strchr(rcChannelLetters
, cmdline
[i
]) && !strchr(cmdline
+ i
+ 1, cmdline
[i
]))
2118 cliShowParseError();
2121 parseRcChannels(cmdline
, rxConfigMutable());
2125 for (i
= 0; i
< 8; i
++)
2126 out
[rxConfig()->rcmap
[i
]] = rcChannelLetters
[i
];
2131 static char *checkCommand(char *cmdLine
, const char *command
)
2133 if(!strncasecmp(cmdLine
, command
, strlen(command
)) // command names match
2134 && (isspace((unsigned)cmdLine
[strlen(command
)]) || cmdLine
[strlen(command
)] == 0)) {
2135 return cmdLine
+ strlen(command
) + 1;
2141 static void cliRebootEx(bool bootLoader
)
2143 cliPrint("\r\nRebooting");
2144 bufWriterFlush(cliWriter
);
2145 waitForSerialPortToFinishTransmitting(cliPort
);
2148 systemResetToBootloader();
2154 static void cliReboot(void)
2159 static void cliBootloader(char *cmdLine
)
2163 cliPrintHashLine("restarting in bootloader mode");
2167 static void cliExit(char *cmdline
)
2171 cliPrintHashLine("leaving CLI mode, unsaved changes lost");
2172 bufWriterFlush(cliWriter
);
2177 // incase a motor was left running during motortest, clear it here
2178 mixerResetDisarmedMotors();
2185 static void cliGpsPassthrough(char *cmdline
)
2189 gpsEnablePassthrough(cliPort
);
2193 #if defined(USE_ESCSERIAL) || defined(USE_DSHOT)
2196 #define ALL_ESCS 255
2199 static int parseEscNumber(char *pch
, bool allowAllEscs
) {
2200 int escNumber
= atoi(pch
);
2201 if ((escNumber
>= 0) && (escNumber
< getMotorCount())) {
2202 tfp_printf("Programming on ESC %d.\r\n", escNumber
);
2203 } else if (allowAllEscs
&& escNumber
== ALL_ESCS
) {
2204 tfp_printf("Programming on all ESCs.\r\n");
2206 tfp_printf("Invalid ESC number, range: 0 to %d.\r\n", getMotorCount() - 1);
2216 static void cliDshotProg(char *cmdline
)
2218 if (isEmpty(cmdline
) || motorConfig()->dev
.motorPwmProtocol
< PWM_TYPE_DSHOT150
) {
2219 cliShowParseError();
2225 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2228 while (pch
!= NULL
) {
2231 escNumber
= parseEscNumber(pch
, true);
2232 if (escNumber
== -1) {
2238 motorControlEnable
= false;
2240 int command
= atoi(pch
);
2241 if (command
>= 0 && command
< DSHOT_MIN_THROTTLE
) {
2242 if (escNumber
== ALL_ESCS
) {
2243 for (unsigned i
= 0; i
< getMotorCount(); i
++) {
2244 pwmWriteDshotCommand(i
, command
);
2247 pwmWriteDshotCommand(escNumber
, command
);
2251 delay(10); // wait for sound output to finish
2254 tfp_printf("Command %d written.\r\n", command
);
2256 tfp_printf("Invalid command, range 1 to %d.\r\n", DSHOT_MIN_THROTTLE
- 1);
2263 pch
= strtok_r(NULL
, " ", &saveptr
);
2266 motorControlEnable
= true;
2270 #ifdef USE_ESCSERIAL
2271 static void cliEscPassthrough(char *cmdline
)
2273 if (isEmpty(cmdline
)) {
2274 cliShowParseError();
2280 char *pch
= strtok_r(cmdline
, " ", &saveptr
);
2284 while (pch
!= NULL
) {
2287 if(strncasecmp(pch
, "sk", strlen(pch
)) == 0) {
2288 mode
= PROTOCOL_SIMONK
;
2289 } else if(strncasecmp(pch
, "bl", strlen(pch
)) == 0) {
2290 mode
= PROTOCOL_BLHELI
;
2291 } else if(strncasecmp(pch
, "ki", strlen(pch
)) == 0) {
2292 mode
= PROTOCOL_KISS
;
2293 } else if(strncasecmp(pch
, "cc", strlen(pch
)) == 0) {
2294 mode
= PROTOCOL_KISSALL
;
2296 cliShowParseError();
2302 escNumber
= parseEscNumber(pch
, mode
== PROTOCOL_KISS
);
2303 if (escNumber
== -1) {
2309 cliShowParseError();
2317 pch
= strtok_r(NULL
, " ", &saveptr
);
2320 escEnablePassthrough(cliPort
, escNumber
, mode
);
2324 #ifndef USE_QUAD_MIXER_ONLY
2325 static void cliMixer(char *cmdline
)
2329 len
= strlen(cmdline
);
2332 cliPrintLinef("Mixer: %s", mixerNames
[mixerConfig()->mixerMode
- 1]);
2334 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2335 cliPrint("Available:");
2336 for (uint32_t i
= 0; ; i
++) {
2337 if (mixerNames
[i
] == NULL
)
2339 cliPrintf(" %s", mixerNames
[i
]);
2345 for (uint32_t i
= 0; ; i
++) {
2346 if (mixerNames
[i
] == NULL
) {
2347 cliPrintLine("Invalid name");
2350 if (strncasecmp(cmdline
, mixerNames
[i
], len
) == 0) {
2351 mixerConfigMutable()->mixerMode
= i
+ 1;
2360 static void cliMotor(char *cmdline
)
2362 int motor_index
= 0;
2363 int motor_value
= 0;
2368 if (isEmpty(cmdline
)) {
2369 cliShowParseError();
2373 pch
= strtok_r(cmdline
, " ", &saveptr
);
2374 while (pch
!= NULL
) {
2377 motor_index
= atoi(pch
);
2380 motor_value
= atoi(pch
);
2384 pch
= strtok_r(NULL
, " ", &saveptr
);
2387 if (motor_index
< 0 || motor_index
>= MAX_SUPPORTED_MOTORS
) {
2388 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS
- 1);
2393 if (motor_value
< PWM_RANGE_MIN
|| motor_value
> PWM_RANGE_MAX
) {
2394 cliShowArgumentRangeError("value", 1000, 2000);
2396 motor_disarmed
[motor_index
] = convertExternalToMotor(motor_value
);
2398 cliPrintLinef("motor %d: %d", motor_index
, convertMotorToExternal(motor_disarmed
[motor_index
]));
2405 static void cliPlaySound(char *cmdline
)
2409 static int lastSoundIdx
= -1;
2411 if (isEmpty(cmdline
)) {
2412 i
= lastSoundIdx
+ 1; //next sound index
2413 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
2414 while (true) { //no name for index; try next one
2415 if (++i
>= beeperTableEntryCount())
2416 i
= 0; //if end then wrap around to first entry
2417 if ((name
=beeperNameForTableIndex(i
)) != NULL
)
2418 break; //if name OK then play sound below
2419 if (i
== lastSoundIdx
+ 1) { //prevent infinite loop
2420 cliPrintLine("Error playing sound");
2425 } else { //index value was given
2427 if ((name
=beeperNameForTableIndex(i
)) == NULL
) {
2428 cliPrintLinef("No sound for index %d", i
);
2434 cliPrintLinef("Playing sound %d: %s", i
, name
);
2435 beeper(beeperModeForTableIndex(i
));
2439 static void cliProfile(char *cmdline
)
2441 if (isEmpty(cmdline
)) {
2442 cliPrintLinef("profile %d", getCurrentPidProfileIndex());
2445 const int i
= atoi(cmdline
);
2446 if (i
>= 0 && i
< MAX_PROFILE_COUNT
) {
2447 systemConfigMutable()->pidProfileIndex
= i
;
2453 static void cliRateProfile(char *cmdline
)
2455 if (isEmpty(cmdline
)) {
2456 cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
2459 const int i
= atoi(cmdline
);
2460 if (i
>= 0 && i
< CONTROL_RATE_PROFILE_COUNT
) {
2461 changeControlRateProfile(i
);
2467 static void cliDumpPidProfile(uint8_t pidProfileIndex
, uint8_t dumpMask
)
2469 if (pidProfileIndex
>= MAX_PROFILE_COUNT
) {
2473 changePidProfile(pidProfileIndex
);
2474 cliPrintHashLine("profile");
2477 dumpAllValues(PROFILE_VALUE
, dumpMask
);
2480 static void cliDumpRateProfile(uint8_t rateProfileIndex
, uint8_t dumpMask
)
2482 if (rateProfileIndex
>= CONTROL_RATE_PROFILE_COUNT
) {
2486 changeControlRateProfile(rateProfileIndex
);
2487 cliPrintHashLine("rateprofile");
2490 dumpAllValues(PROFILE_RATE_VALUE
, dumpMask
);
2493 static void cliSave(char *cmdline
)
2497 cliPrintHashLine("saving");
2502 static void cliDefaults(char *cmdline
)
2506 cliPrintHashLine("resetting to defaults");
2511 static void cliGet(char *cmdline
)
2513 const clivalue_t
*val
;
2514 int matchedCommands
= 0;
2516 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
2517 if (strstr(valueTable
[i
].name
, cmdline
)) {
2518 val
= &valueTable
[i
];
2519 cliPrintf("%s = ", valueTable
[i
].name
);
2520 cliPrintVar(val
, 0);
2522 cliPrintVarRange(val
);
2530 if (matchedCommands
) {
2534 cliPrintLine("Invalid name");
2537 static char *skipSpace(char *buffer
)
2539 while (*(buffer
) == ' ') {
2546 static uint8_t getWordLength(char *bufBegin
, char *bufEnd
)
2548 while (*(bufEnd
- 1) == ' ') {
2552 return bufEnd
- bufBegin
;
2555 static void cliSet(char *cmdline
)
2557 const uint32_t len
= strlen(cmdline
);
2560 if (len
== 0 || (len
== 1 && cmdline
[0] == '*')) {
2561 cliPrintLine("Current settings: ");
2563 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
2564 const clivalue_t
*val
= &valueTable
[i
];
2565 cliPrintf("%s = ", valueTable
[i
].name
);
2566 cliPrintVar(val
, len
); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
2569 } else if ((eqptr
= strstr(cmdline
, "=")) != NULL
) {
2572 uint8_t variableNameLength
= getWordLength(cmdline
, eqptr
);
2574 // skip the '=' and any ' ' characters
2576 eqptr
= skipSpace(eqptr
);
2578 for (uint32_t i
= 0; i
< valueTableEntryCount
; i
++) {
2579 const clivalue_t
*val
= &valueTable
[i
];
2580 // ensure exact match when setting to prevent setting variables with shorter names
2581 if (strncasecmp(cmdline
, valueTable
[i
].name
, strlen(valueTable
[i
].name
)) == 0 && variableNameLength
== strlen(valueTable
[i
].name
)) {
2583 bool valueChanged
= false;
2585 switch (valueTable
[i
].type
& VALUE_MODE_MASK
) {
2587 int16_t value
= atoi(eqptr
);
2589 if (value
>= valueTable
[i
].config
.minmax
.min
&& value
<= valueTable
[i
].config
.minmax
.max
) {
2590 cliSetVar(val
, value
);
2591 valueChanged
= true;
2597 const lookupTableEntry_t
*tableEntry
= &lookupTables
[valueTable
[i
].config
.lookup
.tableIndex
];
2598 bool matched
= false;
2599 for (uint32_t tableValueIndex
= 0; tableValueIndex
< tableEntry
->valueCount
&& !matched
; tableValueIndex
++) {
2600 matched
= strcasecmp(tableEntry
->values
[tableValueIndex
], eqptr
) == 0;
2603 value
= tableValueIndex
;
2605 cliSetVar(val
, value
);
2606 valueChanged
= true;
2613 const uint8_t arrayLength
= valueTable
[i
].config
.array
.length
;
2614 char *valPtr
= eqptr
;
2617 for (int i
= 0; i
< arrayLength
; i
++) {
2618 valPtr
= skipSpace(valPtr
);
2619 char *valEnd
= strstr(valPtr
, ",");
2620 if ((valEnd
!= NULL
) && (i
< arrayLength
- 1)) {
2621 uint8_t varLength
= getWordLength(valPtr
, valEnd
);
2622 if (varLength
<= 3) {
2623 strncpy(curVal
, valPtr
, getWordLength(valPtr
, valEnd
));
2624 curVal
[varLength
] = '\0';
2625 array
[i
] = (uint8_t)atoi((const char *)curVal
);
2626 valPtr
= valEnd
+ 1;
2630 } else if ((valEnd
== NULL
) && (i
== arrayLength
- 1)) {
2631 array
[i
] = atoi(valPtr
);
2633 uint8_t *ptr
= getValuePointer(val
);
2634 memcpy(ptr
, array
, arrayLength
);
2635 valueChanged
= true;
2647 cliPrintf("%s set to ", valueTable
[i
].name
);
2648 cliPrintVar(val
, 0);
2650 cliPrintLine("Invalid value");
2651 cliPrintVarRange(val
);
2657 cliPrintLine("Invalid name");
2659 // no equals, check for matching variables.
2664 static void cliStatus(char *cmdline
)
2668 cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
2669 cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
2671 cliPrintf("CPU Clock=%dMHz", (SystemCoreClock
/ 1000000));
2673 #if defined(USE_SENSOR_NAMES)
2674 const uint32_t detectedSensorsMask
= sensorsMask();
2675 for (uint32_t i
= 0; ; i
++) {
2676 if (sensorTypeNames
[i
] == NULL
) {
2679 const uint32_t mask
= (1 << i
);
2680 if ((detectedSensorsMask
& mask
) && (mask
& SENSOR_NAMES_MASK
)) {
2681 const uint8_t sensorHardwareIndex
= detectedSensors
[i
];
2682 const char *sensorHardware
= sensorHardwareNames
[i
][sensorHardwareIndex
];
2683 cliPrintf(", %s=%s", sensorTypeNames
[i
], sensorHardware
);
2684 if (mask
== SENSOR_ACC
&& acc
.dev
.revisionCode
) {
2685 cliPrintf(".%c", acc
.dev
.revisionCode
);
2689 #endif /* USE_SENSOR_NAMES */
2697 const uint16_t i2cErrorCounter
= i2cGetErrorCounter();
2699 const uint16_t i2cErrorCounter
= 0;
2703 cliPrintf("Stack used: %d, ", stackUsedSize());
2705 cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
2707 cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter
, getEEPROMConfigSize(), &__config_end
- &__config_start
);
2709 const int gyroRate
= getTaskDeltaTime(TASK_GYROPID
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_GYROPID
)));
2710 const int rxRate
= getTaskDeltaTime(TASK_RX
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_RX
)));
2711 const int systemRate
= getTaskDeltaTime(TASK_SYSTEM
) == 0 ? 0 : (int)(1000000.0f
/ ((float)getTaskDeltaTime(TASK_SYSTEM
)));
2712 cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
2713 constrain(averageSystemLoadPercent
, 0, 100), getTaskDeltaTime(TASK_GYROPID
), gyroRate
, rxRate
, systemRate
);
2715 cliPrintLinef("Arming disable flags: 0x%x", getArmingDisableFlags());
2717 cliPrint("Arming disable flags:");
2718 uint16_t flags
= getArmingDisableFlags();
2720 int bitpos
= ffs(flags
) - 1;
2721 flags
&= ~(1 << bitpos
);
2722 cliPrintf(" %s", armingDisableFlagNames
[bitpos
]);
2728 #ifndef SKIP_TASK_STATISTICS
2729 static void cliTasks(char *cmdline
)
2733 int averageLoadSum
= 0;
2736 if (systemConfig()->task_statistics
) {
2737 cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
2739 cliPrintLine("Task list");
2742 for (cfTaskId_e taskId
= 0; taskId
< TASK_COUNT
; taskId
++) {
2743 cfTaskInfo_t taskInfo
;
2744 getTaskInfo(taskId
, &taskInfo
);
2745 if (taskInfo
.isEnabled
) {
2747 int subTaskFrequency
= 0;
2748 if (taskId
== TASK_GYROPID
) {
2749 subTaskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
2750 taskFrequency
= subTaskFrequency
/ pidConfig()->pid_process_denom
;
2751 if (pidConfig()->pid_process_denom
> 1) {
2752 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
2754 taskFrequency
= subTaskFrequency
;
2755 cliPrintf("%02d - (%11s/%3s) ", taskId
, taskInfo
.subTaskName
, taskInfo
.taskName
);
2758 taskFrequency
= taskInfo
.latestDeltaTime
== 0 ? 0 : (int)(1000000.0f
/ ((float)taskInfo
.latestDeltaTime
));
2759 cliPrintf("%02d - (%15s) ", taskId
, taskInfo
.taskName
);
2761 const int maxLoad
= taskInfo
.maxExecutionTime
== 0 ? 0 :(taskInfo
.maxExecutionTime
* taskFrequency
+ 5000) / 1000;
2762 const int averageLoad
= taskInfo
.averageExecutionTime
== 0 ? 0 : (taskInfo
.averageExecutionTime
* taskFrequency
+ 5000) / 1000;
2763 if (taskId
!= TASK_SERIAL
) {
2764 maxLoadSum
+= maxLoad
;
2765 averageLoadSum
+= averageLoad
;
2767 if (systemConfig()->task_statistics
) {
2768 cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
2769 taskFrequency
, taskInfo
.maxExecutionTime
, taskInfo
.averageExecutionTime
,
2770 maxLoad
/10, maxLoad
%10, averageLoad
/10, averageLoad
%10, taskInfo
.totalExecutionTime
/ 1000);
2772 cliPrintLinef("%6d", taskFrequency
);
2774 if (taskId
== TASK_GYROPID
&& pidConfig()->pid_process_denom
> 1) {
2775 cliPrintLinef(" - (%15s) %6d", taskInfo
.subTaskName
, subTaskFrequency
);
2779 if (systemConfig()->task_statistics
) {
2780 cfCheckFuncInfo_t checkFuncInfo
;
2781 getCheckFuncInfo(&checkFuncInfo
);
2782 cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo
.maxExecutionTime
, checkFuncInfo
.averageExecutionTime
, checkFuncInfo
.totalExecutionTime
/ 1000);
2783 cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum
/10, maxLoadSum
%10, averageLoadSum
/10, averageLoadSum
%10);
2788 static void cliVersion(char *cmdline
)
2792 cliPrintLinef("# %s / %s %s %s / %s (%s)",
2802 #if defined(USE_RESOURCE_MGMT)
2804 #define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
2807 const uint8_t owner
;
2810 const uint8_t maxIndex
;
2811 } cliResourceValue_t
;
2813 const cliResourceValue_t resourceTable
[] = {
2815 { OWNER_BEEPER
, PG_BEEPER_DEV_CONFIG
, offsetof(beeperDevConfig_t
, ioTag
), 0 },
2817 { OWNER_MOTOR
, PG_MOTOR_CONFIG
, offsetof(motorConfig_t
, dev
.ioTags
[0]), MAX_SUPPORTED_MOTORS
},
2819 { OWNER_SERVO
, PG_SERVO_CONFIG
, offsetof(servoConfig_t
, dev
.ioTags
[0]), MAX_SUPPORTED_SERVOS
},
2821 #if defined(USE_PWM) || defined(USE_PPM)
2822 { OWNER_PPMINPUT
, PG_PPM_CONFIG
, offsetof(ppmConfig_t
, ioTag
), 0 },
2823 { OWNER_PWMINPUT
, PG_PWM_CONFIG
, offsetof(pwmConfig_t
, ioTags
[0]), PWM_INPUT_PORT_COUNT
},
2826 { OWNER_SONAR_TRIGGER
, PG_SONAR_CONFIG
, offsetof(sonarConfig_t
, triggerTag
), 0 },
2827 { OWNER_SONAR_ECHO
, PG_SONAR_CONFIG
, offsetof(sonarConfig_t
, echoTag
), 0 },
2830 { OWNER_LED_STRIP
, PG_LED_STRIP_CONFIG
, offsetof(ledStripConfig_t
, ioTag
), 0 },
2832 { OWNER_SERIAL_TX
, PG_SERIAL_PIN_CONFIG
, offsetof(serialPinConfig_t
, ioTagTx
[0]), SERIAL_PORT_MAX_INDEX
},
2833 { OWNER_SERIAL_RX
, PG_SERIAL_PIN_CONFIG
, offsetof(serialPinConfig_t
, ioTagRx
[0]), SERIAL_PORT_MAX_INDEX
},
2835 { OWNER_INVERTER
, PG_SERIAL_PIN_CONFIG
, offsetof(serialPinConfig_t
, ioTagInverter
[0]), SERIAL_PORT_MAX_INDEX
},
2838 { OWNER_I2C_SCL
, PG_I2C_CONFIG
, offsetof(i2cConfig_t
, ioTagScl
[0]), I2CDEV_COUNT
},
2839 { OWNER_I2C_SDA
, PG_I2C_CONFIG
, offsetof(i2cConfig_t
, ioTagSda
[0]), I2CDEV_COUNT
},
2841 { OWNER_LED
, PG_STATUS_LED_CONFIG
, offsetof(statusLedConfig_t
, ioTags
[0]), STATUS_LED_NUMBER
},
2842 #ifdef USE_SPEKTRUM_BIND
2843 { OWNER_RX_BIND
, PG_RX_CONFIG
, offsetof(rxConfig_t
, spektrum_bind_pin_override_ioTag
), 0 },
2844 { OWNER_RX_BIND_PLUG
, PG_RX_CONFIG
, offsetof(rxConfig_t
, spektrum_bind_plug_ioTag
), 0 },
2848 static ioTag_t
*getIoTag(const cliResourceValue_t value
, uint8_t index
)
2850 const pgRegistry_t
* rec
= pgFind(value
.pgn
);
2851 return CONST_CAST(ioTag_t
*, rec
->address
+ value
.offset
+ index
);
2854 static void printResource(uint8_t dumpMask
)
2856 for (unsigned int i
= 0; i
< ARRAYLEN(resourceTable
); i
++) {
2857 const char* owner
= ownerNames
[resourceTable
[i
].owner
];
2858 const pgRegistry_t
* pg
= pgFind(resourceTable
[i
].pgn
);
2859 const void *currentConfig
;
2860 const void *defaultConfig
;
2861 if (configIsInCopy
) {
2862 currentConfig
= pg
->copy
;
2863 defaultConfig
= pg
->address
;
2865 currentConfig
= pg
->address
;
2866 defaultConfig
= NULL
;
2869 for (int index
= 0; index
< MAX_RESOURCE_INDEX(resourceTable
[i
].maxIndex
); index
++) {
2870 const ioTag_t ioTag
= *((const ioTag_t
*)currentConfig
+ resourceTable
[i
].offset
+ index
);
2871 const ioTag_t ioTagDefault
= *((const ioTag_t
*)defaultConfig
+ resourceTable
[i
].offset
+ index
);
2873 bool equalsDefault
= ioTag
== ioTagDefault
;
2874 const char *format
= "resource %s %d %c%02d";
2875 const char *formatUnassigned
= "resource %s %d NONE";
2876 if (!ioTagDefault
) {
2877 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
2879 cliDefaultPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTagDefault
) + 'A', IO_GPIOPinIdxByTag(ioTagDefault
));
2882 if (!(dumpMask
& HIDE_UNUSED
)) {
2883 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatUnassigned
, owner
, RESOURCE_INDEX(index
));
2886 cliDumpPrintLinef(dumpMask
, equalsDefault
, format
, owner
, RESOURCE_INDEX(index
), IO_GPIOPortIdxByTag(ioTag
) + 'A', IO_GPIOPinIdxByTag(ioTag
));
2892 static void printResourceOwner(uint8_t owner
, uint8_t index
)
2894 cliPrintf("%s", ownerNames
[resourceTable
[owner
].owner
]);
2896 if (resourceTable
[owner
].maxIndex
> 0) {
2897 cliPrintf(" %d", RESOURCE_INDEX(index
));
2901 static void resourceCheck(uint8_t resourceIndex
, uint8_t index
, ioTag_t newTag
)
2907 const char * format
= "\r\nNOTE: %c%02d already assigned to ";
2908 for (int r
= 0; r
< (int)ARRAYLEN(resourceTable
); r
++) {
2909 for (int i
= 0; i
< MAX_RESOURCE_INDEX(resourceTable
[r
].maxIndex
); i
++) {
2910 ioTag_t
*tag
= getIoTag(resourceTable
[r
], i
);
2911 if (*tag
== newTag
) {
2912 bool cleared
= false;
2913 if (r
== resourceIndex
) {
2921 cliPrintf(format
, DEFIO_TAG_GPIOID(newTag
) + 'A', DEFIO_TAG_PIN(newTag
));
2923 printResourceOwner(r
, i
);
2927 printResourceOwner(r
, i
);
2928 cliPrintf(" disabled");
2937 static void cliResource(char *cmdline
)
2939 int len
= strlen(cmdline
);
2942 printResource(DUMP_MASTER
| HIDE_UNUSED
);
2945 } else if (strncasecmp(cmdline
, "list", len
) == 0) {
2949 cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
2952 for (int i
= 0; i
< DEFIO_IO_USED_COUNT
; i
++) {
2954 owner
= ownerNames
[ioRecs
[i
].owner
];
2956 cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs
+ i
) + 'A', IO_GPIOPinIdx(ioRecs
+ i
), owner
);
2957 if (ioRecs
[i
].index
> 0) {
2958 cliPrintf(" %d", ioRecs
[i
].index
);
2966 cliPrintLine("DMA:");
2968 cliPrintLine("Currently active DMA:");
2971 for (int i
= 0; i
< DMA_MAX_DESCRIPTORS
; i
++) {
2973 owner
= ownerNames
[dmaGetOwner(i
)];
2975 cliPrintf(DMA_OUTPUT_STRING
, i
/ DMA_MOD_VALUE
+ 1, (i
% DMA_MOD_VALUE
) + DMA_MOD_OFFSET
);
2976 uint8_t resourceIndex
= dmaGetResourceIndex(i
);
2977 if (resourceIndex
> 0) {
2978 cliPrintLinef(" %s %d", owner
, resourceIndex
);
2980 cliPrintLinef(" %s", owner
);
2985 cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
2991 uint8_t resourceIndex
= 0;
2996 pch
= strtok_r(cmdline
, " ", &saveptr
);
2997 for (resourceIndex
= 0; ; resourceIndex
++) {
2998 if (resourceIndex
>= ARRAYLEN(resourceTable
)) {
2999 cliPrintLine("Invalid");
3003 if (strncasecmp(pch
, ownerNames
[resourceTable
[resourceIndex
].owner
], len
) == 0) {
3008 pch
= strtok_r(NULL
, " ", &saveptr
);
3011 if (resourceTable
[resourceIndex
].maxIndex
> 0 || index
> 0) {
3012 if (index
<= 0 || index
> MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
)) {
3013 cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable
[resourceIndex
].maxIndex
));
3018 pch
= strtok_r(NULL
, " ", &saveptr
);
3021 ioTag_t
*tag
= getIoTag(resourceTable
[resourceIndex
], index
);
3024 if (strlen(pch
) > 0) {
3025 if (strcasecmp(pch
, "NONE") == 0) {
3028 cliPrintLine("Freed");
3030 cliPrintLine("Resource is freed");
3034 uint8_t port
= (*pch
) - 'A';
3036 port
= (*pch
) - 'a';
3043 ioRec_t
*rec
= IO_Rec(IOGetByTag(DEFIO_TAG_MAKE(port
, pin
)));
3045 resourceCheck(resourceIndex
, index
, DEFIO_TAG_MAKE(port
, pin
));
3047 cliPrintLinef(" %c%02d set", port
+ 'A', pin
);
3049 cliPrintLinef("\r\nResource is set to %c%02d", port
+ 'A', pin
);
3051 *tag
= DEFIO_TAG_MAKE(port
, pin
);
3053 cliShowParseError();
3061 cliShowParseError();
3063 #endif /* USE_RESOURCE_MGMT */
3065 static void backupConfigs(void)
3067 // make copies of configs to do differencing
3069 memcpy(pg
->copy
, pg
->address
, pg
->size
);
3072 configIsInCopy
= true;
3075 static void restoreConfigs(void)
3078 memcpy(pg
->address
, pg
->copy
, pg
->size
);
3081 configIsInCopy
= false;
3084 static void printConfig(char *cmdline
, bool doDiff
)
3086 uint8_t dumpMask
= DUMP_MASTER
;
3088 if ((options
= checkCommand(cmdline
, "master"))) {
3089 dumpMask
= DUMP_MASTER
; // only
3090 } else if ((options
= checkCommand(cmdline
, "profile"))) {
3091 dumpMask
= DUMP_PROFILE
; // only
3092 } else if ((options
= checkCommand(cmdline
, "rates"))) {
3093 dumpMask
= DUMP_RATES
; // only
3094 } else if ((options
= checkCommand(cmdline
, "all"))) {
3095 dumpMask
= DUMP_ALL
; // all profiles and rates
3101 dumpMask
= dumpMask
| DO_DIFF
;
3105 // reset all configs to defaults to do differencing
3108 #if defined(TARGET_CONFIG)
3109 targetConfiguration();
3111 if (checkCommand(options
, "defaults")) {
3112 dumpMask
= dumpMask
| SHOW_DEFAULTS
; // add default values as comments for changed values
3115 if ((dumpMask
& DUMP_MASTER
) || (dumpMask
& DUMP_ALL
)) {
3116 cliPrintHashLine("version");
3119 if ((dumpMask
& (DUMP_ALL
| DO_DIFF
)) == (DUMP_ALL
| DO_DIFF
)) {
3120 cliPrintHashLine("reset configuration to default settings");
3121 cliPrint("defaults");
3125 cliPrintHashLine("name");
3126 printName(dumpMask
, &systemConfig_Copy
);
3128 #ifdef USE_RESOURCE_MGMT
3129 cliPrintHashLine("resources");
3130 printResource(dumpMask
);
3133 #ifndef USE_QUAD_MIXER_ONLY
3134 cliPrintHashLine("mixer");
3135 const bool equalsDefault
= mixerConfig_Copy
.mixerMode
== mixerConfig()->mixerMode
;
3136 const char *formatMixer
= "mixer %s";
3137 cliDefaultPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig()->mixerMode
- 1]);
3138 cliDumpPrintLinef(dumpMask
, equalsDefault
, formatMixer
, mixerNames
[mixerConfig_Copy
.mixerMode
- 1]);
3140 cliDumpPrintLinef(dumpMask
, customMotorMixer(0)->throttle
== 0.0f
, "\r\nmmix reset\r\n");
3142 printMotorMix(dumpMask
, customMotorMixer_CopyArray
, customMotorMixer(0));
3145 cliPrintHashLine("servo");
3146 printServo(dumpMask
, servoParams_CopyArray
, servoParams(0));
3148 cliPrintHashLine("servo mix");
3149 // print custom servo mixer if exists
3150 cliDumpPrintLinef(dumpMask
, customServoMixers(0)->rate
== 0, "smix reset\r\n");
3151 printServoMix(dumpMask
, customServoMixers_CopyArray
, customServoMixers(0));
3155 cliPrintHashLine("feature");
3156 printFeature(dumpMask
, &featureConfig_Copy
, featureConfig());
3159 cliPrintHashLine("beeper");
3160 printBeeper(dumpMask
, &beeperConfig_Copy
, beeperConfig());
3163 cliPrintHashLine("map");
3164 printMap(dumpMask
, &rxConfig_Copy
, rxConfig());
3166 cliPrintHashLine("serial");
3167 printSerial(dumpMask
, &serialConfig_Copy
, serialConfig());
3170 cliPrintHashLine("led");
3171 printLed(dumpMask
, ledStripConfig_Copy
.ledConfigs
, ledStripConfig()->ledConfigs
);
3173 cliPrintHashLine("color");
3174 printColor(dumpMask
, ledStripConfig_Copy
.colors
, ledStripConfig()->colors
);
3176 cliPrintHashLine("mode_color");
3177 printModeColor(dumpMask
, &ledStripConfig_Copy
, ledStripConfig());
3180 cliPrintHashLine("aux");
3181 printAux(dumpMask
, modeActivationConditions_CopyArray
, modeActivationConditions(0));
3183 cliPrintHashLine("adjrange");
3184 printAdjustmentRange(dumpMask
, adjustmentRanges_CopyArray
, adjustmentRanges(0));
3186 cliPrintHashLine("rxrange");
3187 printRxRange(dumpMask
, rxChannelRangeConfigs_CopyArray
, rxChannelRangeConfigs(0));
3190 cliPrintHashLine("vtx");
3191 printVtx(dumpMask
, &vtxConfig_Copy
, vtxConfig());
3194 cliPrintHashLine("rxfail");
3195 printRxFailsafe(dumpMask
, rxFailsafeChannelConfigs_CopyArray
, rxFailsafeChannelConfigs(0));
3197 cliPrintHashLine("master");
3198 dumpAllValues(MASTER_VALUE
, dumpMask
);
3200 if (dumpMask
& DUMP_ALL
) {
3201 const uint8_t pidProfileIndexSave
= systemConfig_Copy
.pidProfileIndex
;
3202 for (uint32_t pidProfileIndex
= 0; pidProfileIndex
< MAX_PROFILE_COUNT
; pidProfileIndex
++) {
3203 cliDumpPidProfile(pidProfileIndex
, dumpMask
);
3205 changePidProfile(pidProfileIndexSave
);
3206 cliPrintHashLine("restore original profile selection");
3209 const uint8_t controlRateProfileIndexSave
= systemConfig_Copy
.activeRateProfile
;
3210 for (uint32_t rateIndex
= 0; rateIndex
< CONTROL_RATE_PROFILE_COUNT
; rateIndex
++) {
3211 cliDumpRateProfile(rateIndex
, dumpMask
);
3213 changeControlRateProfile(controlRateProfileIndexSave
);
3214 cliPrintHashLine("restore original rateprofile selection");
3217 cliPrintHashLine("save configuration");
3220 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
3222 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
3226 if (dumpMask
& DUMP_PROFILE
) {
3227 cliDumpPidProfile(systemConfig_Copy
.pidProfileIndex
, dumpMask
);
3230 if (dumpMask
& DUMP_RATES
) {
3231 cliDumpRateProfile(systemConfig_Copy
.activeRateProfile
, dumpMask
);
3233 // restore configs from copies
3237 static void cliDump(char *cmdline
)
3239 printConfig(cmdline
, false);
3242 static void cliDiff(char *cmdline
)
3244 printConfig(cmdline
, true);
3250 const char *description
;
3253 void (*func
)(char *cmdline
);
3257 #define CLI_COMMAND_DEF(name, description, args, method) \
3265 #define CLI_COMMAND_DEF(name, description, args, method) \
3272 static void cliHelp(char *cmdline
);
3274 // should be sorted a..z for bsearch()
3275 const clicmd_t cmdTable
[] = {
3276 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL
, cliAdjustmentRange
),
3277 CLI_COMMAND_DEF("aux", "configure modes", NULL
, cliAux
),
3279 CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
3280 "\t<+|->[name]", cliBeeper
),
3283 CLI_COMMAND_DEF("color", "configure colors", NULL
, cliColor
),
3285 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL
, cliDefaults
),
3286 CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL
, cliBootloader
),
3287 CLI_COMMAND_DEF("diff", "list configuration changes from default",
3288 "[master|profile|rates|all] {showdefaults}", cliDiff
),
3290 CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg
),
3292 CLI_COMMAND_DEF("dump", "dump configuration",
3293 "[master|profile|rates|all] {showdefaults}", cliDump
),
3294 #ifdef USE_ESCSERIAL
3295 CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough
),
3297 CLI_COMMAND_DEF("exit", NULL
, NULL
, cliExit
),
3298 CLI_COMMAND_DEF("feature", "configure features",
3300 "\t<+|->[name]", cliFeature
),
3302 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL
, cliFlashErase
),
3303 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL
, cliFlashInfo
),
3304 #ifdef USE_FLASH_TOOLS
3305 CLI_COMMAND_DEF("flash_read", NULL
, "<length> <address>", cliFlashRead
),
3306 CLI_COMMAND_DEF("flash_write", NULL
, "<address> <message>", cliFlashWrite
),
3309 CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet
),
3311 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL
, cliGpsPassthrough
),
3313 CLI_COMMAND_DEF("help", NULL
, NULL
, cliHelp
),
3315 CLI_COMMAND_DEF("led", "configure leds", NULL
, cliLed
),
3317 CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap
),
3318 #ifndef USE_QUAD_MIXER_ONLY
3319 CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer
),
3321 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL
, cliMotorMix
),
3323 CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL
, cliModeColor
),
3325 CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor
),
3326 CLI_COMMAND_DEF("name", "name of craft", NULL
, cliName
),
3328 CLI_COMMAND_DEF("play_sound", NULL
, "[<index>]", cliPlaySound
),
3330 CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile
),
3331 CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile
),
3332 #if defined(USE_RESOURCE_MGMT)
3333 CLI_COMMAND_DEF("resource", "show/set resources", NULL
, cliResource
),
3335 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL
, cliRxFailsafe
),
3336 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL
, cliRxRange
),
3337 CLI_COMMAND_DEF("save", "save and reboot", NULL
, cliSave
),
3339 CLI_COMMAND_DEF("sd_info", "sdcard info", NULL
, cliSdInfo
),
3341 CLI_COMMAND_DEF("serial", "configure serial ports", NULL
, cliSerial
),
3342 #ifndef SKIP_SERIAL_PASSTHROUGH
3343 CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", "<id> [baud] [mode] : passthrough to serial", cliSerialPassthrough
),
3346 CLI_COMMAND_DEF("servo", "configure servos", NULL
, cliServo
),
3348 CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet
),
3350 CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
3352 "\tload <mixer>\r\n"
3353 "\treverse <servo> <source> r|n", cliServoMix
),
3355 CLI_COMMAND_DEF("status", "show status", NULL
, cliStatus
),
3356 #ifndef SKIP_TASK_STATISTICS
3357 CLI_COMMAND_DEF("tasks", "show task stats", NULL
, cliTasks
),
3359 CLI_COMMAND_DEF("version", "show version", NULL
, cliVersion
),
3361 CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL
, cliVtx
),
3364 static void cliHelp(char *cmdline
)
3368 for (uint32_t i
= 0; i
< ARRAYLEN(cmdTable
); i
++) {
3369 cliPrint(cmdTable
[i
].name
);
3371 if (cmdTable
[i
].description
) {
3372 cliPrintf(" - %s", cmdTable
[i
].description
);
3374 if (cmdTable
[i
].args
) {
3375 cliPrintf("\r\n\t%s", cmdTable
[i
].args
);
3382 void cliProcess(void)
3388 // Be a little bit tricky. Flush the last inputs buffer, if any.
3389 bufWriterFlush(cliWriter
);
3391 while (serialRxBytesWaiting(cliPort
)) {
3392 uint8_t c
= serialRead(cliPort
);
3393 if (c
== '\t' || c
== '?') {
3394 // do tab completion
3395 const clicmd_t
*cmd
, *pstart
= NULL
, *pend
= NULL
;
3396 uint32_t i
= bufferIndex
;
3397 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
3398 if (bufferIndex
&& (strncasecmp(cliBuffer
, cmd
->name
, bufferIndex
) != 0))
3404 if (pstart
) { /* Buffer matches one or more commands */
3405 for (; ; bufferIndex
++) {
3406 if (pstart
->name
[bufferIndex
] != pend
->name
[bufferIndex
])
3408 if (!pstart
->name
[bufferIndex
] && bufferIndex
< sizeof(cliBuffer
) - 2) {
3409 /* Unambiguous -- append a space */
3410 cliBuffer
[bufferIndex
++] = ' ';
3411 cliBuffer
[bufferIndex
] = '\0';
3414 cliBuffer
[bufferIndex
] = pstart
->name
[bufferIndex
];
3417 if (!bufferIndex
|| pstart
!= pend
) {
3418 /* Print list of ambiguous matches */
3419 cliPrint("\r\033[K");
3420 for (cmd
= pstart
; cmd
<= pend
; cmd
++) {
3421 cliPrint(cmd
->name
);
3425 i
= 0; /* Redraw prompt */
3427 for (; i
< bufferIndex
; i
++)
3428 cliWrite(cliBuffer
[i
]);
3429 } else if (!bufferIndex
&& c
== 4) { // CTRL-D
3432 } else if (c
== 12) { // NewPage / CTRL-L
3434 cliPrint("\033[2J\033[1;1H");
3436 } else if (bufferIndex
&& (c
== '\n' || c
== '\r')) {
3440 // Strip comment starting with # from line
3441 char *p
= cliBuffer
;
3444 bufferIndex
= (uint32_t)(p
- cliBuffer
);
3447 // Strip trailing whitespace
3448 while (bufferIndex
> 0 && cliBuffer
[bufferIndex
- 1] == ' ') {
3452 // Process non-empty lines
3453 if (bufferIndex
> 0) {
3454 cliBuffer
[bufferIndex
] = 0; // null terminate
3456 const clicmd_t
*cmd
;
3458 for (cmd
= cmdTable
; cmd
< cmdTable
+ ARRAYLEN(cmdTable
); cmd
++) {
3459 if ((options
= checkCommand(cliBuffer
, cmd
->name
))) {
3463 if(cmd
< cmdTable
+ ARRAYLEN(cmdTable
))
3466 cliPrint("Unknown command, try 'help'");
3470 memset(cliBuffer
, 0, sizeof(cliBuffer
));
3472 // 'exit' will reset this flag, so we don't need to print prompt again
3477 } else if (c
== 127) {
3480 cliBuffer
[--bufferIndex
] = 0;
3481 cliPrint("\010 \010");
3483 } else if (bufferIndex
< sizeof(cliBuffer
) && c
>= 32 && c
<= 126) {
3484 if (!bufferIndex
&& c
== ' ')
3485 continue; // Ignore leading spaces
3486 cliBuffer
[bufferIndex
++] = c
;
3492 void cliEnter(serialPort_t
*serialPort
)
3495 cliPort
= serialPort
;
3496 setPrintfSerialPort(cliPort
);
3497 cliWriter
= bufWriterInit(cliWriteBuffer
, sizeof(cliWriteBuffer
), (bufWrite_t
)serialWriteBufShim
, serialPort
);
3499 schedulerSetCalulateTaskStatistics(systemConfig()->task_statistics
);
3502 cliPrintLine("\r\nEntering CLI Mode, type 'exit' to return, or 'help'");
3504 cliPrintLine("\r\nCLI");
3508 setArmingDisabled(ARMING_DISABLED_CLI
);
3511 void cliInit(const serialConfig_t
*serialConfig
)
3513 UNUSED(serialConfig
);