Update serial_cli.c
[u360gts.git] / src / main / io / serial_cli.c
blob0eaf5f4db72f7f013381186598108f11fa8661d0
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"
27 #include "version.h"
29 #include "build_config.h"
31 #include "common/axis.h"
32 #include "common/maths.h"
33 #include "common/color.h"
34 #include "common/typeconversion.h"
36 #include "drivers/system.h"
38 #include "drivers/sensor.h"
39 #include "drivers/accgyro.h"
40 #include "drivers/compass.h"
42 #include "drivers/serial.h"
43 #include "drivers/bus_i2c.h"
44 #include "drivers/gpio.h"
45 #include "drivers/timer.h"
46 #include "drivers/pwm_rx.h"
49 #include "io/escservo.h"
50 #include "io/gps.h"
51 #include "io/gimbal.h"
52 #include "io/rc_controls.h"
53 #include "io/serial.h"
54 #include "io/serial_1wire.h"
55 #include "io/ledstrip.h"
56 #include "io/flashfs.h"
57 #include "io/beeper.h"
58 #include "io/display.h"
60 #include "rx/rx.h"
61 #include "rx/spektrum.h"
63 #include "sensors/battery.h"
64 #include "sensors/boardalignment.h"
65 #include "sensors/sensors.h"
66 #include "sensors/acceleration.h"
67 #include "sensors/gyro.h"
68 #include "sensors/compass.h"
69 #include "sensors/barometer.h"
71 #include "flight/pid.h"
72 #include "flight/imu.h"
73 #include "flight/mixer.h"
74 #include "flight/navigation.h"
75 #include "flight/failsafe.h"
77 #include "telemetry/telemetry.h"
78 #include "telemetry/frsky.h"
80 #include "tracker/gps_estimation.h"
82 #include "config/runtime_config.h"
83 #include "config/config.h"
84 #include "config/config_profile.h"
85 #include "config/config_master.h"
87 #include "common/printf.h"
89 #include "serial_cli.h"
91 // FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
92 // signal that we're in cli mode
93 uint8_t cliMode = 0;
94 uint16_t SERVOTEST_HEADING = 0;
95 uint8_t SERVOTEST_TILT = 0;
97 #ifdef USE_CLI
99 extern uint16_t cycleTime; // FIXME dependency on mw.c
101 extern uint16_t rssi;
103 //void gpsEnablePassthrough(serialPort_t *gpsPassthroughPort);
105 static serialPort_t *cliPort;
107 /*static void cliAux(char *cmdline);
108 static void cliRxFail(char *cmdline);
109 static void cliAdjustmentRange(char *cmdline);
110 static void cliMotorMix(char *cmdline);*/
111 static void cliDefaults(char *cmdline);
112 /*static void cliDump(char *cmdLine);*/
113 static void cliExit(char *cmdline);
114 static void cliFeature(char *cmdline);
115 /*static void cliMotor(char *cmdline);*/
116 /*static void cliPlaySound(char *cmdline);
117 static void cliProfile(char *cmdline);
118 static void cliRateProfile(char *cmdline);*/
119 static void cliReboot(void);
120 static void cliSave(char *cmdline);
121 static void cliSerial(char *cmdline);
123 /*#ifdef USE_SERVOS
124 static void cliServo(char *cmdline);
125 static void cliServoMix(char *cmdline);
126 #endif*/
128 static void cliSet(char *cmdline);
129 static void cliGet(char *cmdline);
130 static void cliStatus(char *cmdline);
131 static void cliVersion(char *cmdline);
132 /*static void cliRxRange(char *cmdline);*/
133 static void cliCalibrateCompass(void);
134 static void cliCalibratePan(void);
135 static void cliMovePanServo(char *cmdline);
136 static void cliMoveTiltServo(char *cmdline);
137 static void cliSetPanServoSpeed();
138 static void cliSetOffset();
139 static void cliDumpTracker();
140 static void cliBootMode();
141 /*#ifdef GPS
142 static void cliGpsPassthrough(char *cmdline);
143 #endif*/
145 static void cliHelp(char *cmdline);
146 static void cliRssi();
147 /*static void cliMap(char *cmdline);
149 #ifdef LED_STRIP
150 static void cliLed(char *cmdline);
151 static void cliColor(char *cmdline);
152 #endif*/
154 /*#ifndef USE_QUAD_MIXER_ONLY
155 static void cliMixer(char *cmdline);
156 #endif*/
158 /*#ifdef USE_FLASHFS
159 static void cliFlashInfo(char *cmdline);
160 static void cliFlashErase(char *cmdline);
161 #ifdef USE_FLASH_TOOLS
162 static void cliFlashWrite(char *cmdline);
163 static void cliFlashRead(char *cmdline);
164 #endif
165 #endif
167 #ifdef USE_SERIAL_1WIRE
168 static void cliUSB1Wire(char *cmdline);
169 #endif*/
171 // buffer
172 static char cliBuffer[48];
173 static uint32_t bufferIndex = 0;
175 /*#ifndef USE_QUAD_MIXER_ONLY
176 // sync this with mixerMode_e
177 static const char * const mixerNames[] = {
178 "TRI", "QUADP", "QUADX", "BI",
179 "GIMBAL", "Y6", "HEX6",
180 "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
181 "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
182 "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
183 "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", NULL
185 #endif*/
187 // sync this with features_e
188 static const char * const featureNames[] = {
189 "VBAT",
190 "SERVO_TILT",
191 "SOFTSERIAL",
192 "GPS",
193 "SONAR",
194 "TELEMETRY",
195 "CURRENT_METER",
196 "DISPLAY",
197 "BLACKBOX",
198 "EASING",
199 "NOPID",
200 "DEBUG",
201 "EPS",
202 "RSSI_ADC",
203 "AUTODETECT",
204 NULL
207 // sync this with rxFailsafeChannelMode_e
208 //static const char rxFailsafeModeCharacters[] = "ahs";
210 /*static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
211 { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_INVALID },
212 { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
213 };*/
215 #ifndef CJMCU
216 // sync this with sensors_e
217 static const char * const sensorTypeNames[] = {
218 "GYRO", "ACC", "BARO", "MAG", "SONAR", "GPS", "GPS+MAG", NULL
221 #define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG)
223 static const char * const sensorHardwareNames[4][11] = {
224 { "", "None", "MPU6050", "L3G4200D", "MPU3050", "L3GD20", "MPU6000", "MPU6500", "FAKE", NULL },
225 { "", "None", "ADXL345", "MPU6050", "MMA845x", "BMA280", "LSM303DLHC", "MPU6000", "MPU6500", "FAKE", NULL },
226 { "", "None", "BMP085", "MS5611", "BMP280", NULL },
227 { "", "None", "HMC5883", "AK8975", "QMC5883", NULL }
229 #endif
231 typedef struct {
232 const char *name;
233 #ifndef SKIP_CLI_COMMAND_HELP
234 const char *description;
235 const char *args;
236 #endif
237 void (*func)(char *cmdline);
238 } clicmd_t;
240 #ifndef SKIP_CLI_COMMAND_HELP
241 #define CLI_COMMAND_DEF(name, description, args, method) \
243 name , \
244 description , \
245 args , \
246 method \
248 #else
249 #define CLI_COMMAND_DEF(name, description, args, method) \
251 name, \
252 method \
254 #endif
256 // should be sorted a..z for bsearch()
257 const clicmd_t cmdTable[] = {
258 /*#ifdef USE_SERIAL_1WIRE
259 CLI_COMMAND_DEF("1wire", "1-wire interface to escs", "<esc index>", cliUSB1Wire),
260 #endif
261 CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
262 CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
263 #ifdef LED_STRIP
264 CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
265 #endif*/
266 CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
267 /*CLI_COMMAND_DEF("dump", "dump configuration",
268 "[master|profile]", cliDump),*/
269 CLI_COMMAND_DEF("dump", "dump configuration",NULL, cliDumpTracker),
270 CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
271 CLI_COMMAND_DEF("feature", "configure features",
272 "list\r\n"
273 "\t<+|->[name]", cliFeature),
274 /*#ifdef USE_FLASHFS
275 CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
276 CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
277 #ifdef USE_FLASH_TOOLS
278 CLI_COMMAND_DEF("flash_read", NULL, "<length> <address>", cliFlashRead),
279 CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
280 #endif
281 #endif*/
282 //CLI_COMMAND_DEF("get", "get variable value","[name]", cliGet),
283 /*#ifdef GPS
284 CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
285 #endif*/
286 CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
287 /*#ifdef LED_STRIP
288 CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
289 #endif
290 CLI_COMMAND_DEF("map", "configure rc channel order",
291 "[<map>]", cliMap),
292 #ifndef USE_QUAD_MIXER_ONLY
293 CLI_COMMAND_DEF("mixer", "configure mixer",
294 "list\r\n"
295 "\t<name>", cliMixer),
296 #endif
297 CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
298 CLI_COMMAND_DEF("motor", "get/set motor",
299 "<index> [<value>]", cliMotor),
300 CLI_COMMAND_DEF("play_sound", NULL,
301 "[<index>]\r\n", cliPlaySound),
302 CLI_COMMAND_DEF("profile", "change profile",
303 "[<index>]", cliProfile),
304 CLI_COMMAND_DEF("rateprofile", "change rate profile",
305 "[<index>]", cliRateProfile),
306 CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
307 CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFail),*/
308 CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave),
309 CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),/*
310 #ifdef USE_SERVOS
311 CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
312 #endif*/
313 CLI_COMMAND_DEF("set", "change setting",
314 "[<name>=<value>]", cliSet),
315 /*#ifdef USE_SERVOS
316 CLI_COMMAND_DEF("smix", "servo mixer",
317 "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
318 "\treset\r\n"
319 "\tload <mixer>\r\n"
320 "\treverse <servo> <source> r|n", cliServoMix),
321 #endif*/
322 CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
323 CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
324 CLI_COMMAND_DEF("calibrate mag", "calibrate compass", NULL, cliCalibrateCompass),
325 CLI_COMMAND_DEF("calibrate pan", "calibrate servo pan", NULL, cliCalibratePan),
326 CLI_COMMAND_DEF("heading", "move pan servo to degrees (0 - 360)", "<degrees>", cliMovePanServo),
327 CLI_COMMAND_DEF("tilt", "move tilt servo to degrees (0 - 90)", "<degrees>", cliMoveTiltServo),
328 CLI_COMMAND_DEF("boot mode", "restart the board to boot mode for load a new version of the firmware", NULL, cliBootMode),
329 CLI_COMMAND_DEF("rssi", "show rssi status", NULL, cliRssi)
331 #define CMD_COUNT (sizeof(cmdTable) / sizeof(clicmd_t))
333 static const char * const lookupTableOffOn[] = {
334 "OFF", "ON"
337 static const char * const lookupTableUnit[] = {
338 "IMPERIAL", "METRIC"
341 static const char * const lookupTableAlignment[] = {
342 "DEFAULT",
343 "CW0",
344 "CW90",
345 "CW180",
346 "CW270",
347 "CW0FLIP",
348 "CW90FLIP",
349 "CW180FLIP",
350 "CW270FLIP"
353 static const char * const lookupTabletelemetryProvider[] = {
354 "NONE", "DIY_GPS","INAV","APM10"
357 static const char * const lookupTableAltitudePriority[] = {
358 "BARO","GPS"
361 static const char * const lookupTabletelemetryHome[] = {
362 "DEFAULT", "AUTO"
365 #ifdef GPS
366 static const char * const lookupTableGPSProvider[] = {
367 "NMEA", "UBLOX"
370 static const char * const lookupTableGPSSBASMode[] = {
371 "AUTO", "EGNOS", "WAAS", "MSAS", "GAGAN"
373 #endif
375 static const char * const lookupTableCurrentSensor[] = {
376 "NONE", "ADC", "VIRTUAL"
379 static const char * const lookupTableGimbalMode[] = {
380 "NORMAL", "MIXTILT"
383 static const char * const lookupTablePidController[] = {
384 "MW23", "MWREWRITE", "LUX"
387 static const char * const lookupTableBlackboxDevice[] = {
388 "SERIAL", "SPIFLASH"
391 static const char * const lookupTableSerialRX[] = {
392 "SPEK1024",
393 "SPEK2048",
394 "SBUS",
395 "SUMD",
396 "SUMH",
397 "XB-B",
398 "XB-B-RJ01"
402 typedef struct lookupTableEntry_s {
403 const char * const *values;
404 const uint8_t valueCount;
405 } lookupTableEntry_t;
407 typedef enum {
408 TABLE_OFF_ON = 0,
409 TABLE_UNIT,
410 TABLE_ALIGNMENT,
411 TABLE_TELEMETRY_PROVIDER,
412 TABLE_ALTITUDE_PRIORITY,
413 TABLE_TELEMETRY_HOME,
414 #ifdef GPS
415 TABLE_GPS_PROVIDER,
416 TABLE_GPS_SBAS_MODE,
417 #endif
418 #ifdef BLACKBOX
419 TABLE_BLACKBOX_DEVICE,
420 #endif
421 TABLE_CURRENT_SENSOR,
422 TABLE_GIMBAL_MODE,
423 TABLE_PID_CONTROLLER,
424 TABLE_SERIAL_RX,
425 } lookupTableIndex_e;
427 static const lookupTableEntry_t lookupTables[] = {
428 { lookupTableOffOn, sizeof(lookupTableOffOn) / sizeof(char *) },
429 { lookupTableUnit, sizeof(lookupTableUnit) / sizeof(char *) },
430 { lookupTableAlignment, sizeof(lookupTableAlignment) / sizeof(char *) },
431 { lookupTabletelemetryProvider, sizeof(lookupTabletelemetryProvider) / sizeof(char *) },
432 { lookupTableAltitudePriority, sizeof(lookupTableAltitudePriority) / sizeof(char *) },
433 { lookupTabletelemetryHome, sizeof(lookupTabletelemetryHome) / sizeof(char *) },
434 #ifdef GPS
435 { lookupTableGPSProvider, sizeof(lookupTableGPSProvider) / sizeof(char *) },
436 { lookupTableGPSSBASMode, sizeof(lookupTableGPSSBASMode) / sizeof(char *) },
437 #endif
438 #ifdef BLACKBOX
439 { lookupTableBlackboxDevice, sizeof(lookupTableBlackboxDevice) / sizeof(char *) },
440 #endif
441 { lookupTableCurrentSensor, sizeof(lookupTableCurrentSensor) / sizeof(char *) },
442 { lookupTableGimbalMode, sizeof(lookupTableGimbalMode) / sizeof(char *) },
443 { lookupTablePidController, sizeof(lookupTablePidController) / sizeof(char *) },
444 { lookupTableSerialRX, sizeof(lookupTableSerialRX) / sizeof(char *) }
447 #define VALUE_TYPE_OFFSET 0
448 #define VALUE_SECTION_OFFSET 4
449 #define VALUE_MODE_OFFSET 6
451 typedef enum {
452 // value type
453 VAR_UINT8 = (0 << VALUE_TYPE_OFFSET),
454 VAR_INT8 = (1 << VALUE_TYPE_OFFSET),
455 VAR_UINT16 = (2 << VALUE_TYPE_OFFSET),
456 VAR_INT16 = (3 << VALUE_TYPE_OFFSET),
457 VAR_UINT32 = (4 << VALUE_TYPE_OFFSET),
458 VAR_FLOAT = (5 << VALUE_TYPE_OFFSET),
460 // value section
461 MASTER_VALUE = (0 << VALUE_SECTION_OFFSET),
462 PROFILE_VALUE = (1 << VALUE_SECTION_OFFSET),
463 CONTROL_RATE_VALUE = (2 << VALUE_SECTION_OFFSET),
464 HIDDEN_VALUE = (3 << VALUE_SECTION_OFFSET),
466 // value mode
467 MODE_DIRECT = (0 << VALUE_MODE_OFFSET),
468 MODE_LOOKUP = (1 << VALUE_MODE_OFFSET)
469 } cliValueFlag_e;
471 #define VALUE_TYPE_MASK (0x0F)
472 #define VALUE_SECTION_MASK (0x30)
473 #define VALUE_MODE_MASK (0xC0)
475 typedef struct cliMinMaxConfig_s {
476 const int32_t min;
477 const int32_t max;
478 } cliMinMaxConfig_t;
480 typedef struct cliLookupTableConfig_s {
481 const lookupTableIndex_e tableIndex;
482 } cliLookupTableConfig_t;
484 typedef union {
485 cliLookupTableConfig_t lookup;
486 cliMinMaxConfig_t minmax;
488 } cliValueConfig_t;
490 typedef struct {
491 const char *name;
492 const uint8_t type; // see cliValueFlag_e
493 void *ptr;
494 const cliValueConfig_t config;
495 } clivalue_t;
497 const clivalue_t valueTable[] = {
498 { "looptime", VAR_UINT16 | MASTER_VALUE, &masterConfig.looptime, .config.minmax = {0, 9000} },
499 { "emf_avoidance", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.emf_avoidance, .config.lookup = { TABLE_OFF_ON } },
500 { "min_logic_level", VAR_UINT8 | MASTER_VALUE, &masterConfig.min_logic_level, .config.minmax = { 0, 255 } },
501 /*{ "i2c_overclock", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.i2c_overclock, .config.lookup = { TABLE_OFF_ON } },
503 { "mid_rc", VAR_UINT16 | MASTER_VALUE, &masterConfig.rxConfig.midrc, .config.minmax = { 1200, 1700 } },
504 { "min_check", VAR_UINT16 | MASTER_VALUE, &masterConfig.rxConfig.mincheck, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } },
505 { "max_check", VAR_UINT16 | MASTER_VALUE, &masterConfig.rxConfig.maxcheck, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } },*/
506 { "rssi_channel", VAR_INT8 | MASTER_VALUE, &masterConfig.rxConfig.rssi_channel, .config.minmax = { 0, MAX_SUPPORTED_RC_CHANNEL_COUNT } },
507 { "rssi_scale", VAR_UINT8 | MASTER_VALUE, &masterConfig.rxConfig.rssi_scale, .config.minmax = { RSSI_SCALE_MIN, RSSI_SCALE_MAX } },
508 { "rssi_zoom", VAR_UINT8 | MASTER_VALUE, &masterConfig.rxConfig.rssi_zoom, .config.minmax = { 1, 100 } },
509 /* { "rssi_ppm_invert", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.rxConfig.rssi_ppm_invert, .config.lookup = { TABLE_OFF_ON } },
510 { "rc_smoothing", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.rxConfig.rcSmoothing, .config.lookup = { TABLE_OFF_ON } },
511 { "input_filtering_mode", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.inputFilteringMode, .config.lookup = { TABLE_OFF_ON } },
513 { "min_throttle", VAR_UINT16 | MASTER_VALUE, &masterConfig.escAndServoConfig.minthrottle, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } },
514 { "max_throttle", VAR_UINT16 | MASTER_VALUE, &masterConfig.escAndServoConfig.maxthrottle, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } },
515 { "min_command", VAR_UINT16 | MASTER_VALUE, &masterConfig.escAndServoConfig.mincommand, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } },
516 { "servo_center_pulse", VAR_UINT16 | MASTER_VALUE, &masterConfig.escAndServoConfig.servoCenterPulse, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } },
518 { "3d_deadband_low", VAR_UINT16 | MASTER_VALUE, &masterConfig.flight3DConfig.deadband3d_low, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } }, // FIXME upper limit should match code in the mixer, 1500 currently
519 { "3d_deadband_high", VAR_UINT16 | MASTER_VALUE, &masterConfig.flight3DConfig.deadband3d_high, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } }, // FIXME lower limit should match code in the mixer, 1500 currently,
520 { "3d_neutral", VAR_UINT16 | MASTER_VALUE, &masterConfig.flight3DConfig.neutral3d, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } },
521 { "3d_deadband_throttle", VAR_UINT16 | MASTER_VALUE, &masterConfig.flight3DConfig.deadband3d_throttle, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX } },
523 { "motor_pwm_rate", VAR_UINT16 | MASTER_VALUE, &masterConfig.motor_pwm_rate, .config.minmax = { 50, 32000 } },*/
524 /* { "retarded_arm", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.retarded_arm, .config.lookup = { TABLE_OFF_ON } },
525 { "disarm_kill_switch", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.disarm_kill_switch, .config.lookup = { TABLE_OFF_ON } },
526 { "auto_disarm_delay", VAR_UINT8 | MASTER_VALUE, &masterConfig.auto_disarm_delay, .config.minmax = { 0, 60 } },
527 { "small_angle", VAR_UINT8 | MASTER_VALUE, &masterConfig.small_angle, .config.minmax = { 0, 180 } },
529 { "fixedwing_althold_dir", VAR_INT8 | MASTER_VALUE, &masterConfig.airplaneConfig.fixedwing_althold_dir, .config.minmax = { -1, 1 } },
531 { "reboot_character", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.reboot_character, .config.minmax = { 48, 126 } },
533 #ifdef GPS
534 { "gps_baud", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.portConfigs[1].gps_baudrateIndex, .config.minmax = { BAUD_4800, BAUD_250000 } },
535 { "gps_provider", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.gpsConfig.provider, .config.lookup = { TABLE_GPS_PROVIDER } },
536 { "gps_sbas_mode", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.gpsConfig.sbasMode, .config.lookup = { TABLE_GPS_SBAS_MODE } },
537 { "gps_auto_config", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.gpsConfig.autoConfig, .config.lookup = { TABLE_OFF_ON } },
538 { "gps_auto_baud", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.gpsConfig.autoBaud, .config.lookup = { TABLE_OFF_ON } },
539 { "gps_min_sats", VAR_UINT8 | MASTER_VALUE, &masterConfig.gps_min_sats, .config.minmax = { 4, 20 } },
540 { "gps_home_beeper", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.gpsConfig.homeBeeper, .config.lookup = { TABLE_OFF_ON } },
541 { "update_home_by_local_gps", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.update_home_by_local_gps, .config.lookup = { TABLE_OFF_ON } },
543 { "gps_pos_p", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDPOS], .config.minmax = { 0, 200 } },
544 { "gps_pos_i", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDPOS], .config.minmax = { 0, 200 } },
545 { "gps_pos_d", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDPOS], .config.minmax = { 0, 200 } },
546 { "gps_posr_p", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDPOSR], .config.minmax = { 0, 200 } },
547 { "gps_posr_i", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDPOSR], .config.minmax = { 0, 200 } },
548 { "gps_posr_d", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDPOSR], .config.minmax = { 0, 200 } },
549 { "gps_nav_p", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDNAVR], .config.minmax = { 0, 200 } },
550 { "gps_nav_i", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDNAVR], .config.minmax = { 0, 200 } },
551 { "gps_nav_d", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDNAVR], .config.minmax = { 0, 200 } },
552 { "gps_wp_radius", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.gps_wp_radius, .config.minmax = { 0, 2000 } },
553 { "nav_controls_heading", VAR_UINT8 | PROFILE_VALUE | MODE_LOOKUP, &masterConfig.profile[0].gpsProfile.nav_controls_heading, .config.lookup = { TABLE_OFF_ON } },
554 { "nav_speed_min", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.nav_speed_min, .config.minmax = { 10, 2000 } },
555 { "nav_speed_max", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.nav_speed_max, .config.minmax = { 10, 2000 } },
556 { "nav_slew_rate", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.nav_slew_rate, .config.minmax = { 0, 100 } },
558 #endif
560 /* { "serialrx_provider", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.rxConfig.serialrx_provider, .config.lookup = { TABLE_SERIAL_RX } },
561 { "spektrum_sat_bind", VAR_UINT8 | MASTER_VALUE, &masterConfig.rxConfig.spektrum_sat_bind, .config.minmax = { SPEKTRUM_SAT_BIND_DISABLED, SPEKTRUM_SAT_BIND_MAX} },
563 { "telemetry_switch", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.telemetryConfig.telemetry_switch, .config.lookup = { TABLE_OFF_ON } },*/
564 /* { "frsky_default_lattitude", VAR_FLOAT | MASTER_VALUE, &masterConfig.telemetryConfig.gpsNoFixLatitude, .config.minmax = { -90.0, 90.0 } },
565 { "frsky_default_longitude", VAR_FLOAT | MASTER_VALUE, &masterConfig.telemetryConfig.gpsNoFixLongitude, .config.minmax = { -180.0, 180.0 } },
566 { "frsky_coordinates_format", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetryConfig.frsky_coordinate_format, .config.minmax = { 0, FRSKY_FORMAT_NMEA } },
567 { "frsky_unit", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.telemetryConfig.frsky_unit, .config.lookup = { TABLE_UNIT } },
568 { "frsky_vfas_precision", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetryConfig.frsky_vfas_precision, .config.minmax = { FRSKY_VFAS_PRECISION_LOW, FRSKY_VFAS_PRECISION_HIGH } },
569 { "hott_alarm_sound_interval", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetryConfig.hottAlarmSoundInterval, .config.minmax = { 0, 120 } },
571 { "battery_capacity", VAR_UINT16 | MASTER_VALUE, &masterConfig.batteryConfig.batteryCapacity, .config.minmax = { 0, 20000 } },
572 { "vbat_scale", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.vbatscale, .config.minmax = { VBAT_SCALE_MIN, VBAT_SCALE_MAX } },
573 { "vbat_max_cell_voltage", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.vbatmaxcellvoltage, .config.minmax = { 10, 50 } },
574 { "vbat_min_cell_voltage", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.vbatmincellvoltage, .config.minmax = { 10, 50 } },
575 { "vbat_warning_cell_voltage", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.vbatwarningcellvoltage, .config.minmax = { 10, 50 } },
576 // { "current_meter_scale", VAR_INT16 | MASTER_VALUE, &masterConfig.batteryConfig.currentMeterScale, .config.minmax = { -10000, 10000 } },
577 // { "current_meter_offset", VAR_UINT16 | MASTER_VALUE, &masterConfig.batteryConfig.currentMeterOffset, .config.minmax = { 0, 3300 } },
578 // { "multiwii_current_meter_output", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.batteryConfig.multiwiiCurrentMeterOutput, .config.lookup = { TABLE_OFF_ON } },
579 // { "current_meter_type", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.batteryConfig.currentMeterType, .config.lookup = { TABLE_CURRENT_SENSOR } },
580 { "align_gyro", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.sensorAlignmentConfig.gyro_align, .config.lookup = { TABLE_ALIGNMENT } },
581 // { "align_acc", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.sensorAlignmentConfig.acc_align, .config.lookup = { TABLE_ALIGNMENT } },
582 { "align_mag", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.sensorAlignmentConfig.mag_align, .config.lookup = { TABLE_ALIGNMENT } },
584 { "align_board_roll", VAR_INT16 | MASTER_VALUE, &masterConfig.boardAlignment.rollDegrees, .config.minmax = { -180, 360 } },
585 { "align_board_pitch", VAR_INT16 | MASTER_VALUE, &masterConfig.boardAlignment.pitchDegrees, .config.minmax = { -180, 360 } },
586 { "align_board_yaw", VAR_INT16 | MASTER_VALUE, &masterConfig.boardAlignment.yawDegrees, .config.minmax = { -180, 360 } },
587 // { "max_angle_inclination", VAR_UINT16 | MASTER_VALUE, &masterConfig.max_angle_inclination, .config.minmax = { 100, 900 } },
589 { "gyro_lpf", VAR_UINT16 | MASTER_VALUE, &masterConfig.gyro_lpf, .config.minmax = { 0, 256 } },
590 /* { "moron_threshold", VAR_UINT8 | MASTER_VALUE, &masterConfig.gyroConfig.gyroMovementCalibrationThreshold, .config.minmax = { 0, 128 } },
591 { "gyro_cmpf_factor", VAR_UINT16 | MASTER_VALUE, &masterConfig.gyro_cmpf_factor, .config.minmax = { 100, 1000 } },
592 { "gyro_cmpfm_factor", VAR_UINT16 | MASTER_VALUE, &masterConfig.gyro_cmpfm_factor, .config.minmax = { 100, 1000 } },
594 { "alt_hold_deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].rcControlsConfig.alt_hold_deadband, .config.minmax = { 1, 250 } },
595 { "alt_hold_fast_change", VAR_UINT8 | PROFILE_VALUE | MODE_LOOKUP, &masterConfig.profile[0].rcControlsConfig.alt_hold_fast_change, .config.lookup = { TABLE_OFF_ON } },
596 { "deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].rcControlsConfig.deadband, .config.minmax = { 0, 32 } },
597 { "yaw_deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].rcControlsConfig.yaw_deadband, .config.minmax = { 0, 100 } },
599 { "throttle_correction_value", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].throttle_correction_value, .config.minmax = { 0, 150 } },
600 { "throttle_correction_angle", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].throttle_correction_angle, .config.minmax = { 1, 900 } },
602 { "yaw_control_direction", VAR_INT8 | MASTER_VALUE, &masterConfig.yaw_control_direction, .config.minmax = { -1, 1 } },
604 { "pid_at_min_throttle", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.mixerConfig.pid_at_min_throttle, .config.lookup = { TABLE_OFF_ON } },
605 { "yaw_motor_direction", VAR_INT8 | MASTER_VALUE, &masterConfig.mixerConfig.yaw_motor_direction, .config.minmax = { -1, 1 } },
606 { "yaw_jump_prevention_limit", VAR_UINT16 | MASTER_VALUE, &masterConfig.mixerConfig.yaw_jump_prevention_limit, .config.minmax = { YAW_JUMP_PREVENTION_LIMIT_LOW, YAW_JUMP_PREVENTION_LIMIT_HIGH } },
607 #ifdef USE_SERVOS
608 { "tri_unarmed_servo", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.mixerConfig.tri_unarmed_servo, .config.lookup = { TABLE_OFF_ON } },
609 { "servo_lowpass_freq", VAR_INT16 | MASTER_VALUE, &masterConfig.mixerConfig.servo_lowpass_freq, .config.minmax = { 10, 400} },
610 { "servo_lowpass_enable", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.mixerConfig.servo_lowpass_enable, .config.lookup = { TABLE_OFF_ON } },
611 #endif
613 { "default_rate_profile", VAR_UINT8 | PROFILE_VALUE , &masterConfig.profile[0].defaultRateProfileIndex, .config.minmax = { 0, MAX_CONTROL_RATE_PROFILE_COUNT - 1 } },
614 { "rc_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcRate8, .config.minmax = { 0, 250 } },
615 { "rc_expo", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcExpo8, .config.minmax = { 0, 100 } },
616 { "rc_yaw_expo", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcYawExpo8, .config.minmax = { 0, 100 } },
617 { "thr_mid", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].thrMid8, .config.minmax = { 0, 100 } },
618 { "thr_expo", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].thrExpo8, .config.minmax = { 0, 100 } },
619 { "roll_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rates[FD_ROLL], .config.minmax = { 0, CONTROL_RATE_CONFIG_ROLL_PITCH_RATE_MAX } },
620 { "pitch_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rates[FD_PITCH], .config.minmax = { 0, CONTROL_RATE_CONFIG_ROLL_PITCH_RATE_MAX } },
621 { "yaw_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rates[FD_YAW], .config.minmax = { 0, CONTROL_RATE_CONFIG_YAW_RATE_MAX } },
622 { "tpa_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].dynThrPID, .config.minmax = { 0, CONTROL_RATE_CONFIG_TPA_MAX} },
623 { "tpa_breakpoint", VAR_UINT16 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].tpa_breakpoint, .config.minmax = { PWM_RANGE_MIN, PWM_RANGE_MAX} },
625 { "failsafe_delay", VAR_UINT8 | MASTER_VALUE, &masterConfig.failsafeConfig.failsafe_delay, .config.minmax = { 0, 200 } },
626 { "failsafe_off_delay", VAR_UINT8 | MASTER_VALUE, &masterConfig.failsafeConfig.failsafe_off_delay, .config.minmax = { 0, 200 } },
627 { "failsafe_throttle", VAR_UINT16 | MASTER_VALUE, &masterConfig.failsafeConfig.failsafe_throttle, .config.minmax = { PWM_RANGE_MIN, PWM_RANGE_MAX } },
628 { "failsafe_kill_switch", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.failsafeConfig.failsafe_kill_switch, .config.lookup = { TABLE_OFF_ON } },
629 { "failsafe_throttle_low_delay",VAR_UINT16 | MASTER_VALUE, &masterConfig.failsafeConfig.failsafe_throttle_low_delay, .config.minmax = { 0, 300 } },
631 { "rx_min_usec", VAR_UINT16 | MASTER_VALUE, &masterConfig.rxConfig.rx_min_usec, .config.minmax = { PWM_PULSE_MIN, PWM_PULSE_MAX } },
632 { "rx_max_usec", VAR_UINT16 | MASTER_VALUE, &masterConfig.rxConfig.rx_max_usec, .config.minmax = { PWM_PULSE_MIN, PWM_PULSE_MAX } },
634 #ifdef USE_SERVOS
635 { "gimbal_mode", VAR_UINT8 | PROFILE_VALUE | MODE_LOOKUP, &masterConfig.profile[0].gimbalConfig.mode, .config.lookup = { TABLE_GIMBAL_MODE } },
636 #endif
638 { "acc_hardware", VAR_UINT8 | MASTER_VALUE, &masterConfig.acc_hardware, .config.minmax = { 0, ACC_MAX } },
639 { "acc_lpf_factor", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].acc_lpf_factor, .config.minmax = { 0, 250 } },
640 { "accxy_deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].accDeadband.xy, .config.minmax = { 0, 100 } },
641 { "accz_deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].accDeadband.z, .config.minmax = { 0, 100 } },
642 { "accz_lpf_cutoff", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].accz_lpf_cutoff, .config.minmax = { 1, 20 } },
643 { "acc_unarmedcal", VAR_UINT8 | PROFILE_VALUE | MODE_LOOKUP, &masterConfig.profile[0].acc_unarmedcal, .config.lookup = { TABLE_OFF_ON } },
644 { "acc_trim_pitch", VAR_INT16 | PROFILE_VALUE, &masterConfig.profile[0].accelerometerTrims.values.pitch, .config.minmax = { -300, 300 } },
645 { "acc_trim_roll", VAR_INT16 | PROFILE_VALUE, &masterConfig.profile[0].accelerometerTrims.values.roll, .config.minmax = { -300, 300 } },
647 { "baro_tab_size", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].barometerConfig.baro_sample_count, .config.minmax = { 0, BARO_SAMPLE_COUNT_MAX } },
648 { "baro_noise_lpf", VAR_FLOAT | PROFILE_VALUE | MODE_LOOKUP, &masterConfig.profile[0].barometerConfig.baro_noise_lpf, .config.lookup = { TABLE_OFF_ON } },
649 { "baro_cf_vel", VAR_FLOAT | PROFILE_VALUE | MODE_LOOKUP, &masterConfig.profile[0].barometerConfig.baro_cf_vel, .config.lookup = { TABLE_OFF_ON } },
650 { "baro_cf_alt", VAR_FLOAT | PROFILE_VALUE | MODE_LOOKUP, &masterConfig.profile[0].barometerConfig.baro_cf_alt, .config.lookup = { TABLE_OFF_ON } },
651 { "baro_hardware", VAR_UINT8 | MASTER_VALUE, &masterConfig.baro_hardware, .config.minmax = { 0, BARO_MAX } },*/
653 { "mag_hardware", VAR_UINT8 | MASTER_VALUE, &masterConfig.mag_hardware, .config.minmax = { 0, MAG_MAX } },
655 /*{ "pid_controller", VAR_UINT8 | PROFILE_VALUE | MODE_LOOKUP, &masterConfig.profile[0].pidProfile.pidController, .config.lookup = { TABLE_PID_CONTROLLER } },
657 { "p_pitch", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PITCH], .config.minmax = { 0, 200 } },
658 { "i_pitch", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PITCH], .config.minmax = { 0, 200 } },
659 { "d_pitch", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PITCH], .config.minmax = { 0, 200 } },
660 { "p_roll", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[ROLL], .config.minmax = { 0, 200 } },
661 { "i_roll", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[ROLL], .config.minmax = { 0, 200 } },
662 { "d_roll", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[ROLL], .config.minmax = { 0, 200 } },
663 { "p_yaw", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[YAW], .config.minmax = { 0, 200 } },
664 { "i_yaw", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[YAW], .config.minmax = { 0, 200 } },
665 { "d_yaw", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[YAW], .config.minmax = { 0, 200 } },
667 { "p_pitchf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P_f[PITCH], .config.minmax = { 0, 100 } },
668 { "i_pitchf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I_f[PITCH], .config.minmax = { 0, 100 } },
669 { "d_pitchf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D_f[PITCH], .config.minmax = { 0, 100 } },
670 { "p_rollf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P_f[ROLL], .config.minmax = { 0, 100 } },
671 { "i_rollf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I_f[ROLL], .config.minmax = { 0, 100 } },
672 { "d_rollf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D_f[ROLL], .config.minmax = { 0, 100 } },
673 { "p_yawf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P_f[YAW], .config.minmax = { 0, 100 } },
674 { "i_yawf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I_f[YAW], .config.minmax = { 0, 100 } },
675 { "d_yawf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D_f[YAW], .config.minmax = { 0, 100 } },
677 { "yaw_p_limit", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.yaw_p_limit, .config.minmax = { YAW_P_LIMIT_MIN, YAW_P_LIMIT_MAX } },
678 { "level_horizon", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.H_level, .config.minmax = { 0, 10 } },
679 { "level_angle", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.A_level, .config.minmax = { 0, 10 } },
680 { "sensitivity_horizon", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.H_sensitivity, .config.minmax = { 0, 250 } },
682 { "p_alt", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDALT], .config.minmax = { 0, 200 } },
683 { "i_alt", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDALT], .config.minmax = { 0, 200 } },
684 { "d_alt", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDALT], .config.minmax = { 0, 200 } },
686 { "p_level", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDLEVEL], .config.minmax = { 0, 200 } },
687 { "i_level", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDLEVEL], .config.minmax = { 0, 200 } },
688 { "d_level", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDLEVEL], .config.minmax = { 0, 200 } },
690 { "p_vel", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDVEL], .config.minmax = { 0, 200 } },
691 { "i_vel", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDVEL], .config.minmax = { 0, 200 } },
692 { "d_vel", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDVEL], .config.minmax = { 0, 200 } },
694 { "dterm_cut_hz", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.dterm_cut_hz, .config.minmax = {0, 200 } },
695 { "pterm_cut_hz", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.pterm_cut_hz, .config.minmax = {0, 200 } },
696 { "gyro_cut_hz", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gyro_cut_hz, .config.minmax = {0, 200 } },
698 #ifdef GTUNE
699 { "gtune_loP_rll", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_lolimP[FD_ROLL], .config.minmax = { 10, 200 } },
700 { "gtune_loP_ptch", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_lolimP[FD_PITCH], .config.minmax = { 10, 200 } },
701 { "gtune_loP_yw", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_lolimP[FD_YAW], .config.minmax = { 10, 200 } },
702 { "gtune_hiP_rll", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_hilimP[FD_ROLL], .config.minmax = { 0, 200 } },
703 { "gtune_hiP_ptch", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_hilimP[FD_PITCH], .config.minmax = { 0, 200 } },
704 { "gtune_hiP_yw", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_hilimP[FD_YAW], .config.minmax = { 0, 200 } },
705 { "gtune_pwr", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_pwr, .config.minmax = { 0, 10 } },
706 { "gtune_settle_time", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_settle_time, .config.minmax = { 200, 1000 } },
707 { "gtune_average_cycles", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.gtune_average_cycles, .config.minmax = { 8, 128 } },
708 #endif
710 #ifdef BLACKBOX
711 { "blackbox_rate_num", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_rate_num, .config.minmax = { 1, 32 } },
712 { "blackbox_rate_denom", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_rate_denom, .config.minmax = { 1, 32 } },
713 { "blackbox_device", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.blackbox_device, .config.lookup = { TABLE_BLACKBOX_DEVICE } },
714 #endif
716 { "magzero_x", VAR_INT16 | MASTER_VALUE, &masterConfig.magZero.raw[X], .config.minmax = { -32768, 32767 } },
717 { "magzero_y", VAR_INT16 | MASTER_VALUE, &masterConfig.magZero.raw[Y], .config.minmax = { -32768, 32767 } },
718 { "magzero_z", VAR_INT16 | MASTER_VALUE, &masterConfig.magZero.raw[Z], .config.minmax = { -32768, 32767 } },
719 { "mag_declination", VAR_INT16 | MASTER_VALUE, &masterConfig.profile[0].mag_declination, .config.minmax = { -18000, 18000 } },
720 { "mag_calibrated", VAR_UINT8 | MASTER_VALUE, &masterConfig.mag_calibrated, .config.minmax = { 0, 1 } },
721 { "offset", VAR_INT16 | MASTER_VALUE, &masterConfig.offset, .config.minmax = { -360, 360 } },
722 { "offset_trim", VAR_INT8 | MASTER_VALUE, &masterConfig.offset_trim, .config.minmax = { -20, 20 } },
723 { "p", VAR_UINT16 | MASTER_VALUE, &masterConfig.p, .config.minmax = { 0, 50000 } },
724 { "i", VAR_UINT16 | MASTER_VALUE, &masterConfig.i, .config.minmax = { 0, 50000 } },
725 { "d", VAR_UINT16 | MASTER_VALUE, &masterConfig.d, .config.minmax = { 0, 50000 } },
726 { "max_pid_error", VAR_UINT8 | MASTER_VALUE, &masterConfig.max_pid_error, .config.minmax = { 0, 100 } },
727 { "max_pid_accumulator", VAR_UINT16 | MASTER_VALUE, &masterConfig.max_pid_accumulator, .config.minmax = { 0, 50000 } },
728 { "max_pid_gain", VAR_UINT16 | MASTER_VALUE, &masterConfig.max_pid_gain, .config.minmax = { 0, 5000 } },
729 { "pid_divider", VAR_UINT8 | MASTER_VALUE, &masterConfig.max_pid_divider, .config.minmax = { 0, 255 } },
730 { "nopid_min_delta", VAR_FLOAT | MASTER_VALUE, &masterConfig.nopid_min_delta, .config.minmax = { 0, 100 } },
731 { "nopid_map_angle", VAR_UINT8 | MASTER_VALUE, &masterConfig.nopid_map_angle, .config.minmax = { 0, 180 } },
732 { "nopid_max_speed", VAR_UINT16 | MASTER_VALUE, &masterConfig.nopid_max_speed, .config.minmax = { 0, 500 } },
733 { "pan_pin", VAR_UINT8 | MASTER_VALUE, &masterConfig.pan_pin, .config.minmax = { 0, 7 } },
734 { "pan0", VAR_UINT16 | MASTER_VALUE, &masterConfig.pan0, .config.minmax = { 0, 3000 } },
735 { "pan0_calibrated", VAR_UINT8 | MASTER_VALUE, &masterConfig.pan0_calibrated, .config.minmax = { 0, 1 } },
736 { "pan_calibration_pulse", VAR_UINT16 | MASTER_VALUE, &masterConfig.pan_calibration_pulse, .config.minmax = { 1, 1500 } },
737 { "pan_inverted", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.pan_inverted, .config.lookup = { TABLE_OFF_ON } },
738 { "min_pan_speed", VAR_UINT8 | MASTER_VALUE, &masterConfig.min_pan_speed, .config.minmax = { 0, 100 } },
739 { "tilt_pin", VAR_UINT8 | MASTER_VALUE, &masterConfig.tilt_pin, .config.minmax = { 0, 7 } },
740 { "tilt0", VAR_UINT16 | MASTER_VALUE, &masterConfig.tilt0, .config.minmax = { 0, 3000 } },
741 { "tilt90", VAR_UINT16 | MASTER_VALUE, &masterConfig.tilt90, .config.minmax = { 0, 3000 } },
742 { "tilt_max_angle", VAR_UINT8 | MASTER_VALUE, &masterConfig.tilt_max_angle, .config.minmax = { 0, 90 } },
743 { "easing", VAR_UINT8 | MASTER_VALUE, &masterConfig.easing, .config.minmax = { 1, 4 } }, //0: no easing, 1: out-quart function, 2: out-circ function
744 { "easing_steps", VAR_UINT8 | MASTER_VALUE, &masterConfig.easing_steps, .config.minmax = { 0, 100 } },
745 { "easing_min_angle", VAR_UINT8 | MASTER_VALUE, &masterConfig.easing_min_angle, .config.minmax = { 1, 10 } },
746 { "easing_millis", VAR_UINT8 | MASTER_VALUE, &masterConfig.easing_millis, .config.minmax = { 1, 100 } },
747 { "init_servos", VAR_UINT8 | MASTER_VALUE, &masterConfig.init_servos, .config.minmax = { 0, 1 } },
748 { "servo_pwm_rate", VAR_UINT16 | MASTER_VALUE, &masterConfig.servo_pwm_rate, .config.minmax = { 50, 498 } },
749 //{ "telemetry_port", VAR_UINT8 | MASTER_VALUE, &masterConfig.p, .config.minmax = { 0, 3 } },
750 { "telemetry_baud", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.portConfigs[0].msp_baudrateIndex, .config.minmax = { BAUD_1200, BAUD_250000 } },
751 { "telemetry_protocol", VAR_UINT16 | MASTER_VALUE, &masterConfig.telemetry_protocol, .config.minmax = { TP_SERVOTEST, TP_CROSSFIRE } },
752 { "telemetry_min_sats", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetry_min_sats, .config.minmax = { 0, 20 } },
753 { "telemetry_provider", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.telemetry_provider, .config.lookup = {TABLE_TELEMETRY_PROVIDER} },
754 { "telemetry_home" , VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.telemetry_home, .config.lookup = {TABLE_TELEMETRY_HOME} },
755 { "telemetry_altitude_priority", VAR_UINT8 | HIDDEN_VALUE | MODE_LOOKUP, &masterConfig.altitude_priority, .config.lookup = {TABLE_ALTITUDE_PRIORITY} },
756 { "telemetry_inversion", VAR_UINT8 | HIDDEN_VALUE | MODE_LOOKUP, &masterConfig.telemetryConfig.telemetry_inversion, .config.lookup = { TABLE_OFF_ON } },
757 //{ "gps_port", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.portConfigs[0].msp_baudrateIndex, .config.minmax = { 0, 4 } },
758 { "start_tracking_distance", VAR_UINT8 | MASTER_VALUE, &masterConfig.start_tracking_distance, .config.minmax = { 0, 100 } },
759 { "start_tracking_altitude", VAR_UINT8 | MASTER_VALUE, &masterConfig.start_tracking_altitude, .config.minmax = { 0, 100 } },
760 { "eps", VAR_UINT8 | MASTER_VALUE, &masterConfig.eps, .config.minmax = { 1, 3 } },
761 { "eps_distance_gain", VAR_UINT16 | MASTER_VALUE, &masterConfig.eps_gain.distance, .config.minmax = { 1, 1000 } },
762 { "eps_heading_gain", VAR_UINT16 | MASTER_VALUE, &masterConfig.eps_gain.heading, .config.minmax = { 1, 1000 } },
763 { "eps_speed_gain", VAR_UINT16 | MASTER_VALUE, &masterConfig.eps_gain.speed, .config.minmax = { 1, 1000 } },
764 { "eps_frequency", VAR_UINT16 | MASTER_VALUE, &masterConfig.eps_frequency, .config.minmax = { 100, 1000 } },
765 { "eps_interpolation", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &masterConfig.eps_interpolation, .config.lookup = { TABLE_OFF_ON } },
766 { "eps_interpolation_points", VAR_UINT8 | MASTER_VALUE, &masterConfig.eps_interpolation_points, .config.minmax = { 2, 10 } },
767 { "max_speed_filter", VAR_UINT8 | MASTER_VALUE, &masterConfig.max_speed_filter, .config.minmax = { 0, 255 } }
770 #define VALUE_COUNT (sizeof(valueTable) / sizeof(clivalue_t))
773 typedef union {
774 int32_t int_value;
775 float float_value;
776 } int_float_value_t;
778 static void cliSetVar(const clivalue_t *var, const int_float_value_t value);
779 static void cliPrintVar(const clivalue_t *var, uint32_t full);
780 static void cliPrint(const char *str);
781 static void cliWrite(uint8_t ch);
783 static void cliPrompt(void)
785 cliPrint("\r\n# ");
788 static void cliShowParseError(void)
790 cliPrint("Parse error\r\n");
793 static void cliShowArgumentRangeError(char *name, int min, int max)
795 printf("%s must be between %d and %d\r\n", name, min, max);
798 static char *processChannelRangeArgs(char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
800 int val;
802 for (int argIndex = 0; argIndex < 2; argIndex++) {
803 ptr = strchr(ptr, ' ');
804 if (ptr) {
805 val = atoi(++ptr);
806 val = CHANNEL_VALUE_TO_STEP(val);
807 if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
808 if (argIndex == 0) {
809 range->startStep = val;
810 } else {
811 range->endStep = val;
813 (*validArgumentCount)++;
818 return ptr;
821 // Check if a string's length is zero
822 static bool isEmpty(const char *string)
824 return *string == '\0';
827 /*static void cliRxFail(char *cmdline)
829 uint8_t channel;
830 char buf[3];
832 if (isEmpty(cmdline)) {
833 // print out rxConfig failsafe settings
834 for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
835 cliRxFail(itoa(channel, buf, 10));
837 } else {
838 char *ptr = cmdline;
839 channel = atoi(ptr++);
840 if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
842 rxFailsafeChannelConfiguration_t *channelFailsafeConfiguration = &masterConfig.rxConfig.failsafe_channel_configurations[channel];
844 uint16_t value;
845 rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
846 rxFailsafeChannelMode_e mode = channelFailsafeConfiguration->mode;
847 bool requireValue = channelFailsafeConfiguration->mode == RX_FAILSAFE_MODE_SET;
849 ptr = strchr(ptr, ' ');
850 if (ptr) {
851 char *p = strchr(rxFailsafeModeCharacters, *(++ptr));
852 if (p) {
853 uint8_t requestedMode = p - rxFailsafeModeCharacters;
854 mode = rxFailsafeModesTable[type][requestedMode];
855 } else {
856 mode = RX_FAILSAFE_MODE_INVALID;
858 if (mode == RX_FAILSAFE_MODE_INVALID) {
859 cliShowParseError();
860 return;
863 requireValue = mode == RX_FAILSAFE_MODE_SET;
865 ptr = strchr(ptr, ' ');
866 if (ptr) {
867 if (!requireValue) {
868 cliShowParseError();
869 return;
871 value = atoi(++ptr);
872 value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
873 if (value > MAX_RXFAIL_RANGE_STEP) {
874 cliPrint("Value out of range\r\n");
875 return;
878 channelFailsafeConfiguration->step = value;
879 } else if (requireValue) {
880 cliShowParseError();
881 return;
883 channelFailsafeConfiguration->mode = mode;
887 char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfiguration->mode];
889 // triple use of printf below
890 // 1. acknowledge interpretation on command,
891 // 2. query current setting on single item,
892 // 3. recursive use for full list.
894 if (requireValue) {
895 printf("rxfail %u %c %d\r\n",
896 channel,
897 modeCharacter,
898 RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfiguration->step)
900 } else {
901 printf("rxfail %u %c\r\n",
902 channel,
903 modeCharacter
906 } else {
907 cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
912 static void cliAux(char *cmdline)
914 int i, val = 0;
915 char *ptr;
917 if (isEmpty(cmdline)) {
918 // print out aux channel settings
919 for (i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
920 modeActivationCondition_t *mac = &currentProfile->modeActivationConditions[i];
921 printf("aux %u %u %u %u %u\r\n",
923 mac->modeId,
924 mac->auxChannelIndex,
925 MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
926 MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep)
929 } else {
930 ptr = cmdline;
931 i = atoi(ptr++);
932 if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
933 modeActivationCondition_t *mac = &currentProfile->modeActivationConditions[i];
934 uint8_t validArgumentCount = 0;
935 ptr = strchr(ptr, ' ');
936 if (ptr) {
937 val = atoi(++ptr);
938 if (val >= 0 && val < CHECKBOX_ITEM_COUNT) {
939 mac->modeId = val;
940 validArgumentCount++;
943 ptr = strchr(ptr, ' ');
944 if (ptr) {
945 val = atoi(++ptr);
946 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
947 mac->auxChannelIndex = val;
948 validArgumentCount++;
951 ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
953 if (validArgumentCount != 4) {
954 memset(mac, 0, sizeof(modeActivationCondition_t));
956 } else {
957 cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
962 static void cliSerial(char *cmdline)
964 int i, val;
965 char *ptr;
967 if (isEmpty(cmdline)) {
968 for (i = 0; i < SERIAL_PORT_COUNT; i++) {
969 if (!serialIsPortAvailable(masterConfig.serialConfig.portConfigs[i].identifier)) {
970 continue;
972 printf("serial %d %d %ld %ld %ld %ld\r\n" ,
973 masterConfig.serialConfig.portConfigs[i].identifier,
974 masterConfig.serialConfig.portConfigs[i].functionMask,
975 baudRates[masterConfig.serialConfig.portConfigs[i].msp_baudrateIndex],
976 baudRates[masterConfig.serialConfig.portConfigs[i].gps_baudrateIndex],
977 baudRates[masterConfig.serialConfig.portConfigs[i].telemetry_baudrateIndex],
978 baudRates[masterConfig.serialConfig.portConfigs[i].blackbox_baudrateIndex]
981 return;
984 serialPortConfig_t portConfig;
985 memset(&portConfig, 0 , sizeof(portConfig));
987 serialPortConfig_t *currentConfig;
989 uint8_t validArgumentCount = 0;
991 ptr = cmdline;
993 val = atoi(ptr++);
994 currentConfig = serialFindPortConfiguration(val);
995 if (currentConfig) {
996 portConfig.identifier = val;
997 validArgumentCount++;
1000 ptr = strchr(ptr, ' ');
1001 if (ptr) {
1002 val = atoi(++ptr);
1003 portConfig.functionMask = val & 0xFFFF;
1004 validArgumentCount++;
1007 for (i = 0; i < 4; i ++) {
1008 ptr = strchr(ptr, ' ');
1009 if (!ptr) {
1010 break;
1013 val = atoi(++ptr);
1015 uint8_t baudRateIndex = lookupBaudRateIndex(val);
1016 if (baudRates[baudRateIndex] != (uint32_t) val) {
1017 break;
1020 switch(i) {
1021 case 0:
1022 if (baudRateIndex < BAUD_1200 || baudRateIndex > BAUD_250000) {
1023 continue;
1025 portConfig.msp_baudrateIndex = baudRateIndex;
1026 break;
1027 case 1:
1028 if (baudRateIndex < BAUD_4800 || baudRateIndex > BAUD_115200) {
1029 continue;
1031 portConfig.gps_baudrateIndex = baudRateIndex;
1032 break;
1033 case 2:
1034 if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_250000) {
1035 continue;
1037 portConfig.telemetry_baudrateIndex = baudRateIndex;
1038 break;
1039 case 3:
1040 if (baudRateIndex < BAUD_4800 || baudRateIndex > BAUD_115200) {
1041 continue;
1043 portConfig.blackbox_baudrateIndex = baudRateIndex;
1044 break;
1047 validArgumentCount++;
1050 if (validArgumentCount < 6) {
1051 cliShowParseError();
1052 return;
1055 memcpy(currentConfig, &portConfig, sizeof(portConfig));
1059 static void cliAdjustmentRange(char *cmdline)
1061 int i, val = 0;
1062 char *ptr;
1064 if (isEmpty(cmdline)) {
1065 // print out adjustment ranges channel settings
1066 for (i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
1067 adjustmentRange_t *ar = &currentProfile->adjustmentRanges[i];
1068 printf("adjrange %u %u %u %u %u %u %u\r\n",
1070 ar->adjustmentIndex,
1071 ar->auxChannelIndex,
1072 MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
1073 MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
1074 ar->adjustmentFunction,
1075 ar->auxSwitchChannelIndex
1078 } else {
1079 ptr = cmdline;
1080 i = atoi(ptr++);
1081 if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
1082 adjustmentRange_t *ar = &currentProfile->adjustmentRanges[i];
1083 uint8_t validArgumentCount = 0;
1085 ptr = strchr(ptr, ' ');
1086 if (ptr) {
1087 val = atoi(++ptr);
1088 if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
1089 ar->adjustmentIndex = val;
1090 validArgumentCount++;
1093 ptr = strchr(ptr, ' ');
1094 if (ptr) {
1095 val = atoi(++ptr);
1096 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1097 ar->auxChannelIndex = val;
1098 validArgumentCount++;
1102 ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
1104 ptr = strchr(ptr, ' ');
1105 if (ptr) {
1106 val = atoi(++ptr);
1107 if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
1108 ar->adjustmentFunction = val;
1109 validArgumentCount++;
1112 ptr = strchr(ptr, ' ');
1113 if (ptr) {
1114 val = atoi(++ptr);
1115 if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
1116 ar->auxSwitchChannelIndex = val;
1117 validArgumentCount++;
1121 if (validArgumentCount != 6) {
1122 memset(ar, 0, sizeof(adjustmentRange_t));
1123 cliShowParseError();
1125 } else {
1126 cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
1131 static void cliMotorMix(char *cmdline)
1133 #ifdef USE_QUAD_MIXER_ONLY
1134 UNUSED(cmdline);
1135 #else
1136 int i, check = 0;
1137 int num_motors = 0;
1138 uint8_t len;
1139 char buf[16];
1140 char *ptr;
1142 if (isEmpty(cmdline)) {
1143 cliPrint("Motor\tThr\tRoll\tPitch\tYaw\r\n");
1144 for (i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1145 if (masterConfig.customMotorMixer[i].throttle == 0.0f)
1146 break;
1147 num_motors++;
1148 printf("#%d:\t", i);
1149 printf("%s\t", ftoa(masterConfig.customMotorMixer[i].throttle, buf));
1150 printf("%s\t", ftoa(masterConfig.customMotorMixer[i].roll, buf));
1151 printf("%s\t", ftoa(masterConfig.customMotorMixer[i].pitch, buf));
1152 printf("%s\r\n", ftoa(masterConfig.customMotorMixer[i].yaw, buf));
1154 return;
1155 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1156 // erase custom mixer
1157 for (i = 0; i < MAX_SUPPORTED_MOTORS; i++)
1158 masterConfig.customMotorMixer[i].throttle = 0.0f;
1159 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1160 ptr = strchr(cmdline, ' ');
1161 if (ptr) {
1162 len = strlen(++ptr);
1163 for (i = 0; ; i++) {
1164 if (mixerNames[i] == NULL) {
1165 cliPrint("Invalid name\r\n");
1166 break;
1168 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1169 mixerLoadMix(i, masterConfig.customMotorMixer);
1170 printf("Loaded %s\r\n", mixerNames[i]);
1171 cliMotorMix("");
1172 break;
1176 } else {
1177 ptr = cmdline;
1178 i = atoi(ptr); // get motor number
1179 if (i < MAX_SUPPORTED_MOTORS) {
1180 ptr = strchr(ptr, ' ');
1181 if (ptr) {
1182 masterConfig.customMotorMixer[i].throttle = fastA2F(++ptr);
1183 check++;
1185 ptr = strchr(ptr, ' ');
1186 if (ptr) {
1187 masterConfig.customMotorMixer[i].roll = fastA2F(++ptr);
1188 check++;
1190 ptr = strchr(ptr, ' ');
1191 if (ptr) {
1192 masterConfig.customMotorMixer[i].pitch = fastA2F(++ptr);
1193 check++;
1195 ptr = strchr(ptr, ' ');
1196 if (ptr) {
1197 masterConfig.customMotorMixer[i].yaw = fastA2F(++ptr);
1198 check++;
1200 if (check != 4) {
1201 cliShowParseError();
1202 } else {
1203 cliMotorMix("");
1205 } else {
1206 cliShowArgumentRangeError("index", 1, MAX_SUPPORTED_MOTORS);
1209 #endif
1212 static void cliRxRange(char *cmdline)
1214 int i, validArgumentCount = 0;
1215 char *ptr;
1217 if (isEmpty(cmdline)) {
1218 for (i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
1219 rxChannelRangeConfiguration_t *channelRangeConfiguration = &masterConfig.rxConfig.channelRanges[i];
1220 printf("rxrange %u %u %u\r\n", i, channelRangeConfiguration->min, channelRangeConfiguration->max);
1222 } else if (strcasecmp(cmdline, "reset") == 0) {
1223 resetAllRxChannelRangeConfigurations(masterConfig.rxConfig.channelRanges);
1224 } else {
1225 ptr = cmdline;
1226 i = atoi(ptr);
1227 if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
1228 int rangeMin, rangeMax;
1230 ptr = strchr(ptr, ' ');
1231 if (ptr) {
1232 rangeMin = atoi(++ptr);
1233 validArgumentCount++;
1236 ptr = strchr(ptr, ' ');
1237 if (ptr) {
1238 rangeMax = atoi(++ptr);
1239 validArgumentCount++;
1242 if (validArgumentCount != 2) {
1243 cliShowParseError();
1244 } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
1245 cliShowParseError();
1246 } else {
1247 rxChannelRangeConfiguration_t *channelRangeConfiguration = &masterConfig.rxConfig.channelRanges[i];
1248 channelRangeConfiguration->min = rangeMin;
1249 channelRangeConfiguration->max = rangeMax;
1251 } else {
1252 cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
1257 #ifdef LED_STRIP
1258 static void cliLed(char *cmdline)
1260 int i;
1261 char *ptr;
1262 char ledConfigBuffer[20];
1264 if (isEmpty(cmdline)) {
1265 for (i = 0; i < MAX_LED_STRIP_LENGTH; i++) {
1266 generateLedConfig(i, ledConfigBuffer, sizeof(ledConfigBuffer));
1267 printf("led %u %s\r\n", i, ledConfigBuffer);
1269 } else {
1270 ptr = cmdline;
1271 i = atoi(ptr);
1272 if (i < MAX_LED_STRIP_LENGTH) {
1273 ptr = strchr(cmdline, ' ');
1274 if (!parseLedStripConfig(i, ++ptr)) {
1275 cliShowParseError();
1277 } else {
1278 cliShowArgumentRangeError("index", 0, MAX_LED_STRIP_LENGTH - 1);
1283 static void cliColor(char *cmdline)
1285 int i;
1286 char *ptr;
1288 if (isEmpty(cmdline)) {
1289 for (i = 0; i < CONFIGURABLE_COLOR_COUNT; i++) {
1290 printf("color %u %d,%u,%u\r\n",
1292 masterConfig.colors[i].h,
1293 masterConfig.colors[i].s,
1294 masterConfig.colors[i].v
1297 } else {
1298 ptr = cmdline;
1299 i = atoi(ptr);
1300 if (i < CONFIGURABLE_COLOR_COUNT) {
1301 ptr = strchr(cmdline, ' ');
1302 if (!parseColor(i, ++ptr)) {
1303 cliShowParseError();
1305 } else {
1306 cliShowArgumentRangeError("index", 0, CONFIGURABLE_COLOR_COUNT - 1);
1310 #endif
1312 #ifdef USE_SERVOS
1313 static void cliServo(char *cmdline)
1315 enum { SERVO_ARGUMENT_COUNT = 8 };
1316 int16_t arguments[SERVO_ARGUMENT_COUNT];
1318 servoParam_t *servo;
1320 int i;
1321 char *ptr;
1323 if (isEmpty(cmdline)) {
1324 // print out servo settings
1325 for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1326 servo = &currentProfile->servoConf[i];
1328 printf("servo %u %d %d %d %d %d %d %d\r\n",
1330 servo->min,
1331 servo->max,
1332 servo->middle,
1333 servo->angleAtMin,
1334 servo->angleAtMax,
1335 servo->rate,
1336 servo->forwardFromChannel
1339 } else {
1340 int validArgumentCount = 0;
1342 ptr = cmdline;
1344 // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
1346 // If command line doesn't fit the format, don't modify the config
1347 while (*ptr) {
1348 if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
1349 if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
1350 cliShowParseError();
1351 return;
1354 arguments[validArgumentCount++] = atoi(ptr);
1356 do {
1357 ptr++;
1358 } while (*ptr >= '0' && *ptr <= '9');
1359 } else if (*ptr == ' ') {
1360 ptr++;
1361 } else {
1362 cliShowParseError();
1363 return;
1367 enum {INDEX = 0, MIN, MAX, MIDDLE, ANGLE_AT_MIN, ANGLE_AT_MAX, RATE, FORWARD};
1369 i = arguments[INDEX];
1371 // Check we got the right number of args and the servo index is correct (don't validate the other values)
1372 if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
1373 cliShowParseError();
1374 return;
1377 servo = &currentProfile->servoConf[i];
1379 if (
1380 arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
1381 arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
1382 arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
1383 arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
1384 arguments[RATE] < -100 || arguments[RATE] > 100 ||
1385 arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT ||
1386 arguments[ANGLE_AT_MIN] < 0 || arguments[ANGLE_AT_MIN] > 180 ||
1387 arguments[ANGLE_AT_MAX] < 0 || arguments[ANGLE_AT_MAX] > 180
1389 cliShowParseError();
1390 return;
1393 servo->min = arguments[1];
1394 servo->max = arguments[2];
1395 servo->middle = arguments[3];
1396 servo->angleAtMin = arguments[4];
1397 servo->angleAtMax = arguments[5];
1398 servo->rate = arguments[6];
1399 servo->forwardFromChannel = arguments[7];
1402 #endif
1404 #ifdef USE_SERVOS
1405 static void cliServoMix(char *cmdline)
1407 int i;
1408 uint8_t len;
1409 char *ptr;
1410 int args[8], check = 0;
1411 len = strlen(cmdline);
1413 if (len == 0) {
1415 cliPrint("Rule\tServo\tSource\tRate\tSpeed\tMin\tMax\tBox\r\n");
1417 for (i = 0; i < MAX_SERVO_RULES; i++) {
1418 if (masterConfig.customServoMixer[i].rate == 0)
1419 break;
1421 printf("#%d:\t%d\t%d\t%d\t%d\t%d\t%d\t%d\r\n",
1423 masterConfig.customServoMixer[i].targetChannel,
1424 masterConfig.customServoMixer[i].inputSource,
1425 masterConfig.customServoMixer[i].rate,
1426 masterConfig.customServoMixer[i].speed,
1427 masterConfig.customServoMixer[i].min,
1428 masterConfig.customServoMixer[i].max,
1429 masterConfig.customServoMixer[i].box
1432 printf("\r\n");
1433 return;
1434 } else if (strncasecmp(cmdline, "reset", 5) == 0) {
1435 // erase custom mixer
1436 memset(masterConfig.customServoMixer, 0, sizeof(masterConfig.customServoMixer));
1437 for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1438 currentProfile->servoConf[i].reversedSources = 0;
1440 } else if (strncasecmp(cmdline, "load", 4) == 0) {
1441 ptr = strchr(cmdline, ' ');
1442 if (ptr) {
1443 len = strlen(++ptr);
1444 for (i = 0; ; i++) {
1445 if (mixerNames[i] == NULL) {
1446 printf("Invalid name\r\n");
1447 break;
1449 if (strncasecmp(ptr, mixerNames[i], len) == 0) {
1450 servoMixerLoadMix(i, masterConfig.customServoMixer);
1451 printf("Loaded %s\r\n", mixerNames[i]);
1452 cliServoMix("");
1453 break;
1457 } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
1458 enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
1459 int servoIndex, inputSource;
1460 ptr = strchr(cmdline, ' ');
1462 len = strlen(ptr);
1463 if (len == 0) {
1464 printf("s");
1465 for (inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1466 printf("\ti%d", inputSource);
1467 printf("\r\n");
1469 for (servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
1470 printf("%d", servoIndex);
1471 for (inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
1472 printf("\t%s ", (currentProfile->servoConf[servoIndex].reversedSources & (1 << inputSource)) ? "r" : "n");
1473 printf("\r\n");
1475 return;
1478 ptr = strtok(ptr, " ");
1479 while (ptr != NULL && check < ARGS_COUNT - 1) {
1480 args[check++] = atoi(ptr);
1481 ptr = strtok(NULL, " ");
1484 if (ptr == NULL || check != ARGS_COUNT - 1) {
1485 cliShowParseError();
1486 return;
1489 if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
1490 && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
1491 && (*ptr == 'r' || *ptr == 'n')) {
1492 if (*ptr == 'r')
1493 currentProfile->servoConf[args[SERVO]].reversedSources |= 1 << args[INPUT];
1494 else
1495 currentProfile->servoConf[args[SERVO]].reversedSources &= ~(1 << args[INPUT]);
1496 } else
1497 cliShowParseError();
1499 cliServoMix("reverse");
1500 } else {
1501 enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
1502 ptr = strtok(cmdline, " ");
1503 while (ptr != NULL && check < ARGS_COUNT) {
1504 args[check++] = atoi(ptr);
1505 ptr = strtok(NULL, " ");
1508 if (ptr != NULL || check != ARGS_COUNT) {
1509 cliShowParseError();
1510 return;
1513 i = args[RULE];
1514 if (i >= 0 && i < MAX_SERVO_RULES &&
1515 args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
1516 args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
1517 args[RATE] >= -100 && args[RATE] <= 100 &&
1518 args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
1519 args[MIN] >= 0 && args[MIN] <= 100 &&
1520 args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
1521 args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
1522 masterConfig.customServoMixer[i].targetChannel = args[TARGET];
1523 masterConfig.customServoMixer[i].inputSource = args[INPUT];
1524 masterConfig.customServoMixer[i].rate = args[RATE];
1525 masterConfig.customServoMixer[i].speed = args[SPEED];
1526 masterConfig.customServoMixer[i].min = args[MIN];
1527 masterConfig.customServoMixer[i].max = args[MAX];
1528 masterConfig.customServoMixer[i].box = args[BOX];
1529 cliServoMix("");
1530 } else {
1531 cliShowParseError();
1535 #endif
1538 #ifdef USE_FLASHFS
1540 static void cliFlashInfo(char *cmdline)
1542 const flashGeometry_t *layout = flashfsGetGeometry();
1544 UNUSED(cmdline);
1546 printf("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u\r\n",
1547 layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
1550 static void cliFlashErase(char *cmdline)
1552 UNUSED(cmdline);
1554 printf("Erasing...\r\n");
1555 flashfsEraseCompletely();
1557 while (!flashfsIsReady()) {
1558 delay(100);
1561 printf("Done.\r\n");
1564 #ifdef USE_FLASH_TOOLS
1566 static void cliFlashWrite(char *cmdline)
1568 uint32_t address = atoi(cmdline);
1569 char *text = strchr(cmdline, ' ');
1571 if (!text) {
1572 cliShowParseError();
1573 } else {
1574 flashfsSeekAbs(address);
1575 flashfsWrite((uint8_t*)text, strlen(text), true);
1576 flashfsFlushSync();
1578 printf("Wrote %u bytes at %u.\r\n", strlen(text), address);
1583 static void cliFlashRead(char *cmdline)
1585 uint32_t address = atoi(cmdline);
1586 uint32_t length;
1587 int i;
1589 uint8_t buffer[32];
1591 char *nextArg = strchr(cmdline, ' ');
1593 if (!nextArg) {
1594 cliShowParseError();
1595 } else {
1596 length = atoi(nextArg);
1598 printf("Reading %u bytes at %u:\r\n", length, address);
1600 while (length > 0) {
1601 int bytesRead;
1603 bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
1605 for (i = 0; i < bytesRead; i++) {
1606 cliWrite(buffer[i]);
1609 length -= bytesRead;
1610 address += bytesRead;
1612 if (bytesRead == 0) {
1613 //Assume we reached the end of the volume or something fatal happened
1614 break;
1617 printf("\r\n");
1621 #endif
1622 #endif*/
1625 static void dumpValues(uint16_t valueSection)
1627 uint32_t i;
1628 const clivalue_t *value;
1629 for (i = 0; i < VALUE_COUNT; i++) {
1630 value = &valueTable[i];
1632 if ((value->type & VALUE_SECTION_MASK) != valueSection) {
1633 continue;
1636 printf("set %s = ", valueTable[i].name);
1637 cliPrintVar(value, 0);
1638 cliPrint("\r\n");
1642 typedef enum {
1643 DUMP_MASTER = (1 << 0),
1644 DUMP_PROFILE = (1 << 1),
1645 DUMP_CONTROL_RATE_PROFILE = (1 << 2),
1646 DUMP_TRACKER = (1 << 3)
1647 } dumpFlags_e;
1649 #define DUMP_ALL (DUMP_MASTER | DUMP_PROFILE | DUMP_CONTROL_RATE_PROFILE | DUMP_TRACKER )
1652 static const char* const sectionBreak = "\r\n";
1654 #define printSectionBreak() printf((char *)sectionBreak)
1656 /*static void cliDump(char *cmdline)
1658 unsigned int i;
1659 char buf[16];
1660 uint32_t mask;
1662 #ifndef USE_QUAD_MIXER_ONLY
1663 float thr, roll, pitch, yaw;
1664 #endif
1666 uint8_t dumpMask = DUMP_ALL;
1667 if (strcasecmp(cmdline, "master") == 0) {
1668 dumpMask = DUMP_MASTER; // only
1670 if (strcasecmp(cmdline, "profile") == 0) {
1671 dumpMask = DUMP_PROFILE; // only
1673 if (strcasecmp(cmdline, "rates") == 0) {
1674 dumpMask = DUMP_CONTROL_RATE_PROFILE; // only
1677 if (strcasecmp(cmdline, "tracker") == 0) {
1678 dumpMask = DUMP_TRACKER; // only
1680 if (dumpMask & DUMP_MASTER) {
1682 cliPrint("\r\n# version\r\n");
1683 cliVersion(NULL);
1685 cliPrint("\r\n# dump master\r\n");
1686 cliPrint("\r\n# mixer\r\n");
1688 #ifndef USE_QUAD_MIXER_ONLY
1689 printf("mixer %s\r\n", mixerNames[masterConfig.mixerMode - 1]);
1691 printf("mmix reset\r\n");
1693 for (i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
1694 if (masterConfig.customMotorMixer[i].throttle == 0.0f)
1695 break;
1696 thr = masterConfig.customMotorMixer[i].throttle;
1697 roll = masterConfig.customMotorMixer[i].roll;
1698 pitch = masterConfig.customMotorMixer[i].pitch;
1699 yaw = masterConfig.customMotorMixer[i].yaw;
1700 printf("mmix %d", i);
1701 if (thr < 0)
1702 cliWrite(' ');
1703 printf("%s", ftoa(thr, buf));
1704 if (roll < 0)
1705 cliWrite(' ');
1706 printf("%s", ftoa(roll, buf));
1707 if (pitch < 0)
1708 cliWrite(' ');
1709 printf("%s", ftoa(pitch, buf));
1710 if (yaw < 0)
1711 cliWrite(' ');
1712 printf("%s\r\n", ftoa(yaw, buf));
1715 // print custom servo mixer if exists
1716 printf("smix reset\r\n");
1718 for (i = 0; i < MAX_SERVO_RULES; i++) {
1720 if (masterConfig.customServoMixer[i].rate == 0)
1721 break;
1723 printf("smix %d %d %d %d %d %d %d %d\r\n",
1725 masterConfig.customServoMixer[i].targetChannel,
1726 masterConfig.customServoMixer[i].inputSource,
1727 masterConfig.customServoMixer[i].rate,
1728 masterConfig.customServoMixer[i].speed,
1729 masterConfig.customServoMixer[i].min,
1730 masterConfig.customServoMixer[i].max,
1731 masterConfig.customServoMixer[i].box
1735 #endif
1737 cliPrint("\r\n\r\n# feature\r\n");
1739 mask = featureMask();
1740 for (i = 0; ; i++) { // disable all feature first
1741 if (featureNames[i] == NULL)
1742 break;
1743 printf("feature -%s\r\n", featureNames[i]);
1745 for (i = 0; ; i++) { // reenable what we want.
1746 if (featureNames[i] == NULL)
1747 break;
1748 if (mask & (1 << i))
1749 printf("feature %s\r\n", featureNames[i]);
1752 cliPrint("\r\n\r\n# map\r\n");
1754 for (i = 0; i < 8; i++)
1755 buf[masterConfig.rxConfig.rcmap[i]] = rcChannelLetters[i];
1756 buf[i] = '\0';
1757 printf("map %s\r\n", buf);
1759 cliPrint("\r\n\r\n# serial\r\n");
1760 cliSerial("");
1762 #ifdef LED_STRIP
1763 cliPrint("\r\n\r\n# led\r\n");
1764 cliLed("");
1766 cliPrint("\r\n\r\n# color\r\n");
1767 cliColor("");
1768 #endif
1769 printSectionBreak();
1770 dumpValues(MASTER_VALUE);
1772 cliPrint("\r\n# rxfail\r\n");
1773 cliRxFail("");
1776 if (dumpMask & DUMP_PROFILE) {
1777 cliPrint("\r\n# dump profile\r\n");
1779 cliPrint("\r\n# profile\r\n");
1780 cliProfile("");
1782 cliPrint("\r\n# aux\r\n");
1784 cliAux("");
1786 cliPrint("\r\n# adjrange\r\n");
1788 cliAdjustmentRange("");
1790 printf("\r\n# rxrange\r\n");
1792 cliRxRange("");
1794 #ifdef USE_SERVOS
1795 cliPrint("\r\n# servo\r\n");
1797 cliServo("");
1799 // print servo directions
1800 unsigned int channel;
1802 for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
1803 for (channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
1804 if (servoDirection(i, channel) < 0) {
1805 printf("smix reverse %d %d r\r\n", i , channel);
1809 #endif
1811 printSectionBreak();
1813 dumpValues(PROFILE_VALUE);
1816 if (dumpMask & DUMP_CONTROL_RATE_PROFILE) {
1817 cliPrint("\r\n# dump rates\r\n");
1819 cliPrint("\r\n# rateprofile\r\n");
1820 cliRateProfile("");
1822 printSectionBreak();
1824 dumpValues(CONTROL_RATE_VALUE);
1829 void cliEnter(serialPort_t *serialPort)
1831 cliMode = 1;
1832 cliPort = serialPort;
1833 setPrintfSerialPort(cliPort);
1834 cliPrint("\r\nEntering CLI Mode, type 'exit' to return, or 'help'\r\n");
1835 cliPrompt();
1836 /*ENABLE_ARMING_FLAG(PREVENT_ARMING);*/
1837 DISABLE_PROTOCOL(masterConfig.telemetry_protocol);
1838 ENABLE_PROTOCOL(TP_SERVOTEST);
1840 displayCliMode();
1842 if(masterConfig.pan0_calibrated==0) cliPrint("\r\nYou must adjust the pan0 value\r\n");
1845 static void cliExit(char *cmdline)
1847 DISABLE_PROTOCOL(TP_SERVOTEST);
1848 ENABLE_PROTOCOL(masterConfig.telemetry_protocol);
1850 UNUSED(cmdline);
1852 cliPrint("\r\nLeaving CLI mode, unsaved changes lost.\r\n");
1853 *cliBuffer = '\0';
1854 bufferIndex = 0;
1855 cliMode = 0;
1856 // incase a motor was left running during motortest, clear it here
1857 mixerResetDisarmedMotors();
1858 cliReboot();
1860 cliPort = NULL;
1861 if(feature(FEATURE_DISPLAY)){
1862 displayResetPageCycling();
1863 displayEnablePageCycling();
1867 static void cliFeature(char *cmdline)
1869 uint32_t i;
1870 uint32_t len;
1871 uint32_t mask;
1873 len = strlen(cmdline);
1874 mask = featureMask();
1876 if (len == 0) {
1877 cliPrint("Enabled: ");
1878 for (i = 0; ; i++) {
1879 if (featureNames[i] == NULL)
1880 break;
1881 if (mask & (1 << i))
1882 printf("%s ", featureNames[i]);
1884 cliPrint("\r\n");
1885 } else if (strncasecmp(cmdline, "list", len) == 0) {
1886 cliPrint("Available: ");
1887 for (i = 0; ; i++) {
1888 if (featureNames[i] == NULL)
1889 break;
1890 printf("%s ", featureNames[i]);
1892 cliPrint("\r\n");
1893 return;
1894 } else {
1895 bool remove = false;
1896 if (cmdline[0] == '-') {
1897 // remove feature
1898 remove = true;
1899 cmdline++; // skip over -
1900 len--;
1903 for (i = 0; ; i++) {
1904 if (featureNames[i] == NULL) {
1905 cliPrint("Invalid name\r\n");
1906 break;
1909 if (strncasecmp(cmdline, featureNames[i], len) == 0) {
1911 mask = 1 << i;
1912 #ifndef GPS
1913 if (mask & FEATURE_GPS) {
1914 cliPrint("unavailable\r\n");
1915 break;
1917 #endif
1918 #ifndef SONAR
1919 if (mask & FEATURE_SONAR) {
1920 cliPrint("unavailable\r\n");
1921 break;
1923 #endif
1924 if (remove) {
1925 featureClear(mask);
1926 cliPrint("Disabled");
1927 } else {
1928 featureSet(mask);
1929 cliPrint("Enabled");
1931 printf(" %s\r\n", featureNames[i]);
1932 break;
1938 /*#ifdef GPS
1939 static void cliGpsPassthrough(char *cmdline)
1941 UNUSED(cmdline);
1943 gpsEnablePassthrough(cliPort);
1945 #endif*/
1947 static void cliHelp(char *cmdline)
1949 uint32_t i = 0;
1951 UNUSED(cmdline);
1953 for (i = 0; i < CMD_COUNT; i++) {
1954 cliPrint(cmdTable[i].name);
1955 #ifndef SKIP_CLI_COMMAND_HELP
1956 if (cmdTable[i].description) {
1957 printf(" - %s", cmdTable[i].description);
1959 if (cmdTable[i].args) {
1960 printf("\r\n\t%s", cmdTable[i].args);
1962 #endif
1963 cliPrint("\r\n");
1965 printf("\r\n# Bauds:\r\n");
1966 for(i=0;i< BAUD_250000-1;i++)
1967 printf("# %u: %u\r\n",i,baudRates[i]);
1968 cliPrint("\r\n");
1970 printf("\r\n# Outgoing Telemetry:\r\n");
1971 printf("%u: MFD\r\n",FUNCTION_TELEMETRY_MFD);
1972 printf("%u: MAVLINK\r\n",FUNCTION_TELEMETRY_MAVLINK);
1973 printf("%u: NMEA\r\n",FUNCTION_TELEMETRY_NMEA);
1974 printf("%u: LTM\r\n",FUNCTION_TELEMETRY_LTM);
1975 cliPrint("\r\n");
1979 /*static void cliMap(char *cmdline)
1981 uint32_t len;
1982 uint32_t i;
1983 char out[9];
1985 len = strlen(cmdline);
1987 if (len == 8) {
1988 // uppercase it
1989 for (i = 0; i < 8; i++)
1990 cmdline[i] = toupper((unsigned char)cmdline[i]);
1991 for (i = 0; i < 8; i++) {
1992 if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i]))
1993 continue;
1994 cliShowParseError();
1995 return;
1997 parseRcChannels(cmdline, &masterConfig.rxConfig);
1999 cliPrint("Map: ");
2000 for (i = 0; i < 8; i++)
2001 out[masterConfig.rxConfig.rcmap[i]] = rcChannelLetters[i];
2002 out[i] = '\0';
2003 printf("%s\r\n", out);
2006 #ifndef USE_QUAD_MIXER_ONLY
2007 static void cliMixer(char *cmdline)
2009 int i;
2010 int len;
2012 len = strlen(cmdline);
2014 if (len == 0) {
2015 printf("Mixer: %s\r\n", mixerNames[masterConfig.mixerMode - 1]);
2016 return;
2017 } else if (strncasecmp(cmdline, "list", len) == 0) {
2018 cliPrint("Available mixers: ");
2019 for (i = 0; ; i++) {
2020 if (mixerNames[i] == NULL)
2021 break;
2022 printf("%s ", mixerNames[i]);
2024 cliPrint("\r\n");
2025 return;
2028 for (i = 0; ; i++) {
2029 if (mixerNames[i] == NULL) {
2030 cliPrint("Invalid name\r\n");
2031 return;
2033 if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
2034 masterConfig.mixerMode = i + 1;
2035 break;
2039 cliMixer("");
2041 #endif
2043 static void cliMotor(char *cmdline)
2045 int motor_index = 0;
2046 int motor_value = 0;
2047 int index = 0;
2048 char *pch = NULL;
2049 char *saveptr;
2051 if (isEmpty(cmdline)) {
2052 cliShowParseError();
2053 return;
2056 pch = strtok_r(cmdline, " ", &saveptr);
2057 while (pch != NULL) {
2058 switch (index) {
2059 case 0:
2060 motor_index = atoi(pch);
2061 break;
2062 case 1:
2063 motor_value = atoi(pch);
2064 break;
2066 index++;
2067 pch = strtok_r(NULL, " ", &saveptr);
2070 if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
2071 cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS);
2072 return;
2075 if (index == 2) {
2076 if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
2077 cliShowArgumentRangeError("value", 1000, 2000);
2078 return;
2079 } else {
2080 motor_disarmed[motor_index] = motor_value;
2084 printf("motor %d: %d\r\n", motor_index, motor_disarmed[motor_index]);
2087 static void cliPlaySound(char *cmdline)
2089 #if FLASH_SIZE <= 64
2090 UNUSED(cmdline);
2091 #else
2092 int i;
2093 const char *name;
2094 static int lastSoundIdx = -1;
2096 if (isEmpty(cmdline)) {
2097 i = lastSoundIdx + 1; //next sound index
2098 if ((name=beeperNameForTableIndex(i)) == NULL) {
2099 while (true) { //no name for index; try next one
2100 if (++i >= beeperTableEntryCount())
2101 i = 0; //if end then wrap around to first entry
2102 if ((name=beeperNameForTableIndex(i)) != NULL)
2103 break; //if name OK then play sound below
2104 if (i == lastSoundIdx + 1) { //prevent infinite loop
2105 printf("Error playing sound\r\n");
2106 return;
2110 } else { //index value was given
2111 i = atoi(cmdline);
2112 if ((name=beeperNameForTableIndex(i)) == NULL) {
2113 printf("No sound for index %d\r\n", i);
2114 return;
2117 lastSoundIdx = i;
2118 beeperSilence();
2119 printf("Playing sound %d: %s\r\n", i, name);
2120 beeper(beeperModeForTableIndex(i));
2121 #endif
2124 static void cliProfile(char *cmdline)
2126 int i;
2128 if (isEmpty(cmdline)) {
2129 printf("profile %d\r\n", getCurrentProfile());
2130 return;
2131 } else {
2132 i = atoi(cmdline);
2133 if (i >= 0 && i < MAX_PROFILE_COUNT) {
2134 masterConfig.current_profile_index = i;
2135 writeEEPROM();
2136 readEEPROM();
2137 cliProfile("");
2142 static void cliRateProfile(char *cmdline)
2144 int i;
2146 if (isEmpty(cmdline)) {
2147 printf("rateprofile %d\r\n", getCurrentControlRateProfile());
2148 return;
2149 } else {
2150 i = atoi(cmdline);
2151 if (i >= 0 && i < MAX_CONTROL_RATE_PROFILE_COUNT) {
2152 changeControlRateProfile(i);
2153 cliRateProfile("");
2158 static void cliReboot(void) {
2159 cliPrint("\r\nRebooting");
2160 waitForSerialPortToFinishTransmitting(cliPort);
2161 //stopMotors();
2162 //handleOneshotFeatureChangeOnRestart();
2163 systemReset();
2166 static void cliSave(char *cmdline)
2168 UNUSED(cmdline);
2170 cliPrint("Saving");
2171 //copyCurrentProfileToProfileSlot(masterConfig.current_profile_index);
2172 writeEEPROM();
2173 cliReboot();
2176 static void cliDefaults(char *cmdline)
2178 UNUSED(cmdline);
2180 cliPrint("Resetting to defaults");
2181 resetEEPROM();
2182 cliReboot();
2185 static void cliPrint(const char *str)
2187 while (*str)
2188 serialWrite(cliPort, *(str++));
2191 static void cliWrite(uint8_t ch)
2193 serialWrite(cliPort, ch);
2196 static void cliPrintVar(const clivalue_t *var, uint32_t full)
2198 int32_t value = 0;
2199 char buf[8];
2201 void *ptr = var->ptr;
2202 if ((var->type & VALUE_SECTION_MASK) == PROFILE_VALUE) {
2203 ptr = ((uint8_t *)ptr) + (sizeof(profile_t) * masterConfig.current_profile_index);
2205 if ((var->type & VALUE_SECTION_MASK) == CONTROL_RATE_VALUE) {
2206 ptr = ((uint8_t *)ptr) + (sizeof(controlRateConfig_t) * getCurrentControlRateProfile());
2209 switch (var->type & VALUE_TYPE_MASK) {
2210 case VAR_UINT8:
2211 value = *(uint8_t *)ptr;
2212 break;
2214 case VAR_INT8:
2215 value = *(int8_t *)ptr;
2216 break;
2218 case VAR_UINT16:
2219 value = *(uint16_t *)ptr;
2220 break;
2222 case VAR_INT16:
2223 value = *(int16_t *)ptr;
2224 break;
2226 case VAR_UINT32:
2227 value = *(uint32_t *)ptr;
2228 break;
2230 case VAR_FLOAT:
2231 printf("%s", ftoa(*(float *)ptr, buf));
2232 if (full && (var->type & VALUE_MODE_MASK) == MODE_DIRECT) {
2233 printf(" %s", ftoa((float)var->config.minmax.min, buf));
2234 printf(" %s", ftoa((float)var->config.minmax.max, buf));
2236 return; // return from case for float only
2239 switch(var->type & VALUE_MODE_MASK) {
2240 case MODE_DIRECT:
2241 printf("%d", value);
2242 if (full) {
2243 printf(" %d %d", var->config.minmax.min, var->config.minmax.max);
2245 break;
2246 case MODE_LOOKUP:
2247 printf(lookupTables[var->config.lookup.tableIndex].values[value]);
2248 break;
2252 static void cliSetVar(const clivalue_t *var, const int_float_value_t value)
2254 void *ptr = var->ptr;
2255 if ((var->type & VALUE_SECTION_MASK) == PROFILE_VALUE) {
2256 ptr = ((uint8_t *)ptr) + (sizeof(profile_t) * masterConfig.current_profile_index);
2258 if ((var->type & VALUE_SECTION_MASK) == CONTROL_RATE_VALUE) {
2259 ptr = ((uint8_t *)ptr) + (sizeof(controlRateConfig_t) * getCurrentControlRateProfile());
2262 switch (var->type & VALUE_TYPE_MASK) {
2263 case VAR_UINT8:
2264 case VAR_INT8:
2265 *(int8_t *)ptr = value.int_value;
2266 break;
2268 case VAR_UINT16:
2269 case VAR_INT16:
2270 *(int16_t *)ptr = value.int_value;
2271 if(ptr==&masterConfig.pan0)
2272 cliSetPanServoSpeed();
2273 break;
2275 case VAR_UINT32:
2276 *(uint32_t *)ptr = value.int_value;
2277 break;
2279 case VAR_FLOAT:
2280 *(float *)ptr = (float)value.float_value;
2281 if(ptr==&masterConfig.offset)
2282 cliSetOffset();
2283 break;
2287 static void cliSet(char *cmdline)
2289 uint32_t i;
2290 uint32_t len;
2291 const clivalue_t *val;
2292 char *eqptr = NULL;
2294 len = strlen(cmdline);
2296 if (len == 0 || (len == 1 && cmdline[0] == '*')) {
2297 cliPrint("Current settings: \r\n");
2298 for (i = 0; i < VALUE_COUNT; i++) {
2299 val = &valueTable[i];
2300 printf("%s = ", valueTable[i].name);
2301 cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
2302 cliPrint("\r\n");
2304 } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
2305 // has equals
2307 char *lastNonSpaceCharacter = eqptr;
2308 while (*(lastNonSpaceCharacter - 1) == ' ') {
2309 lastNonSpaceCharacter--;
2311 uint8_t variableNameLength = lastNonSpaceCharacter - cmdline;
2313 // skip the '=' and any ' ' characters
2314 eqptr++;
2315 while (*(eqptr) == ' ') {
2316 eqptr++;
2319 for (i = 0; i < VALUE_COUNT; i++) {
2320 val = &valueTable[i];
2321 // ensure exact match when setting to prevent setting variables with shorter names
2322 if (strncasecmp(cmdline, valueTable[i].name, strlen(valueTable[i].name)) == 0 && variableNameLength == strlen(valueTable[i].name)) {
2324 bool changeValue = false;
2325 int_float_value_t tmp;
2326 switch (valueTable[i].type & VALUE_MODE_MASK) {
2327 case MODE_DIRECT: {
2328 int32_t value = 0;
2329 float valuef = 0;
2331 value = atoi(eqptr);
2332 valuef = fastA2F(eqptr);
2334 if (valuef >= valueTable[i].config.minmax.min && valuef <= valueTable[i].config.minmax.max) { // note: compare float value
2336 if ((valueTable[i].type & VALUE_TYPE_MASK) == VAR_FLOAT)
2337 tmp.float_value = valuef;
2338 else
2339 tmp.int_value = value;
2341 changeValue = true;
2344 break;
2345 case MODE_LOOKUP: {
2346 const lookupTableEntry_t *tableEntry = &lookupTables[valueTable[i].config.lookup.tableIndex];
2347 bool matched = false;
2348 for (uint8_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
2349 matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
2351 if (matched) {
2352 tmp.int_value = tableValueIndex;
2353 changeValue = true;
2357 break;
2360 if (changeValue) {
2361 cliSetVar(val, tmp);
2363 printf("%s set to ", valueTable[i].name);
2364 cliPrintVar(val, 0);
2365 } else {
2366 cliPrint("Invalid value\r\n");
2369 return;
2372 cliPrint("Invalid name\r\n");
2373 } else {
2374 // no equals, check for matching variables.
2375 cliGet(cmdline);
2379 static void cliGet(char *cmdline)
2381 uint32_t i;
2382 const clivalue_t *val;
2383 int matchedCommands = 0;
2385 for (i = 0; i < VALUE_COUNT; i++) {
2386 if (strstr(valueTable[i].name, cmdline)) {
2387 val = &valueTable[i];
2388 printf("%s = ", valueTable[i].name);
2389 cliPrintVar(val, 0);
2390 cliPrint("\r\n");
2392 matchedCommands++;
2397 if (matchedCommands) {
2398 return;
2401 cliPrint("Invalid name\r\n");
2404 static void cliStatus(char *cmdline)
2406 UNUSED(cmdline);
2408 printf("System Uptime: %d seconds, Voltage: %d * 0.1V (%dS battery - %s)\r\n",
2409 millis() / 1000, vbat, batteryCellCount, getBatteryStateString());
2412 printf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
2414 #ifndef CJMCU
2415 uint8_t i;
2416 uint32_t mask;
2417 uint32_t detectedSensorsMask = sensorsMask();
2419 for (i = 0; ; i++) {
2421 if (sensorTypeNames[i] == NULL)
2422 break;
2424 mask = (1 << i);
2425 if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
2426 const char *sensorHardware;
2427 uint8_t sensorHardwareIndex = detectedSensors[i];
2428 sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
2430 printf(", %s=%s", sensorTypeNames[i], sensorHardware);
2432 if (mask == SENSOR_ACC && acc.revisionCode) {
2433 printf(".%c", acc.revisionCode);
2436 printf("%d", heading);
2439 #endif
2441 #ifdef GPS
2442 if(feature(FEATURE_GPS))
2443 printf(", GPS=%d", feature(FEATURE_GPS) + isGpsReceivingData() + (STATE(GPS_FIX) == GPS_FIX));
2444 #endif
2446 cliPrint("\r\n");
2448 #ifdef USE_I2C
2449 uint16_t i2cErrorCounter = i2cGetErrorCounter();
2450 #else
2451 uint16_t i2cErrorCounter = 0;
2452 #endif
2454 printf("Cycle Time: %d, I2C Errors: %d, config size: %d\r\n", cycleTime, i2cErrorCounter, sizeof(master_t));
2457 /*#ifdef USE_SERIAL_1WIRE
2458 static void cliUSB1Wire(char *cmdline)
2460 int i;
2462 if (isEmpty(cmdline)) {
2463 cliPrint("Please specify a ouput channel. e.g. `1wire 2` to connect to motor 2\r\n");
2464 return;
2465 } else {
2466 i = atoi(cmdline);
2467 if (i >= 0 && i <= ESC_COUNT) {
2468 printf("Switching to BlHeli mode on motor port %d\r\n", i);
2470 else {
2471 printf("Invalid motor port, valid range: 1 to %d\r\n", ESC_COUNT);
2474 // motor 1 => index 0
2475 usb1WirePassthrough(i-1);
2477 #endif*/
2479 static void cliVersion(char *cmdline)
2481 UNUSED(cmdline);
2483 printf("# u360gts / %s %s %s / %s (%s)",
2484 targetName,
2485 FC_VERSION_STRING,
2486 buildDate,
2487 buildTime,
2488 shortGitRevision
2491 static void cliCalibrateCompass(void)
2493 pwmPan0 = masterConfig.pan0;
2494 pwmPanCalibrationPulse = masterConfig.pan_calibration_pulse;
2495 ENABLE_STATE(CALIBRATE_MAG);
2498 static void cliCalibratePan(void)
2500 pwmPan0 = masterConfig.pan0;
2501 pwmPanCalibrationPulse = masterConfig.pan_calibration_pulse;
2502 ENABLE_STATE(CALIBRATE_PAN);
2505 static void cliMovePanServo(char *cmdline)
2507 int16_t degrees;
2508 if (isEmpty(cmdline)) {
2509 cliPrint("Please specify the destination angle in degrees.\r\n");
2510 return;
2511 } else {
2512 degrees=atoi(cmdline);
2513 if(degrees>=0 && degrees<=360 ) {
2514 printf("Moving pan servo to %u deg.\r\n", degrees);
2515 SERVOTEST_HEADING=degrees;
2516 ENABLE_SERVO(SERVOPAN_MOVE);
2518 else {
2519 printf("Angle %u is out of range.\r\n",degrees);
2524 static void cliMoveTiltServo(char *cmdline)
2526 int16_t degrees;
2527 if (isEmpty(cmdline)) {
2528 cliPrint("Please specify the destination angle in degrees.\r\n");
2529 return;
2530 } else {
2531 degrees=atoi(cmdline);
2532 if(degrees >=0 && degrees <= 90 ) {
2533 printf("Moving tilt servo to %u deg.\r\n", degrees);
2534 SERVOTEST_TILT = degrees;
2535 ENABLE_SERVO(SERVOTILT_MOVE);
2537 else {
2538 printf("Angle %u is out of range.\r\n",degrees);
2544 static void cliSetPanServoSpeed()
2546 masterConfig.pan0_calibrated=0;
2547 pwmPan0=masterConfig.pan0;
2548 pwmWriteServo(masterConfig.pan_pin,masterConfig.pan0);
2549 cliPrint("\r\nWhen pan servo stops moving then you must set parameter pan0_calibrated to 1.\r\n");
2551 static void cliSetOffset()
2553 OFFSET=masterConfig.offset;
2556 static void cliDumpTracker() {
2558 cliPrint("\r\n# dump configuration\r\n");
2560 cliPrint("\r\n\r\n# version\r\n");
2562 cliVersion(NULL);
2564 cliPrint("\r\n\r\n# serial\r\n");
2566 cliSerial(NULL);
2568 cliPrint("\r\n\r\n# feature\r\n");
2570 unsigned int i;
2571 char buf[16];
2572 uint32_t mask;
2573 mask = featureMask();
2574 for (i = 0; ; i++) { // disable all feature first
2575 if (featureNames[i] == NULL)
2576 break;
2577 printf("feature -%s\r\n", featureNames[i]);
2579 for (i = 0; ; i++) { // reenable what we want.
2580 if (featureNames[i] == NULL)
2581 break;
2582 if (mask & (1 << i))
2583 printf("feature %s\r\n", featureNames[i]);
2586 printSectionBreak();
2588 cliPrint("\r\n\r\n# parameters\r\n");
2590 dumpValues(MASTER_VALUE);
2592 cliPrint("\r\n\r\n# dump finished\r\n");
2595 static void cliBootMode(){
2596 UNUSED(cliPort);
2597 cliPort = openSerialPort(masterConfig.serialConfig.portConfigs[0].identifier, FUNCTION_NONE, NULL, BAUD_115200, MODE_RXTX, SERIAL_NOT_INVERTED);
2598 systemResetToBootloader();
2601 void cliProcess(void)
2603 if (!cliPort) {
2604 return;
2607 while (serialRxBytesWaiting(cliPort)) {
2608 uint8_t c = serialRead(cliPort);
2609 if (c == '\t' || c == '?') {
2610 // do tab completion
2611 const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
2612 uint32_t i = bufferIndex;
2613 for (cmd = cmdTable; cmd < cmdTable + CMD_COUNT; cmd++) {
2614 if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0))
2615 continue;
2616 if (!pstart)
2617 pstart = cmd;
2618 pend = cmd;
2620 if (pstart) { /* Buffer matches one or more commands */
2621 for (; ; bufferIndex++) {
2622 if (pstart->name[bufferIndex] != pend->name[bufferIndex])
2623 break;
2624 if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) {
2625 /* Unambiguous -- append a space */
2626 cliBuffer[bufferIndex++] = ' ';
2627 cliBuffer[bufferIndex] = '\0';
2628 break;
2630 cliBuffer[bufferIndex] = pstart->name[bufferIndex];
2633 if (!bufferIndex || pstart != pend) {
2634 /* Print list of ambiguous matches */
2635 cliPrint("\r\033[K");
2636 for (cmd = pstart; cmd <= pend; cmd++) {
2637 cliPrint(cmd->name);
2638 cliWrite('\t');
2640 cliPrompt();
2641 i = 0; /* Redraw prompt */
2643 for (; i < bufferIndex; i++)
2644 cliWrite(cliBuffer[i]);
2645 } else if (!bufferIndex && c == 4) { // CTRL-D
2646 cliExit(cliBuffer);
2647 return;
2648 } else if (c == 12) { // NewPage / CTRL-L
2649 // clear screen
2650 cliPrint("\033[2J\033[1;1H");
2651 cliPrompt();
2652 } else if (bufferIndex && (c == '\n' || c == '\r')) {
2653 // enter pressed
2654 cliPrint("\r\n");
2656 // Strip comment starting with # from line
2657 char *p = cliBuffer;
2658 p = strchr(p, '%');
2659 if (NULL != p) {
2660 bufferIndex = (uint32_t)(p - cliBuffer);
2663 // Strip trailing whitespace
2664 while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
2665 bufferIndex--;
2668 // Process non-empty lines
2669 if (bufferIndex > 0) {
2670 cliBuffer[bufferIndex] = 0; // null terminate
2672 const clicmd_t *cmd;
2673 for (cmd = cmdTable; cmd < cmdTable + CMD_COUNT; cmd++) {
2674 if(!strncasecmp(cliBuffer, cmd->name, strlen(cmd->name)) // command names match
2675 && !isalnum((unsigned)cliBuffer[strlen(cmd->name)])) // next characted in bufffer is not alphanumeric (command is correctly terminated)
2676 break;
2678 if(cmd < cmdTable + CMD_COUNT)
2679 cmd->func(cliBuffer + strlen(cmd->name) + 1);
2680 else
2681 cliPrint("Unknown command, try 'help'");
2682 bufferIndex = 0;
2685 memset(cliBuffer, 0, sizeof(cliBuffer));
2687 // 'exit' will reset this flag, so we don't need to print prompt again
2688 if (!cliMode)
2689 return;
2691 cliPrompt();
2692 } else if (c == 127) {
2693 // backspace
2694 if (bufferIndex) {
2695 cliBuffer[--bufferIndex] = 0;
2696 cliPrint("\010 \010");
2698 } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
2699 if (!bufferIndex && c == ' ')
2700 continue; // Ignore leading spaces
2701 cliBuffer[bufferIndex++] = c;
2702 cliWrite(c);
2707 void cliInit(serialConfig_t *serialConfig)
2709 UNUSED(serialConfig);
2711 void displayCliMode(){
2712 if(feature(FEATURE_DISPLAY)) {
2713 displayShowFixedPage(PAGE_CLI_MODE);
2716 static void cliRssi(void){
2717 printf("# rssi: %d %d %\r\n",rssi,calculateRssiPercentage());
2719 #endif