/*
* This file is part of Cleanflight.
*
* Cleanflight is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cleanflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cleanflight. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include "platform.h"
// FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
// signal that we're in cli mode
uint8_t cliMode = 0;
extern uint8_t __config_start; // configured via linker script when building binaries.
extern uint8_t __config_end;
#ifdef USE_CLI
#include "blackbox/blackbox.h"
#include "build/assert.h"
#include "build/build_config.h"
#include "build/version.h"
#include "common/axis.h"
#include "common/color.h"
#include "common/maths.h"
#include "common/printf.h"
#include "common/typeconversion.h"
#include "config/config_eeprom.h"
#include "config/feature.h"
#include "config/parameter_group.h"
#include "config/parameter_group_ids.h"
#include "drivers/accgyro.h"
#include "drivers/buf_writer.h"
#include "drivers/bus_i2c.h"
#include "drivers/compass.h"
#include "drivers/io.h"
#include "drivers/io_impl.h"
#include "drivers/logging.h"
#include "drivers/pwm_rx.h"
#include "drivers/sdcard.h"
#include "drivers/sensor.h"
#include "drivers/serial.h"
#include "drivers/stack_check.h"
#include "drivers/system.h"
#include "drivers/time.h"
#include "drivers/timer.h"
#include "fc/cli.h"
#include "fc/config.h"
#include "fc/controlrate_profile.h"
#include "fc/rc_adjustments.h"
#include "fc/rc_controls.h"
#include "fc/runtime_config.h"
#include "flight/failsafe.h"
#include "flight/imu.h"
#include "flight/mixer.h"
#include "flight/pid.h"
#include "flight/servos.h"
#include "io/asyncfatfs/asyncfatfs.h"
#include "io/beeper.h"
#include "io/flashfs.h"
#include "io/gimbal.h"
#include "io/gps.h"
#include "io/ledstrip.h"
#include "io/osd.h"
#include "io/serial.h"
#include "navigation/navigation.h"
#include "rx/rx.h"
#include "rx/spektrum.h"
#include "scheduler/scheduler.h"
#include "sensors/acceleration.h"
#include "sensors/barometer.h"
#include "sensors/battery.h"
#include "sensors/boardalignment.h"
#include "sensors/compass.h"
#include "sensors/diagnostics.h"
#include "sensors/gyro.h"
#include "sensors/pitotmeter.h"
#include "sensors/rangefinder.h"
#include "sensors/sensors.h"
#include "telemetry/frsky.h"
#include "telemetry/telemetry.h"
#include "build/debug.h"
#if FLASH_SIZE > 128
#define PLAY_SOUND
#endif
extern timeDelta_t cycleTime; // FIXME dependency on mw.c
extern uint8_t detectedSensors[SENSOR_INDEX_COUNT];
static serialPort_t *cliPort;
static bufWriter_t *cliWriter;
static uint8_t cliWriteBuffer[sizeof(*cliWriter) + 128];
#if defined(USE_ASSERT)
static void cliAssert(char *cmdline);
#endif
#if defined(BOOTLOG)
static void cliBootlog(char *cmdline);
#endif
// buffer
static char cliBuffer[64];
static uint32_t bufferIndex = 0;
#ifndef USE_QUAD_MIXER_ONLY
// sync this with mixerMode_e
static const char * const mixerNames[] = {
"TRI", "QUADP", "QUADX", "BI",
"GIMBAL", "Y6", "HEX6",
"FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
"AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
"HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
"ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", NULL
};
#endif
// sync this with features_e
static const char * const featureNames[] = {
"RX_PPM", "VBAT", "", "RX_SERIAL", "MOTOR_STOP",
"SERVO_TILT", "SOFTSERIAL", "GPS", "",
"", "TELEMETRY", "CURRENT_METER", "3D", "RX_PARALLEL_PWM",
"RX_MSP", "RSSI_ADC", "LED_STRIP", "DASHBOARD", "",
"BLACKBOX", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
"SUPEREXPO", "VTX", "RX_SPI", "SOFTSPI", "PWM_SERVO_DRIVER", "PWM_OUTPUT_ENABLE", "OSD", NULL
};
/* Sensor names (used in lookup tables for *_hardware settings and in status command output) */
// sync with accelerationSensor_e
static const char * const lookupTableAccHardware[] = { "NONE", "AUTO", "ADXL345", "MPU6050", "MMA845x", "BMA280", "LSM303DLHC", "MPU6000", "MPU6500", "MPU9250", "FAKE"};
#if (FLASH_SIZE > 64)
// sync with gyroSensor_e
static const char * const gyroNames[] = { "NONE", "AUTO", "MPU6050", "L3G4200D", "MPU3050", "L3GD20", "MPU6000", "MPU6500", "MPU9250", "FAKE"};
// sync with baroSensor_e
static const char * const lookupTableBaroHardware[] = { "NONE", "AUTO", "BMP085", "MS5611", "BMP280", "MS5607", "FAKE"};
// sync with magSensor_e
static const char * const lookupTableMagHardware[] = { "NONE", "AUTO", "HMC5883", "AK8975", "GPSMAG", "MAG3110", "AK8963", "IST8310", "FAKE"};
// sycn with rangefinderType_e
static const char * const lookupTableRangefinderHardware[] = { "NONE", "HCSR04", "SRF10"};
// sync with pitotSensor_e
static const char * const lookupTablePitotHardware[] = { "NONE", "AUTO", "MS4525", "FAKE"};
// sync this with sensors_e
static const char * const sensorTypeNames[] = {
"GYRO", "ACC", "BARO", "MAG", "SONAR", "PITOT", "GPS", "GPS+MAG", NULL
};
#define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_SONAR | SENSOR_PITOT)
static const char * const hardwareSensorStatusNames[] = {
"NONE", "OK", "UNAVAILABLE", "FAILING"
};
static const char * const *sensorHardwareNames[] = {
gyroNames,
lookupTableAccHardware,
lookupTableBaroHardware,
lookupTableMagHardware,
lookupTableRangefinderHardware,
lookupTablePitotHardware
};
#endif
static const char * const lookupTableOffOn[] = {
"OFF", "ON"
};
static const char * const lookupTableUnit[] = {
"IMPERIAL", "METRIC"
};
static const char * const lookupTableAlignment[] = {
"DEFAULT",
"CW0",
"CW90",
"CW180",
"CW270",
"CW0FLIP",
"CW90FLIP",
"CW180FLIP",
"CW270FLIP"
};
#ifdef GPS
static const char * const lookupTableGPSProvider[] = {
"NMEA", "UBLOX", "I2C-NAV", "NAZA"
};
static const char * const lookupTableGPSSBASMode[] = {
"AUTO", "EGNOS", "WAAS", "MSAS", "GAGAN", "NONE"
};
static const char * const lookupTableGpsModel[] = {
"PEDESTRIAN", "AIR_1G", "AIR_4G"
};
#endif
static const char * const lookupTableCurrentSensor[] = {
"NONE", "ADC", "VIRTUAL"
};
#ifdef USE_SERVOS
static const char * const lookupTableGimbalMode[] = {
"NORMAL", "MIXTILT"
};
#endif
#ifdef BLACKBOX
static const char * const lookupTableBlackboxDevice[] = {
"SERIAL", "SPIFLASH", "SDCARD"
};
#endif
#ifdef SERIAL_RX
static const char * const lookupTableSerialRX[] = {
"SPEK1024",
"SPEK2048",
"SBUS",
"SUMD",
"SUMH",
"XB-B",
"XB-B-RJ01",
"IBUS",
"JETIEXBUS",
"CRSF"
};
#endif
#ifdef USE_RX_SPI
// sync with rx_spi_protocol_e
static const char * const lookupTableRxSpi[] = {
"V202_250K",
"V202_1M",
"SYMA_X",
"SYMA_X5C",
"CX10",
"CX10A",
"H8_3D",
"INAV"
};
#endif
static const char * const lookupTableGyroLpf[] = {
"256HZ",
"188HZ",
"98HZ",
"42HZ",
"20HZ",
"10HZ"
};
static const char * const lookupTableFailsafeProcedure[] = {
"SET-THR", "DROP", "RTH", "NONE"
};
#ifdef NAV
static const char * const lookupTableNavControlMode[] = {
"ATTI", "CRUISE"
};
static const char * const lookupTableNavRthAltMode[] = {
"CURRENT", "EXTRA", "FIXED", "MAX", "AT_LEAST"
};
static const char * const lookupTableNavResetAltitude[] = {
"NEVER", "FIRST_ARM", "EACH_ARM"
};
#endif
static const char * const lookupTableAuxOperator[] = {
"OR", "AND"
};
#ifdef OSD
static const char * const lookupTableOsdType[] = {
"AUTO",
"PAL",
"NTSC"
};
#endif
static const char * const lookupTablePwmProtocol[] = {
"STANDARD", "ONESHOT125", "ONESHOT42", "MULTISHOT", "BRUSHED"
};
#ifdef ASYNC_GYRO_PROCESSING
static const char * const lookupTableAsyncMode[] = {
"NONE", "GYRO", "ALL"
};
#endif
static const char * const lookupTableDebug[DEBUG_COUNT] = {
"NONE",
"GYRO",
"NOTCH"
};
#ifdef TELEMETRY_LTM
static const char * const lookupTableLTMRates[] = {
"NORMAL", "MEDIUM", "SLOW"
};
#endif
typedef struct lookupTableEntry_s {
const char * const *values;
const uint8_t valueCount;
} lookupTableEntry_t;
typedef enum {
TABLE_OFF_ON = 0,
TABLE_UNIT,
TABLE_ALIGNMENT,
#ifdef GPS
TABLE_GPS_PROVIDER,
TABLE_GPS_SBAS_MODE,
TABLE_GPS_DYN_MODEL,
#endif
#ifdef BLACKBOX
TABLE_BLACKBOX_DEVICE,
#endif
TABLE_CURRENT_SENSOR,
#ifdef USE_SERVOS
TABLE_GIMBAL_MODE,
#endif
#ifdef SERIAL_RX
TABLE_SERIAL_RX,
#endif
#ifdef USE_RX_SPI
TABLE_RX_SPI,
#endif
TABLE_GYRO_LPF,
TABLE_ACC_HARDWARE,
#ifdef BARO
TABLE_BARO_HARDWARE,
#endif
#ifdef MAG
TABLE_MAG_HARDWARE,
#endif
#ifdef SONAR
TABLE_RANGEFINDER_HARDWARE, // currently not used
#endif
#ifdef PITOT
TABLE_PITOT_HARDWARE,
#endif
#ifdef NAV
TABLE_NAV_USER_CTL_MODE,
TABLE_NAV_RTH_ALT_MODE,
TABLE_NAV_RESET_ALTITUDE,
#endif
TABLE_AUX_OPERATOR,
TABLE_MOTOR_PWM_PROTOCOL,
TABLE_FAILSAFE_PROCEDURE,
#ifdef ASYNC_GYRO_PROCESSING
TABLE_ASYNC_MODE,
#endif
#ifdef OSD
TABLE_OSD,
#endif
TABLE_DEBUG,
#ifdef TELEMETRY_LTM
TABLE_LTM_UPDATE_RATE,
#endif
LOOKUP_TABLE_COUNT
} lookupTableIndex_e;
static const lookupTableEntry_t lookupTables[] = {
{ lookupTableOffOn, sizeof(lookupTableOffOn) / sizeof(char *) },
{ lookupTableUnit, sizeof(lookupTableUnit) / sizeof(char *) },
{ lookupTableAlignment, sizeof(lookupTableAlignment) / sizeof(char *) },
#ifdef GPS
{ lookupTableGPSProvider, sizeof(lookupTableGPSProvider) / sizeof(char *) },
{ lookupTableGPSSBASMode, sizeof(lookupTableGPSSBASMode) / sizeof(char *) },
{ lookupTableGpsModel, sizeof(lookupTableGpsModel) / sizeof(char *) },
#endif
#ifdef BLACKBOX
{ lookupTableBlackboxDevice, sizeof(lookupTableBlackboxDevice) / sizeof(char *) },
#endif
{ lookupTableCurrentSensor, sizeof(lookupTableCurrentSensor) / sizeof(char *) },
#ifdef USE_SERVOS
{ lookupTableGimbalMode, sizeof(lookupTableGimbalMode) / sizeof(char *) },
#endif
#ifdef SERIAL_RX
{ lookupTableSerialRX, sizeof(lookupTableSerialRX) / sizeof(char *) },
#endif
#ifdef USE_RX_SPI
{ lookupTableRxSpi, sizeof(lookupTableRxSpi) / sizeof(char *) },
#endif
{ lookupTableGyroLpf, sizeof(lookupTableGyroLpf) / sizeof(char *) },
{ lookupTableAccHardware, sizeof(lookupTableAccHardware) / sizeof(char *) },
#ifdef BARO
{ lookupTableBaroHardware, sizeof(lookupTableBaroHardware) / sizeof(char *) },
#endif
#ifdef MAG
{ lookupTableMagHardware, sizeof(lookupTableMagHardware) / sizeof(char *) },
#endif
#ifdef SONAR
{ lookupTableRangefinderHardware, sizeof(lookupTableRangefinderHardware) / sizeof(char *) },
#endif
#ifdef PITOT
{ lookupTablePitotHardware, sizeof(lookupTablePitotHardware) / sizeof(char *) },
#endif
#ifdef NAV
{ lookupTableNavControlMode, sizeof(lookupTableNavControlMode) / sizeof(char *) },
{ lookupTableNavRthAltMode, sizeof(lookupTableNavRthAltMode) / sizeof(char *) },
{ lookupTableNavResetAltitude, sizeof(lookupTableNavResetAltitude) / sizeof(char *) },
#endif
{ lookupTableAuxOperator, sizeof(lookupTableAuxOperator) / sizeof(char *) },
{ lookupTablePwmProtocol, sizeof(lookupTablePwmProtocol) / sizeof(char *) },
{ lookupTableFailsafeProcedure, sizeof(lookupTableFailsafeProcedure) / sizeof(char *) },
#ifdef ASYNC_GYRO_PROCESSING
{ lookupTableAsyncMode, sizeof(lookupTableAsyncMode) / sizeof(char *) },
#endif
#ifdef OSD
{ lookupTableOsdType, sizeof(lookupTableOsdType) / sizeof(char *) },
#endif
{ lookupTableDebug, sizeof(lookupTableDebug) / sizeof(char *) },
#ifdef TELEMETRY_LTM
{lookupTableLTMRates, sizeof(lookupTableLTMRates) / sizeof(char *) },
#endif
};
#define VALUE_TYPE_OFFSET 0
#define VALUE_SECTION_OFFSET 4
#define VALUE_MODE_OFFSET 6
typedef enum {
// value type, bits 0-3
VAR_UINT8 = (0 << VALUE_TYPE_OFFSET),
VAR_INT8 = (1 << VALUE_TYPE_OFFSET),
VAR_UINT16 = (2 << VALUE_TYPE_OFFSET),
VAR_INT16 = (3 << VALUE_TYPE_OFFSET),
VAR_UINT32 = (4 << VALUE_TYPE_OFFSET),
VAR_FLOAT = (5 << VALUE_TYPE_OFFSET), // 0x05
// value section, bits 4-5
MASTER_VALUE = (0 << VALUE_SECTION_OFFSET),
PROFILE_VALUE = (1 << VALUE_SECTION_OFFSET),
CONTROL_RATE_VALUE = (2 << VALUE_SECTION_OFFSET), // 0x20
// value mode, bits 6-7
MODE_DIRECT = (0 << VALUE_MODE_OFFSET),
MODE_LOOKUP = (1 << VALUE_MODE_OFFSET), // 0x40
MODE_MAX = (2 << VALUE_MODE_OFFSET), // 0x80
} cliValueFlag_e;
#define VALUE_TYPE_MASK (0x0F)
#define VALUE_SECTION_MASK (0x30)
#define VALUE_MODE_MASK (0xC0)
typedef struct cliMinMaxConfig_s {
const int16_t min;
const int16_t max;
} cliMinMaxConfig_t;
typedef struct cliMaxConfig_s {
const uint32_t max;
} cliMaxConfig_t;
typedef struct cliLookupTableConfig_s {
const lookupTableIndex_e tableIndex;
} cliLookupTableConfig_t;
typedef union {
cliLookupTableConfig_t lookup;
cliMinMaxConfig_t minmax;
cliMaxConfig_t max;
} cliValueConfig_t;
typedef struct {
const char *name;
const uint8_t type; // see cliValueFlag_e
const cliValueConfig_t config;
pgn_t pgn;
uint16_t offset;
} __attribute__((packed)) clivalue_t;
static const clivalue_t valueTable[] = {
// PG_GYRO_CONFIG
{ "looptime", VAR_UINT16 | MASTER_VALUE, .config.minmax = {0, 9000}, PG_GYRO_CONFIG, offsetof(gyroConfig_t, looptime) },
{ "gyro_sync", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyroSync) },
{ "gyro_sync_denom", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 1, 32 }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyroSyncDenominator) },
{ "align_gyro", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_ALIGNMENT }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyro_align) },
{ "gyro_hardware_lpf", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GYRO_LPF }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyro_lpf) },
{ "gyro_lpf_hz", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 200 }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyro_soft_lpf_hz) },
{ "moron_threshold", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 128 }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyroMovementCalibrationThreshold) },
#ifdef USE_GYRO_NOTCH_1
{ "gyro_notch1_hz", VAR_UINT16 | MASTER_VALUE, .config.minmax = {0, 500 }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyro_soft_notch_hz_1) },
{ "gyro_notch1_cutoff", VAR_UINT16 | MASTER_VALUE, .config.minmax = {1, 500 }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyro_soft_notch_cutoff_1) },
#endif
#ifdef USE_GYRO_NOTCH_2
{ "gyro_notch2_hz", VAR_UINT16 | MASTER_VALUE, .config.minmax = {0, 500 }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyro_soft_notch_hz_2) },
{ "gyro_notch2_cutoff", VAR_UINT16 | MASTER_VALUE, .config.minmax = {1, 500 }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyro_soft_notch_cutoff_2) },
#endif
#ifdef USE_DUAL_GYRO
{ "gyro_to_use", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 1 }, PG_GYRO_CONFIG, offsetof(gyroConfig_t, gyro_to_use) },
#endif
// PG_ACCELEROMETER_CONFIG
{ "align_acc", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_ALIGNMENT }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, acc_align) },
{ "acc_hardware", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_ACC_HARDWARE }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, acc_hardware) },
{ "acc_lpf_hz", VAR_UINT16 | MASTER_VALUE, .config.minmax = {0, 200 }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, acc_lpf_hz) },
{ "acczero_x", VAR_INT16 | MASTER_VALUE, .config.minmax = { INT16_MIN, INT16_MAX }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, accZero.raw[X]) },
{ "acczero_y", VAR_INT16 | MASTER_VALUE, .config.minmax = { INT16_MIN, INT16_MAX }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, accZero.raw[Y]) },
{ "acczero_z", VAR_INT16 | MASTER_VALUE, .config.minmax = { INT16_MIN, INT16_MAX }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, accZero.raw[Z]) },
{ "accgain_x", VAR_INT16 | MASTER_VALUE, .config.minmax = { 1, 8192 }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, accGain.raw[X]) },
{ "accgain_y", VAR_INT16 | MASTER_VALUE, .config.minmax = { 1, 8192 }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, accGain.raw[Y]) },
{ "accgain_z", VAR_INT16 | MASTER_VALUE, .config.minmax = { 1, 8192 }, PG_ACCELEROMETER_CONFIG, offsetof(accelerometerConfig_t, accGain.raw[Z]) },
// PG_RANGEFINDER_CONFIG
#ifdef SONAR
{ "rangefinder_hardware", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_RANGEFINDER_HARDWARE }, PG_RANGEFINDER_CONFIG, offsetof(rangefinderConfig_t, rangefinder_hardware) },
#endif
// PG_COMPASS_CONFIG
#ifdef MAG
{ "align_mag", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_ALIGNMENT }, PG_COMPASS_CONFIG, offsetof(compassConfig_t, mag_align) },
{ "mag_hardware", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_MAG_HARDWARE }, PG_COMPASS_CONFIG, offsetof(compassConfig_t, mag_hardware) },
{ "mag_declination", VAR_INT16 | MASTER_VALUE, .config.minmax = { -18000, 18000 }, PG_COMPASS_CONFIG, offsetof(compassConfig_t, mag_declination) },
{ "magzero_x", VAR_INT16 | MASTER_VALUE, .config.minmax = { INT16_MIN, INT16_MAX }, PG_COMPASS_CONFIG, offsetof(compassConfig_t, magZero.raw[X]) },
{ "magzero_y", VAR_INT16 | MASTER_VALUE, .config.minmax = { INT16_MIN, INT16_MAX }, PG_COMPASS_CONFIG, offsetof(compassConfig_t, magZero.raw[Y]) },
{ "magzero_z", VAR_INT16 | MASTER_VALUE, .config.minmax = { INT16_MIN, INT16_MAX }, PG_COMPASS_CONFIG, offsetof(compassConfig_t, magZero.raw[Z]) },
{ "mag_calibration_time", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 30, 120 }, PG_COMPASS_CONFIG, offsetof(compassConfig_t, magCalibrationTimeLimit) },
#endif
// PG_BAROMETER_CONFIG
#ifdef BARO
{ "baro_hardware", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_BARO_HARDWARE }, PG_BAROMETER_CONFIG, offsetof(barometerConfig_t, baro_hardware) },
{ "baro_use_median_filter", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_BAROMETER_CONFIG, offsetof(barometerConfig_t, use_median_filtering) },
#endif
// PG_PITOTMETER_CONFIG
#ifdef PITOT
{ "pitot_hardware", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_PITOT_HARDWARE }, PG_PITOTMETER_CONFIG, offsetof(pitotmeterConfig_t, pitot_hardware) },
{ "pitot_use_median_filter", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_PITOTMETER_CONFIG, offsetof(pitotmeterConfig_t, use_median_filtering) },
{ "pitot_noise_lpf", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 1 }, PG_PITOTMETER_CONFIG, offsetof(pitotmeterConfig_t, pitot_noise_lpf) },
{ "pitot_scale", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 100 }, PG_PITOTMETER_CONFIG, offsetof(pitotmeterConfig_t, pitot_scale) },
#endif
// PG_RX_CONFIG
{ "mid_rc", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1200, 1700 }, PG_RX_CONFIG, offsetof(rxConfig_t, midrc) },
{ "min_check", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_RX_CONFIG, offsetof(rxConfig_t, mincheck) },
{ "max_check", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_RX_CONFIG, offsetof(rxConfig_t, maxcheck) },
{ "rssi_channel", VAR_INT8 | MASTER_VALUE, .config.minmax = { 0, MAX_SUPPORTED_RC_CHANNEL_COUNT }, PG_RX_CONFIG, offsetof(rxConfig_t, rssi_channel) },
{ "rssi_scale", VAR_UINT8 | MASTER_VALUE, .config.minmax = { RSSI_SCALE_MIN, RSSI_SCALE_MAX }, PG_RX_CONFIG, offsetof(rxConfig_t, rssi_scale) },
{ "rssi_invert", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_RX_CONFIG, offsetof(rxConfig_t, rssiInvert) },
{ "rc_smoothing", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_RX_CONFIG, offsetof(rxConfig_t, rcSmoothing) },
#ifdef SERIAL_RX
{ "serialrx_provider", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_SERIAL_RX }, PG_RX_CONFIG, offsetof(rxConfig_t, serialrx_provider) },
{ "sbus_inversion", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_RX_CONFIG, offsetof(rxConfig_t, sbus_inversion) },
#endif
#ifdef USE_RX_SPI
{ "rx_spi_protocol", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_RX_SPI }, PG_RX_CONFIG, offsetof(rxConfig_t, rx_spi_protocol) },
{ "rx_spi_id", VAR_UINT32 | MASTER_VALUE, .config.minmax = { 0, 0 }, PG_RX_CONFIG, offsetof(rxConfig_t, rx_spi_id) },
{ "rx_spi_rf_channel_count", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 8 }, PG_RX_CONFIG, offsetof(rxConfig_t, rx_spi_rf_channel_count) },
#endif
#ifdef SPEKTRUM_BIND
{ "spektrum_sat_bind", VAR_UINT8 | MASTER_VALUE, .config.minmax = { SPEKTRUM_SAT_BIND_DISABLED, SPEKTRUM_SAT_BIND_MAX}, PG_RX_CONFIG, offsetof(rxConfig_t, spektrum_sat_bind) },
#endif
{ "rx_min_usec", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_PULSE_MIN, PWM_PULSE_MAX }, PG_RX_CONFIG, offsetof(rxConfig_t, rx_min_usec) },
{ "rx_max_usec", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_PULSE_MIN, PWM_PULSE_MAX }, PG_RX_CONFIG, offsetof(rxConfig_t, rx_max_usec) },
// PG_BLACKBOX_CONFIG
#ifdef BLACKBOX
{ "blackbox_rate_num", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 1, 255 }, PG_BLACKBOX_CONFIG, offsetof(blackboxConfig_t, rate_num) },
{ "blackbox_rate_denom", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 1, 255 }, PG_BLACKBOX_CONFIG, offsetof(blackboxConfig_t, rate_denom) },
{ "blackbox_device", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_BLACKBOX_DEVICE }, PG_BLACKBOX_CONFIG, offsetof(blackboxConfig_t, device) },
#endif
// PG_MOTOR_CONFIG
{ "min_throttle", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, minthrottle) },
{ "max_throttle", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, maxthrottle) },
{ "min_command", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, mincommand) },
{ "motor_pwm_rate", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 50, 32000 }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, motorPwmRate) },
{ "motor_pwm_protocol", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_MOTOR_PWM_PROTOCOL }, PG_MOTOR_CONFIG, offsetof(motorConfig_t, motorPwmProtocol) },
// PG_FAILSAFE_CONFIG
{ "failsafe_delay", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 200 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_delay) },
{ "failsafe_recovery_delay", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 200 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_recovery_delay) },
{ "failsafe_off_delay", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 200 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_off_delay) },
{ "failsafe_throttle", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1000, 2000 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_throttle) },
{ "failsafe_throttle_low_delay",VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 300 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_throttle_low_delay) },
{ "failsafe_procedure", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_FAILSAFE_PROCEDURE }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_procedure) },
{ "failsafe_stick_threshold", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 500 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_stick_motion_threshold) },
{ "failsafe_fw_roll_angle", VAR_INT16 | MASTER_VALUE, .config.minmax = { -800, 800 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_fw_roll_angle) },
{ "failsafe_fw_pitch_angle", VAR_INT16 | MASTER_VALUE, .config.minmax = { -800, 800 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_fw_pitch_angle) },
{ "failsafe_fw_yaw_rate", VAR_INT16 | MASTER_VALUE, .config.minmax = { -1000, 1000 }, PG_FAILSAFE_CONFIG, offsetof(failsafeConfig_t, failsafe_fw_yaw_rate) },
// PG_BOARDALIGNMENT_CONFIG
{ "align_board_roll", VAR_INT16 | MASTER_VALUE, .config.minmax = { -1800, 3600 }, PG_BOARD_ALIGNMENT, offsetof(boardAlignment_t, rollDeciDegrees) },
{ "align_board_pitch", VAR_INT16 | MASTER_VALUE, .config.minmax = { -1800, 3600 }, PG_BOARD_ALIGNMENT, offsetof(boardAlignment_t, pitchDeciDegrees) },
{ "align_board_yaw", VAR_INT16 | MASTER_VALUE, .config.minmax = { -1800, 3600 }, PG_BOARD_ALIGNMENT, offsetof(boardAlignment_t, yawDeciDegrees) },
// PG_GIMBAL_CONFIG
#ifdef USE_SERVOS
{ "gimbal_mode", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GIMBAL_MODE }, PG_GIMBAL_CONFIG, offsetof(gimbalConfig_t, mode) },
#endif
// PG_BATTERY_CONFIG
{ "battery_capacity", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 20000 }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, batteryCapacity) },
{ "vbat_scale", VAR_UINT8 | MASTER_VALUE, .config.minmax = { VBAT_SCALE_MIN, VBAT_SCALE_MAX }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, vbatscale) },
{ "vbat_max_cell_voltage", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 10, 50 }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, vbatmaxcellvoltage) },
{ "vbat_min_cell_voltage", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 10, 50 }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, vbatmincellvoltage) },
{ "vbat_warning_cell_voltage", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 10, 50 }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, vbatwarningcellvoltage) },
{ "current_meter_scale", VAR_INT16 | MASTER_VALUE, .config.minmax = { -10000, 10000 }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, currentMeterScale) },
{ "current_meter_offset", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 3300 }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, currentMeterOffset) },
{ "multiwii_current_meter_output", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, multiwiiCurrentMeterOutput) },
{ "current_meter_type", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_CURRENT_SENSOR }, PG_BATTERY_CONFIG, offsetof(batteryConfig_t, currentMeterType) },
// PG_MIXER_CONFIG
{ "yaw_motor_direction", VAR_INT8 | MASTER_VALUE, .config.minmax = { -1, 1 }, PG_MIXER_CONFIG, offsetof(mixerConfig_t, yaw_motor_direction) },
{ "yaw_jump_prevention_limit", VAR_UINT16 | MASTER_VALUE, .config.minmax = { YAW_JUMP_PREVENTION_LIMIT_LOW, YAW_JUMP_PREVENTION_LIMIT_HIGH }, PG_MIXER_CONFIG, offsetof(mixerConfig_t, yaw_jump_prevention_limit) },
// PG_MOTOR_3D_CONFIG
{ "3d_deadband_low", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_MOTOR_3D_CONFIG, offsetof(flight3DConfig_t, deadband3d_low) }, // FIXME upper limit should match code in the mixer, 1500 currently
{ "3d_deadband_high", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_MOTOR_3D_CONFIG, offsetof(flight3DConfig_t, deadband3d_high) }, // FIXME lower limit should match code in the mixer, 1500 currently,
{ "3d_neutral", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_MOTOR_3D_CONFIG, offsetof(flight3DConfig_t, neutral3d) },
#ifdef USE_SERVOS
// PG_SERVO_CONFIG
{ "servo_center_pulse", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_SERVO_CONFIG, offsetof(servoConfig_t, servoCenterPulse) },
{ "servo_pwm_rate", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 50, 498 }, PG_SERVO_CONFIG, offsetof(servoConfig_t, servoPwmRate) },
{ "servo_lpf_hz", VAR_INT16 | MASTER_VALUE, .config.minmax = { 0, 400}, PG_SERVO_CONFIG, offsetof(servoConfig_t, servo_lowpass_freq) },
{ "flaperon_throw_offset", VAR_INT16 | MASTER_VALUE, .config.minmax = { FLAPERON_THROW_MIN, FLAPERON_THROW_MAX}, PG_SERVO_CONFIG, offsetof(servoConfig_t, flaperon_throw_offset) },
{ "tri_unarmed_servo", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_SERVO_CONFIG, offsetof(servoConfig_t, tri_unarmed_servo) },
#endif
// PG_CONTROLRATE_PROFILE
{ "rc_expo", VAR_UINT8 | CONTROL_RATE_VALUE, .config.minmax = { 0, 100 }, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, rcExpo8) },
{ "rc_yaw_expo", VAR_UINT8 | CONTROL_RATE_VALUE, .config.minmax = { 0, 100 }, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, rcYawExpo8) },
{ "thr_mid", VAR_UINT8 | CONTROL_RATE_VALUE, .config.minmax = { 0, 100 }, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, thrMid8) },
{ "thr_expo", VAR_UINT8 | CONTROL_RATE_VALUE, .config.minmax = { 0, 100 }, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, thrExpo8) },
// New rates are in dps/10. That means, Rate of 20 means 200dps of rotation speed on given axis.
// Rate 180 (1800dps) is max. value gyro can measure reliably
{ "roll_rate", VAR_UINT8 | CONTROL_RATE_VALUE, .config.minmax = { CONTROL_RATE_CONFIG_ROLL_PITCH_RATE_MIN, CONTROL_RATE_CONFIG_ROLL_PITCH_RATE_MAX }, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, rates[FD_ROLL]) },
{ "pitch_rate", VAR_UINT8 | CONTROL_RATE_VALUE, .config.minmax = { CONTROL_RATE_CONFIG_ROLL_PITCH_RATE_MIN, CONTROL_RATE_CONFIG_ROLL_PITCH_RATE_MAX }, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, rates[FD_PITCH]) },
{ "yaw_rate", VAR_UINT8 | CONTROL_RATE_VALUE, .config.minmax = { CONTROL_RATE_CONFIG_YAW_RATE_MIN, CONTROL_RATE_CONFIG_YAW_RATE_MAX }, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, rates[FD_YAW]) },
{ "tpa_rate", VAR_UINT8 | CONTROL_RATE_VALUE, .config.minmax = { 0, CONTROL_RATE_CONFIG_TPA_MAX}, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, dynThrPID) },
{ "tpa_breakpoint", VAR_UINT16 | CONTROL_RATE_VALUE, .config.minmax = { PWM_RANGE_MIN, PWM_RANGE_MAX}, PG_CONTROL_RATE_PROFILES, offsetof(controlRateConfig_t, tpa_breakpoint) },
// PG_SERIAL_CONFIG
{ "reboot_character", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 48, 126 }, PG_SERIAL_CONFIG, offsetof(serialConfig_t, reboot_character) },
// PG_IMU_CONFIG
{ "imu_dcm_kp", VAR_UINT16 | MASTER_VALUE | MODE_MAX, .config.max = { UINT16_MAX }, PG_IMU_CONFIG, offsetof(imuConfig_t, dcm_kp_acc) },
{ "imu_dcm_ki", VAR_UINT16 | MASTER_VALUE | MODE_MAX, .config.max = { UINT16_MAX }, PG_IMU_CONFIG, offsetof(imuConfig_t, dcm_ki_acc) },
{ "imu_dcm_kp_mag", VAR_UINT16 | MASTER_VALUE | MODE_MAX, .config.max = { UINT16_MAX }, PG_IMU_CONFIG, offsetof(imuConfig_t, dcm_kp_mag) },
{ "imu_dcm_ki_mag", VAR_UINT16 | MASTER_VALUE | MODE_MAX, .config.max = { UINT16_MAX }, PG_IMU_CONFIG, offsetof(imuConfig_t, dcm_ki_mag) },
{ "small_angle", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 180 }, PG_IMU_CONFIG, offsetof(imuConfig_t, small_angle) },
// PG_ARMING_CONFIG
{ "fixed_wing_auto_arm", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_ARMING_CONFIG, offsetof(armingConfig_t, fixed_wing_auto_arm) },
{ "disarm_kill_switch", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_ARMING_CONFIG, offsetof(armingConfig_t, disarm_kill_switch) },
{ "auto_disarm_delay", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 60 }, PG_ARMING_CONFIG, offsetof(armingConfig_t, auto_disarm_delay) },
// PG_GPS_CONFIG
#ifdef GPS
{ "gps_provider", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GPS_PROVIDER }, PG_GPS_CONFIG, offsetof(gpsConfig_t, provider) },
{ "gps_sbas_mode", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GPS_SBAS_MODE }, PG_GPS_CONFIG, offsetof(gpsConfig_t, sbasMode) },
{ "gps_dyn_model", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_GPS_DYN_MODEL }, PG_GPS_CONFIG, offsetof(gpsConfig_t, dynModel) },
{ "gps_auto_config", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, autoConfig) },
{ "gps_auto_baud", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_GPS_CONFIG, offsetof(gpsConfig_t, autoBaud) },
{ "gps_min_sats", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 5, 10}, PG_GPS_CONFIG, offsetof(gpsConfig_t, gpsMinSats) },
#endif
// PG_RC_CONTROLS_CONFIG
{ "deadband", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 32 }, PG_RC_CONTROLS_CONFIG, offsetof(rcControlsConfig_t, deadband) },
{ "yaw_deadband", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 100 }, PG_RC_CONTROLS_CONFIG, offsetof(rcControlsConfig_t, yaw_deadband) },
{ "pos_hold_deadband", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 10, 250 }, PG_RC_CONTROLS_CONFIG, offsetof(rcControlsConfig_t, pos_hold_deadband) },
{ "alt_hold_deadband", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 10, 250 }, PG_RC_CONTROLS_CONFIG, offsetof(rcControlsConfig_t, alt_hold_deadband) },
{ "3d_deadband_throttle", VAR_UINT16 | MASTER_VALUE, .config.minmax = { PWM_RANGE_ZERO, PWM_RANGE_MAX }, PG_RC_CONTROLS_CONFIG, offsetof(rcControlsConfig_t, deadband3d_throttle) },
// PG_PID_PROFILE
// { "default_rate_profile", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, MAX_CONTROL_RATE_PROFILE_COUNT - 1 }, PG_PID_CONFIG, offsetof(pidProfile_t, defaultRateProfileIndex) },
{ "mc_p_pitch", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_PITCH].P) },
{ "mc_i_pitch", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_PITCH].I) },
{ "mc_d_pitch", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_PITCH].D) },
{ "mc_p_roll", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_ROLL].P) },
{ "mc_i_roll", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_ROLL].I) },
{ "mc_d_roll", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_ROLL].D) },
{ "mc_p_yaw", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_YAW].P) },
{ "mc_i_yaw", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_YAW].I) },
{ "mc_d_yaw", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_YAW].D) },
{ "mc_p_level", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_LEVEL].P) },
{ "mc_i_level", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 100 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_LEVEL].I) },
{ "mc_d_level", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 100 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_LEVEL].D) },
{ "fw_p_pitch", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_PITCH].P) },
{ "fw_i_pitch", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_PITCH].I) },
{ "fw_ff_pitch", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_PITCH].D) },
{ "fw_p_roll", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_ROLL].P) },
{ "fw_i_roll", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_ROLL].I) },
{ "fw_ff_roll", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_ROLL].D) },
{ "fw_p_yaw", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_YAW].P) },
{ "fw_i_yaw", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_YAW].I) },
{ "fw_ff_yaw", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_YAW].D) },
{ "fw_p_level", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_LEVEL].P) },
{ "fw_i_level", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 100 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_LEVEL].I) },
{ "fw_d_level", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 100 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_LEVEL].D) },
{ "max_angle_inclination_rll", VAR_INT16 | PROFILE_VALUE, .config.minmax = { 100, 900 }, PG_PID_PROFILE, offsetof(pidProfile_t, max_angle_inclination[FD_ROLL]) },
{ "max_angle_inclination_pit", VAR_INT16 | PROFILE_VALUE, .config.minmax = { 100, 900 }, PG_PID_PROFILE, offsetof(pidProfile_t, max_angle_inclination[FD_PITCH]) },
{ "dterm_lpf_hz", VAR_UINT8 | PROFILE_VALUE, .config.minmax = {0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, dterm_lpf_hz) },
{ "yaw_lpf_hz", VAR_UINT8 | PROFILE_VALUE, .config.minmax = {0, 200 }, PG_PID_PROFILE, offsetof(pidProfile_t, yaw_lpf_hz) },
{ "dterm_setpoint_weight", VAR_FLOAT | PROFILE_VALUE, .config.minmax = {0, 2 }, PG_PID_PROFILE, offsetof(pidProfile_t, dterm_setpoint_weight) },
#ifdef USE_SERVOS
{ "fw_iterm_throw_limit", VAR_UINT16 | PROFILE_VALUE, .config.minmax = { FW_ITERM_THROW_LIMIT_MIN, FW_ITERM_THROW_LIMIT_MAX}, PG_PID_PROFILE, offsetof(pidProfile_t, fixedWingItermThrowLimit) },
#endif
#ifdef USE_DTERM_NOTCH
{ "dterm_notch_hz", VAR_UINT16 | PROFILE_VALUE, .config.minmax = {0, 500 }, PG_PID_PROFILE, offsetof(pidProfile_t, dterm_soft_notch_hz) },
{ "dterm_notch_cutoff", VAR_UINT16 | PROFILE_VALUE, .config.minmax = {1, 500 }, PG_PID_PROFILE, offsetof(pidProfile_t, dterm_soft_notch_cutoff) },
#endif
{ "pidsum_limit", VAR_UINT16 | PROFILE_VALUE, .config.minmax = { PID_SUM_LIMIT_MIN, PID_SUM_LIMIT_MAX }, PG_PID_PROFILE, offsetof(pidProfile_t, pidSumLimit) },
{ "yaw_p_limit", VAR_UINT16 | PROFILE_VALUE, .config.minmax = { YAW_P_LIMIT_MIN, YAW_P_LIMIT_MAX }, PG_PID_PROFILE, offsetof(pidProfile_t, yaw_p_limit) },
{ "iterm_ignore_threshold", VAR_UINT16 | PROFILE_VALUE, .config.minmax = {15, 1000 }, PG_PID_PROFILE, offsetof(pidProfile_t, rollPitchItermIgnoreRate) },
{ "yaw_iterm_ignore_threshold", VAR_UINT16 | PROFILE_VALUE, .config.minmax = {15, 1000 }, PG_PID_PROFILE, offsetof(pidProfile_t, yawItermIgnoreRate) },
{ "rate_accel_limit_roll_pitch",VAR_UINT32 | PROFILE_VALUE | MODE_MAX, .config.max = { 500000 }, PG_PID_PROFILE, offsetof(pidProfile_t, axisAccelerationLimitRollPitch) },
{ "rate_accel_limit_yaw", VAR_UINT32 | PROFILE_VALUE | MODE_MAX, .config.max = { 500000 }, PG_PID_PROFILE, offsetof(pidProfile_t, axisAccelerationLimitYaw) },
{ "heading_hold_rate_limit", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { HEADING_HOLD_RATE_LIMIT_MIN, HEADING_HOLD_RATE_LIMIT_MAX }, PG_PID_PROFILE, offsetof(pidProfile_t, heading_hold_rate_limit) },
#ifdef NAV
{ "nav_mc_pos_z_p", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_POS_Z].P) },
{ "nav_mc_pos_z_i", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_POS_Z].I) },
{ "nav_mc_pos_z_d", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_POS_Z].D) },
{ "nav_mc_vel_z_p", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_VEL_Z].P) },
{ "nav_mc_vel_z_i", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_VEL_Z].I) },
{ "nav_mc_vel_z_d", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_VEL_Z].D) },
{ "nav_mc_pos_xy_p", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_POS_XY].P) },
{ "nav_mc_pos_xy_i", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_POS_XY].I) },
{ "nav_mc_pos_xy_d", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_POS_XY].D) },
{ "nav_mc_vel_xy_p", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_VEL_XY].P) },
{ "nav_mc_vel_xy_i", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_VEL_XY].I) },
{ "nav_mc_vel_xy_d", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_mc.pid[PID_VEL_XY].D) },
{ "nav_fw_pos_z_p", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_POS_Z].P) },
{ "nav_fw_pos_z_i", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_POS_Z].I) },
{ "nav_fw_pos_z_d", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_POS_Z].D) },
{ "nav_fw_pos_xy_p", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_POS_XY].P) },
{ "nav_fw_pos_xy_i", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_POS_XY].I) },
{ "nav_fw_pos_xy_d", VAR_UINT8 | PROFILE_VALUE, .config.minmax = { 0, 255 }, PG_PID_PROFILE, offsetof(pidProfile_t, bank_fw.pid[PID_POS_XY].D) },
// PG_POSITION_ESTIMATION_CONFIG
#if defined(NAV_AUTO_MAG_DECLINATION)
{ "inav_auto_mag_decl", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, automatic_mag_declination) },
#endif
{ "inav_accz_unarmedcal", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, accz_unarmed_cal) },
{ "inav_use_gps_velned", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, use_gps_velned) },
{ "inav_gps_delay", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 500 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, gps_delay_ms) },
{ "inav_reset_altitude", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_NAV_RESET_ALTITUDE }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, reset_altitude_type) },
{ "inav_max_sonar_altitude", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 1000 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, max_sonar_altitude) },
{ "inav_w_z_sonar_p", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_z_sonar_p) },
{ "inav_w_z_sonar_v", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_z_sonar_v) },
{ "inav_w_z_baro_p", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_z_baro_p) },
{ "inav_w_z_gps_p", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_z_gps_p) },
{ "inav_w_z_gps_v", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_z_gps_v) },
{ "inav_w_xy_gps_p", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_xy_gps_p) },
{ "inav_w_xy_gps_v", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_xy_gps_v) },
{ "inav_w_z_res_v", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_z_res_v) },
{ "inav_w_xy_res_v", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_xy_res_v) },
{ "inav_w_acc_bias", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 1 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, w_acc_bias) },
{ "inav_max_eph_epv", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 9999 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, max_eph_epv) },
{ "inav_baro_epv", VAR_FLOAT | MASTER_VALUE, .config.minmax = { 0, 9999 }, PG_POSITION_ESTIMATION_CONFIG, offsetof(positionEstimationConfig_t, baro_epv) },
// PG_NAV_CONFIG
{ "nav_disarm_on_landing", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.disarm_on_landing) },
{ "nav_use_midthr_for_althold", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.use_thr_mid_for_althold) },
{ "nav_extra_arming_safety", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.extra_arming_safety) },
{ "nav_user_control_mode", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_NAV_USER_CTL_MODE }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.user_control_mode) },
{ "nav_position_timeout", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 10 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.pos_failure_timeout) },
{ "nav_wp_radius", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 10, 10000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.waypoint_radius) },
{ "nav_wp_safe_distance", VAR_UINT16 | MASTER_VALUE | MODE_MAX, .config.max = { 65000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.waypoint_safe_distance) },
{ "nav_auto_speed", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 10, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.max_auto_speed) },
{ "nav_auto_climb_rate", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 10, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.max_auto_climb_rate) },
{ "nav_manual_speed", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 10, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.max_manual_speed) },
{ "nav_manual_climb_rate", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 10, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.max_manual_climb_rate ) },
{ "nav_landing_speed", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 100, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.land_descent_rate) },
{ "nav_land_slowdown_minalt", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 50, 1000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.land_slowdown_minalt) },
{ "nav_land_slowdown_maxalt", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 500, 4000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.land_slowdown_maxalt) },
{ "nav_emerg_landing_speed", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 100, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.emerg_descent_rate) },
{ "nav_min_rth_distance", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 5000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.min_rth_distance) },
{ "nav_rth_climb_first", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.rth_climb_first) },
{ "nav_rth_climb_ignore_emerg", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.rth_climb_ignore_emerg) },
{ "nav_rth_tail_first", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.rth_tail_first) },
{ "nav_rth_allow_landing", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.rth_allow_landing) },
{ "nav_rth_alt_mode", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_NAV_RTH_ALT_MODE }, PG_NAV_CONFIG, offsetof(navConfig_t, general.flags.rth_alt_control_mode) },
{ "nav_rth_abort_threshold", VAR_UINT16 | MASTER_VALUE | MODE_MAX, .config.max = { 65000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.rth_abort_threshold) },
{ "nav_rth_altitude", VAR_UINT16 | MASTER_VALUE | MODE_MAX, .config.max = { 65000 }, PG_NAV_CONFIG, offsetof(navConfig_t, general.rth_altitude) },
{ "nav_mc_bank_angle", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 15, 45 }, PG_NAV_CONFIG, offsetof(navConfig_t, mc.max_bank_angle) },
{ "nav_mc_hover_thr", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1000, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, mc.hover_throttle) },
{ "nav_mc_auto_disarm_delay", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 100, 10000 }, PG_NAV_CONFIG, offsetof(navConfig_t, mc.auto_disarm_delay) },
{ "nav_fw_cruise_thr", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1000, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.cruise_throttle) },
{ "nav_fw_min_thr", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1000, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.min_throttle) },
{ "nav_fw_max_thr", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1000, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.max_throttle) },
{ "nav_fw_bank_angle", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 5, 80 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.max_bank_angle) },
{ "nav_fw_climb_angle", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 5, 80 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.max_climb_angle) },
{ "nav_fw_dive_angle", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 5, 80 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.max_dive_angle) },
{ "nav_fw_pitch2thr", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 100 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.pitch_to_throttle) },
{ "nav_fw_roll2pitch", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 200 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.roll_to_pitch) },
{ "nav_fw_loiter_radius", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 10000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.loiter_radius) },
{ "nav_fw_launch_velocity", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 100, 10000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_velocity_thresh) },
{ "nav_fw_launch_accel", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1000, 20000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_accel_thresh) },
{ "nav_fw_launch_detect_time", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 10, 1000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_time_thresh) },
{ "nav_fw_launch_thr", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1000, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_throttle) },
{ "nav_fw_launch_idle_thr", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 1000, 2000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_idle_throttle) },
{ "nav_fw_launch_motor_delay", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 5000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_motor_timer) },
{ "nav_fw_launch_spinup_time", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 1000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_motor_spinup_time) },
{ "nav_fw_launch_timeout", VAR_UINT16 | MASTER_VALUE | MODE_MAX , .config.max = { 60000 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_timeout) },
{ "nav_fw_launch_climb_angle", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 5, 45 }, PG_NAV_CONFIG, offsetof(navConfig_t, fw.launch_climb_angle) },
#endif
#ifdef TELEMETRY
// PG_TELEMETRY_CONFIG
{ "telemetry_switch", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, telemetry_switch) },
{ "telemetry_inversion", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, telemetry_inversion) },
{ "frsky_default_lattitude", VAR_FLOAT | MASTER_VALUE, .config.minmax = { -90, 90 }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, gpsNoFixLatitude) },
{ "frsky_default_longitude", VAR_FLOAT | MASTER_VALUE, .config.minmax = { -180, 180 }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, gpsNoFixLongitude) },
{ "frsky_coordinates_format", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, FRSKY_FORMAT_NMEA }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, frsky_coordinate_format) },
{ "frsky_unit", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_UNIT }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, frsky_unit) },
{ "frsky_vfas_precision", VAR_UINT8 | MASTER_VALUE, .config.minmax = { FRSKY_VFAS_PRECISION_LOW, FRSKY_VFAS_PRECISION_HIGH }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, frsky_vfas_precision) },
{ "frsky_vfas_cell_voltage", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, frsky_vfas_cell_voltage) },
{ "hott_alarm_sound_interval", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 120 }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, hottAlarmSoundInterval) },
#ifdef TELEMETRY_SMARTPORT
{ "smartport_uart_unidir", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, smartportUartUnidirectional) },
#endif
{ "ibus_telemetry_type", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 255 }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, ibusTelemetryType ) },
#ifdef TELEMETRY_LTM
{"ltm_update_rate", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_LTM_UPDATE_RATE }, PG_TELEMETRY_CONFIG, offsetof(telemetryConfig_t, ltmUpdateRate) },
#endif
#endif
#ifdef LED_STRIP
{ "ledstrip_visual_beeper", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_LED_STRIP_CONFIG, offsetof(ledStripConfig_t, ledstrip_visual_beeper) },
#endif
#ifdef OSD
{ "osd_video_system", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 2 }, PG_OSD_CONFIG, offsetof(osdConfig_t, video_system) },
{ "osd_row_shiftdown", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 1 }, PG_OSD_CONFIG, offsetof(osdConfig_t, row_shiftdown) },
{ "osd_units", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_UNIT }, PG_OSD_CONFIG, offsetof(osdConfig_t, units) },
{ "osd_rssi_alarm", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 100 }, PG_OSD_CONFIG, offsetof(osdConfig_t, rssi_alarm) },
{ "osd_cap_alarm", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 20000 }, PG_OSD_CONFIG, offsetof(osdConfig_t, cap_alarm) },
{ "osd_time_alarm", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 60 }, PG_OSD_CONFIG, offsetof(osdConfig_t, time_alarm) },
{ "osd_alt_alarm", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, 10000 }, PG_OSD_CONFIG, offsetof(osdConfig_t, alt_alarm) },
{ "osd_main_voltage_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_MAIN_BATT_VOLTAGE]) },
{ "osd_rssi_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_RSSI_VALUE]) },
{ "osd_flytimer_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_FLYTIME]) },
{ "osd_ontime_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_ONTIME]) },
{ "osd_flymode_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_FLYMODE]) },
{ "osd_throttle_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_THROTTLE_POS]) },
{ "osd_vtx_channel_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_VTX_CHANNEL]) },
{ "osd_crosshairs", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_CROSSHAIRS]) },
{ "osd_artificial_horizon", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_ARTIFICIAL_HORIZON]) },
{ "osd_current_draw_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_CURRENT_DRAW]) },
{ "osd_mah_drawn_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_MAH_DRAWN]) },
{ "osd_craft_name_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_CRAFT_NAME]) },
{ "osd_gps_speed_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_GPS_SPEED]) },
{ "osd_gps_sats_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_GPS_SATS]) },
{ "osd_gps_lon", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_GPS_LON]) },
{ "osd_gps_lat", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_GPS_LAT]) },
{ "osd_home_dir", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_HOME_DIR]) },
{ "osd_home_dist", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_HOME_DIST]) },
{ "osd_altitude_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_ALTITUDE]) },
{ "osd_vario", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_VARIO]) },
{ "osd_vario_num", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_VARIO_NUM]) },
{ "osd_pid_roll_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_ROLL_PIDS]) },
{ "osd_pid_pitch_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_PITCH_PIDS]) },
{ "osd_pid_yaw_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_YAW_PIDS]) },
{ "osd_power_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POS_MAX_CLI }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_POWER]) },
#endif
// PG_SYSTEM_CONFIG
{ "i2c_overclock", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_SYSTEM_CONFIG, offsetof(systemConfig_t, i2c_overclock) },
{ "debug_mode", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_DEBUG }, PG_SYSTEM_CONFIG, offsetof(systemConfig_t, debug_mode) },
#ifdef ASYNC_GYRO_PROCESSING
{ "acc_task_frequency", VAR_UINT16 | MASTER_VALUE, .config.minmax = { ACC_TASK_FREQUENCY_MIN, ACC_TASK_FREQUENCY_MAX }, PG_SYSTEM_CONFIG, offsetof(systemConfig_t, accTaskFrequency) },
{ "attitude_task_frequency", VAR_UINT16 | MASTER_VALUE, .config.minmax = { ATTITUDE_TASK_FREQUENCY_MIN, ATTITUDE_TASK_FREQUENCY_MAX }, PG_SYSTEM_CONFIG, offsetof(systemConfig_t, attitudeTaskFrequency) },
{ "async_mode", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_ASYNC_MODE }, PG_SYSTEM_CONFIG, offsetof(systemConfig_t, asyncMode) },
#endif
{ "throttle_tilt_comp_str", VAR_UINT8 | MASTER_VALUE, .config.minmax = { 0, 100 }, PG_SYSTEM_CONFIG, offsetof(systemConfig_t, throttle_tilt_compensation_strength) },
{ "input_filtering_mode", VAR_INT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_SYSTEM_CONFIG, offsetof(systemConfig_t, pwmRxInputFilteringMode) },
{ "mode_range_logic_operator", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_AUX_OPERATOR }, PG_MODE_ACTIVATION_OPERATOR_CONFIG, offsetof(modeActivationOperatorConfig_t, modeActivationOperator) },
};
static featureConfig_t featureConfigCopy;
static gyroConfig_t gyroConfigCopy;
static accelerometerConfig_t accelerometerConfigCopy;
#ifdef MAG
static compassConfig_t compassConfigCopy;
#endif
#ifdef BARO
static barometerConfig_t barometerConfigCopy;
#endif
#ifdef PITOT
static pitotmeterConfig_t pitotmeterConfigCopy;
#endif
static featureConfig_t featureConfigCopy;
static rxConfig_t rxConfigCopy;
#ifdef BLACKBOX
static blackboxConfig_t blackboxConfigCopy;
#endif
static rxChannelRangeConfig_t rxChannelRangeConfigsCopy[NON_AUX_CHANNEL_COUNT];
static motorConfig_t motorConfigCopy;
static failsafeConfig_t failsafeConfigCopy;
static boardAlignment_t boardAlignmentCopy;
#ifdef USE_SERVOS
static servoConfig_t servoConfigCopy;
static gimbalConfig_t gimbalConfigCopy;
static servoMixer_t customServoMixersCopy[MAX_SERVO_RULES];
static servoParam_t servoParamsCopy[MAX_SUPPORTED_SERVOS];
#endif
static batteryConfig_t batteryConfigCopy;
static motorMixer_t customMotorMixerCopy[MAX_SUPPORTED_MOTORS];
static mixerConfig_t mixerConfigCopy;
static flight3DConfig_t flight3DConfigCopy;
static serialConfig_t serialConfigCopy;
static imuConfig_t imuConfigCopy;
static armingConfig_t armingConfigCopy;
static rcControlsConfig_t rcControlsConfigCopy;
#ifdef GPS
static gpsConfig_t gpsConfigCopy;
#endif
#ifdef NAV
static positionEstimationConfig_t positionEstimationConfigCopy;
static navConfig_t navConfigCopy;
#endif
#ifdef TELEMETRY
static telemetryConfig_t telemetryConfigCopy;
#endif
static modeActivationCondition_t modeActivationConditionsCopy[MAX_MODE_ACTIVATION_CONDITION_COUNT];
static adjustmentRange_t adjustmentRangesCopy[MAX_ADJUSTMENT_RANGE_COUNT];
#ifdef LED_STRIP
static ledStripConfig_t ledStripConfigCopy;
#endif
#ifdef OSD
static osdConfig_t osdConfigCopy;
#endif
static systemConfig_t systemConfigCopy;
#ifdef BEEPER
static beeperConfig_t beeperConfigCopy;
#endif
#ifdef SONAR
static rangefinderConfig_t rangefinderConfigCopy;
#endif
static controlRateConfig_t controlRateProfilesCopy[MAX_CONTROL_RATE_PROFILE_COUNT];
static pidProfile_t pidProfileCopy[MAX_PROFILE_COUNT];
static modeActivationOperatorConfig_t modeActivationOperatorConfigCopy;
static beeperConfig_t beeperConfigCopy;
static void cliPrint(const char *str)
{
while (*str) {
bufWriterAppend(cliWriter, *str++);
}
}
#ifdef CLI_MINIMAL_VERBOSITY
#define cliPrintHashLine(str)
#else
static void cliPrintHashLine(const char *str)
{
cliPrint("\r\n# ");
cliPrint(str);
cliPrint("\r\n");
}
#endif
static void cliPutp(void *p, char ch)
{
bufWriterAppend(p, ch);
}
typedef enum {
DUMP_MASTER = (1 << 0),
DUMP_PROFILE = (1 << 1),
DUMP_RATES = (1 << 2),
DUMP_ALL = (1 << 3),
DO_DIFF = (1 << 4),
SHOW_DEFAULTS = (1 << 5),
HIDE_UNUSED = (1 << 6)
} dumpFlags_e;
static bool cliDumpPrintf(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
{
if (!((dumpMask & DO_DIFF) && equalsDefault)) {
va_list va;
va_start(va, format);
tfp_format(cliWriter, cliPutp, format, va);
va_end(va);
return true;
} else {
return false;
}
}
static void cliWrite(uint8_t ch)
{
bufWriterAppend(cliWriter, ch);
}
static bool cliDefaultPrintf(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
{
if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
cliWrite('#');
va_list va;
va_start(va, format);
tfp_format(cliWriter, cliPutp, format, va);
va_end(va);
return true;
} else {
return false;
}
}
static void cliPrintf(const char *format, ...)
{
va_list va;
va_start(va, format);
tfp_format(cliWriter, cliPutp, format, va);
va_end(va);
}
static void printValuePointer(const clivalue_t *var, const void *valuePointer, uint32_t full)
{
int32_t value = 0;
char buf[8];
switch (var->type & VALUE_TYPE_MASK) {
case VAR_UINT8:
value = *(uint8_t *)valuePointer;
break;
case VAR_INT8:
value = *(int8_t *)valuePointer;
break;
case VAR_UINT16:
value = *(uint16_t *)valuePointer;
break;
case VAR_INT16:
value = *(int16_t *)valuePointer;
break;
case VAR_UINT32:
value = *(uint32_t *)valuePointer;
break;
case VAR_FLOAT:
cliPrintf("%s", ftoa(*(float *)valuePointer, buf));
if (full) {
if ((var->type & VALUE_MODE_MASK) == MODE_DIRECT) {
cliPrintf(" %s", ftoa((float)var->config.minmax.min, buf));
cliPrintf(" %s", ftoa((float)var->config.minmax.max, buf));
} else if ((var->type & VALUE_MODE_MASK) == MODE_MAX) {
cliPrintf("0 %s", ftoa((float)var->config.max.max, buf));
}
}
return; // return from case for float only
}
switch(var->type & VALUE_MODE_MASK) {
case MODE_MAX:
case MODE_DIRECT:
cliPrintf("%d", value);
if (full) {
if ((var->type & VALUE_MODE_MASK) == MODE_DIRECT) {
cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
} else {
cliPrintf(" 0 %d", var->config.max.max);
}
}
break;
case MODE_LOOKUP:
if (var->config.lookup.tableIndex < ARRAYLEN(lookupTables)) {
cliPrintf(lookupTables[var->config.lookup.tableIndex].values[value]);
} else{
cliPrintf("VALUE %s OUT OF RANGE\r\n", var->name);
}
break;
}
}
static bool valuePtrEqualsDefault(uint8_t type, const void *ptr, const void *ptrDefault)
{
bool result = false;
switch (type & VALUE_TYPE_MASK) {
case VAR_UINT8:
result = *(uint8_t *)ptr == *(uint8_t *)ptrDefault;
break;
case VAR_INT8:
result = *(int8_t *)ptr == *(int8_t *)ptrDefault;
break;
case VAR_UINT16:
result = *(uint16_t *)ptr == *(uint16_t *)ptrDefault;
break;
case VAR_INT16:
result = *(int16_t *)ptr == *(int16_t *)ptrDefault;
break;
case VAR_UINT32:
result = *(uint32_t *)ptr == *(uint32_t *)ptrDefault;
break;
case VAR_FLOAT:
result = *(float *)ptr == *(float *)ptrDefault;
break;
}
return result;
}
typedef struct cliCurrentAndDefaultConfig_s {
const void *currentConfig; // the copy
const void *defaultConfig; // the PG value as set by default
} cliCurrentAndDefaultConfig_t;
static const cliCurrentAndDefaultConfig_t *getCurrentAndDefaultConfigs(pgn_t pgn)
{
static cliCurrentAndDefaultConfig_t ret;
switch (pgn) {
case PG_GYRO_CONFIG:
ret.currentConfig = &gyroConfigCopy;
ret.defaultConfig = gyroConfig();
break;
case PG_ACCELEROMETER_CONFIG:
ret.currentConfig = &accelerometerConfigCopy;
ret.defaultConfig = accelerometerConfig();
break;
#ifdef MAG
case PG_COMPASS_CONFIG:
ret.currentConfig = &compassConfigCopy;
ret.defaultConfig = compassConfig();
break;
#endif
#ifdef BARO
case PG_BAROMETER_CONFIG:
ret.currentConfig = &barometerConfigCopy;
ret.defaultConfig = barometerConfig();
break;
#endif
#ifdef PITOT
case PG_PITOTMETER_CONFIG:
ret.currentConfig = &pitotmeterConfigCopy;
ret.defaultConfig = pitotmeterConfig();
break;
#endif
case PG_FEATURE_CONFIG:
ret.currentConfig = &featureConfigCopy;
ret.defaultConfig = featureConfig();
break;
case PG_RX_CONFIG:
ret.currentConfig = &rxConfigCopy;
ret.defaultConfig = rxConfig();
break;
#ifdef BLACKBOX
case PG_BLACKBOX_CONFIG:
ret.currentConfig = &blackboxConfigCopy;
ret.defaultConfig = blackboxConfig();
break;
#endif
case PG_MOTOR_CONFIG:
ret.currentConfig = &motorConfigCopy;
ret.defaultConfig = motorConfig();
break;
case PG_FAILSAFE_CONFIG:
ret.currentConfig = &failsafeConfigCopy;
ret.defaultConfig = failsafeConfig();
break;
case PG_BOARD_ALIGNMENT:
ret.currentConfig = &boardAlignmentCopy;
ret.defaultConfig = boardAlignment();
break;
case PG_MIXER_CONFIG:
ret.currentConfig = &mixerConfigCopy;
ret.defaultConfig = mixerConfig();
break;
case PG_MOTOR_3D_CONFIG:
ret.currentConfig = &flight3DConfigCopy;
ret.defaultConfig = flight3DConfig();
break;
#ifdef USE_SERVOS
case PG_SERVO_CONFIG:
ret.currentConfig = &servoConfigCopy;
ret.defaultConfig = servoConfig();
break;
case PG_GIMBAL_CONFIG:
ret.currentConfig = &gimbalConfigCopy;
ret.defaultConfig = gimbalConfig();
break;
#endif
case PG_BATTERY_CONFIG:
ret.currentConfig = &batteryConfigCopy;
ret.defaultConfig = batteryConfig();
break;
case PG_SERIAL_CONFIG:
ret.currentConfig = &serialConfigCopy;
ret.defaultConfig = serialConfig();
break;
case PG_IMU_CONFIG:
ret.currentConfig = &imuConfigCopy;
ret.defaultConfig = imuConfig();
break;
case PG_RC_CONTROLS_CONFIG:
ret.currentConfig = &rcControlsConfigCopy;
ret.defaultConfig = rcControlsConfig();
break;
case PG_ARMING_CONFIG:
ret.currentConfig = &armingConfigCopy;
ret.defaultConfig = armingConfig();
break;
#ifdef GPS
case PG_GPS_CONFIG:
ret.currentConfig = &gpsConfigCopy;
ret.defaultConfig = gpsConfig();
break;
#endif
#ifdef NAV
case PG_POSITION_ESTIMATION_CONFIG:
ret.currentConfig = &positionEstimationConfigCopy;
ret.defaultConfig = positionEstimationConfig();
break;
case PG_NAV_CONFIG:
ret.currentConfig = &navConfigCopy;
ret.defaultConfig = navConfig();
break;
#endif
#ifdef TELEMETRY
case PG_TELEMETRY_CONFIG:
ret.currentConfig = &telemetryConfigCopy;
ret.defaultConfig = telemetryConfig();
break;
#endif
#ifdef LED_STRIP
case PG_LED_STRIP_CONFIG:
ret.currentConfig = &ledStripConfigCopy;
ret.defaultConfig = ledStripConfig();
break;
#endif
#ifdef OSD
case PG_OSD_CONFIG:
ret.currentConfig = &osdConfigCopy;
ret.defaultConfig = osdConfig();
break;
#endif
case PG_SYSTEM_CONFIG:
ret.currentConfig = &systemConfigCopy;
ret.defaultConfig = systemConfig();
break;
case PG_MODE_ACTIVATION_OPERATOR_CONFIG:
ret.currentConfig = &modeActivationOperatorConfigCopy;
ret.defaultConfig = modeActivationOperatorConfig();
break;
case PG_CONTROL_RATE_PROFILES:
ret.currentConfig = &controlRateProfilesCopy[0];
ret.defaultConfig = controlRateProfiles(0);
break;
case PG_PID_PROFILE:
ret.currentConfig = &pidProfileCopy[getConfigProfile()];
ret.defaultConfig = pidProfile();
break;
case PG_RX_CHANNEL_RANGE_CONFIG:
ret.currentConfig = &rxChannelRangeConfigsCopy[0];
ret.defaultConfig = rxChannelRangeConfigs(0);
break;
#ifdef USE_SERVOS
case PG_SERVO_MIXER:
ret.currentConfig = &customServoMixersCopy[0];
ret.defaultConfig = customServoMixers(0);
break;
case PG_SERVO_PARAMS:
ret.currentConfig = &servoParamsCopy[0];
ret.defaultConfig = servoParams(0);
break;
#endif
case PG_MOTOR_MIXER:
ret.currentConfig = &customMotorMixerCopy[0];
ret.defaultConfig = customMotorMixer(0);
break;
case PG_MODE_ACTIVATION_PROFILE:
ret.currentConfig = &modeActivationConditionsCopy[0];
ret.defaultConfig = modeActivationConditions(0);
break;
case PG_ADJUSTMENT_RANGE_CONFIG:
ret.currentConfig = &adjustmentRangesCopy[0];
ret.defaultConfig = adjustmentRanges(0);
break;
case PG_BEEPER_CONFIG:
ret.currentConfig = &beeperConfigCopy;
ret.defaultConfig = beeperConfig();
break;
#ifdef SONAR
case PG_RANGEFINDER_CONFIG:
ret.currentConfig = &rangefinderConfigCopy;
ret.defaultConfig = rangefinderConfig();
break;
#endif
default:
ret.currentConfig = NULL;
ret.defaultConfig = NULL;
break;
}
return &ret;
}
static uint16_t getValueOffset(const clivalue_t *value)
{
switch (value->type & VALUE_SECTION_MASK) {
case MASTER_VALUE:
case PROFILE_VALUE:
return value->offset; // Profile offset within a PG is handled by `getCurrentAndDefaultConfigs()`
case CONTROL_RATE_VALUE:
return value->offset + sizeof(controlRateConfig_t) * getConfigProfile();
}
return 0;
}
static void *getValuePointer(const clivalue_t *value)
{
const pgRegistry_t* rec = pgFind(value->pgn);
switch (value->type & VALUE_SECTION_MASK) {
case MASTER_VALUE:
case PROFILE_VALUE:
return pgIsSystem(rec) ? rec->address + value->offset : rec->address + value->offset + pgSize(rec) * getConfigProfile();
case CONTROL_RATE_VALUE:
return rec->address + value->offset + sizeof(controlRateConfig_t) * getConfigProfile();
}
return NULL;
}
static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
{
const char *format = "set %s = ";
const cliCurrentAndDefaultConfig_t *config = getCurrentAndDefaultConfigs(value->pgn);
if (config->currentConfig == NULL || config->defaultConfig == NULL) {
// has not been set up properly
cliPrintf("VALUE %s ERROR\r\n", value->name);
return;
}
const int valueOffset = getValueOffset(value);
switch (dumpMask & (DO_DIFF | SHOW_DEFAULTS)) {
case DO_DIFF:
if (valuePtrEqualsDefault(value->type, (uint8_t*)config->currentConfig + valueOffset, (uint8_t*)config->defaultConfig + valueOffset)) {
break;
}
// drop through, since not equal to default
case 0:
case SHOW_DEFAULTS:
cliPrintf(format, value->name);
printValuePointer(value, (uint8_t*)config->currentConfig + valueOffset, 0);
cliPrint("\r\n");
break;
}
}
static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
{
for (uint32_t i = 0; i < ARRAYLEN(valueTable); i++) {
const clivalue_t *value = &valueTable[i];
bufWriterFlush(cliWriter);
if ((value->type & VALUE_SECTION_MASK) == valueSection) {
dumpPgValue(value, dumpMask);
}
}
}
static void cliPrintVar(const clivalue_t *var, uint32_t full)
{
const void *ptr = getValuePointer(var);
printValuePointer(var, ptr, full);
}
static void cliPrintVarRange(const clivalue_t *var)
{
switch (var->type & VALUE_MODE_MASK) {
case (MODE_DIRECT):
cliPrintf("Allowed range: %d - %d\r\n", var->config.minmax.min, var->config.minmax.max);
break;
case (MODE_MAX):
cliPrintf("Allowed range: 0- %d\r\n", var->config.max.max);
break;
case (MODE_LOOKUP): {
const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
cliPrint("Allowed values:");
for (uint32_t i = 0; i < tableEntry->valueCount ; i++) {
if (i > 0)
cliPrint(",");
cliPrintf(" %s", tableEntry->values[i]);
}
cliPrint("\r\n");
}
break;
}
}
typedef union {
int32_t int_value;
float float_value;
} int_float_value_t;
static void cliSetVar(const clivalue_t *var, const int_float_value_t value)
{
void *ptr = getValuePointer(var);
switch (var->type & VALUE_TYPE_MASK) {
case VAR_UINT8:
case VAR_INT8:
*(int8_t *)ptr = value.int_value;
break;
case VAR_UINT16:
case VAR_INT16:
*(int16_t *)ptr = value.int_value;
break;
case VAR_UINT32:
*(uint32_t *)ptr = value.int_value;
break;
case VAR_FLOAT:
*(float *)ptr = (float)value.float_value;
break;
}
}
static void cliPrompt(void)
{
cliPrint("\r\n# ");
bufWriterFlush(cliWriter);
}
static void cliShowParseError(void)
{
cliPrint("Parse error\r\n");
}
static void cliShowArgumentRangeError(char *name, int min, int max)
{
cliPrintf("%s must be between %d and %d\r\n", name, min, max);
}
static const char *nextArg(const char *currentArg)
{
const char *ptr = strchr(currentArg, ' ');
while (ptr && *ptr == ' ') {
ptr++;
}
return ptr;
}
static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
{
for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
ptr = nextArg(ptr);
if (ptr) {
int val = atoi(ptr);
val = CHANNEL_VALUE_TO_STEP(val);
if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
if (argIndex == 0) {
range->startStep = val;
} else {
range->endStep = val;
}
(*validArgumentCount)++;
}
}
}
return ptr;
}
// Check if a string's length is zero
static bool isEmpty(const char *string)
{
return (string == NULL || *string == '\0') ? true : false;
}
#if defined(USE_ASSERT)
static void cliAssert(char *cmdline)
{
UNUSED(cmdline);
if (assertFailureLine) {
if (assertFailureFile) {
cliPrintf("Assertion failed at line %d, file %s\r\n", assertFailureLine, assertFailureFile);
}
else {
cliPrintf("Assertion failed at line %d\r\n", assertFailureLine);
}
}
else {
cliPrintf("No assert() failed\r\n");
}
}
#endif
#if defined(BOOTLOG)
static void cliBootlog(char *cmdline)
{
UNUSED(cmdline);
int bootEventCount = getBootlogEventCount();
#if defined(BOOTLOG_DESCRIPTIONS)
cliPrintf("Time Evt Description Parameters\r\n");
#else
cliPrintf("Time Evt Parameters\r\n");
#endif
for (int idx = 0; idx < bootEventCount; idx++) {
bootLogEntry_t * event = getBootlogEvent(idx);
#if defined(BOOTLOG_DESCRIPTIONS)
const char * eventDescription = getBootlogEventDescription(event->eventCode);
if (!eventDescription) {
eventDescription = "";
}
cliPrintf("%4d: %2d %22s ", event->timestamp, event->eventCode, eventDescription);
#else
cliPrintf("%4d: %2d ", event->timestamp, event->eventCode);
#endif
if (event->eventFlags & BOOT_EVENT_FLAGS_PARAM16) {
cliPrintf(" (%d, %d, %d, %d)\r\n", event->params.u16[0], event->params.u16[1], event->params.u16[2], event->params.u16[3]);
}
else if (event->eventFlags & BOOT_EVENT_FLAGS_PARAM32) {
cliPrintf(" (%d, %d)\r\n", event->params.u32[0], event->params.u32[1]);
}
else {
cliPrintf("\r\n");
}
}
}
#endif
static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
{
const char *format = "aux %u %u %u %u %u\r\n";
// print out aux channel settings
for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
const modeActivationCondition_t *mac = &modeActivationConditions[i];
bool equalsDefault = false;
if (defaultModeActivationConditions) {
const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
equalsDefault = mac->modeId == macDefault->modeId
&& mac->auxChannelIndex == macDefault->auxChannelIndex
&& mac->range.startStep == macDefault->range.startStep
&& mac->range.endStep == macDefault->range.endStep;
cliDefaultPrintf(dumpMask, equalsDefault, format,
i,
macDefault->modeId,
macDefault->auxChannelIndex,
MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep)
);
}
cliDumpPrintf(dumpMask, equalsDefault, format,
i,
mac->modeId,
mac->auxChannelIndex,
MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep)
);
}
}
static void cliAux(char *cmdline)
{
int i, val = 0;
const char *ptr;
if (isEmpty(cmdline)) {
printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
} else {
ptr = cmdline;
i = atoi(ptr++);
if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
uint8_t validArgumentCount = 0;
ptr = nextArg(ptr);
if (ptr) {
val = atoi(ptr);
if (val >= 0 && val < CHECKBOX_ITEM_COUNT) {
mac->modeId = val;
validArgumentCount++;
}
}
ptr = nextArg(ptr);
if (ptr) {
val = atoi(ptr);
if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
mac->auxChannelIndex = val;
validArgumentCount++;
}
}
ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
if (validArgumentCount != 4) {
memset(mac, 0, sizeof(modeActivationCondition_t));
}
} else {
cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
}
}
}
static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
{
const char *format = "serial %d %d %ld %ld %ld %ld\r\n";
for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
continue;
};
bool equalsDefault = false;
if (serialConfigDefault) {
equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
&& serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
&& serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
&& serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
&& serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
&& serialConfig->portConfigs[i].blackbox_baudrateIndex == serialConfigDefault->portConfigs[i].blackbox_baudrateIndex;
cliDefaultPrintf(dumpMask, equalsDefault, format,
serialConfigDefault->portConfigs[i].identifier,
serialConfigDefault->portConfigs[i].functionMask,
baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
);
}
cliDumpPrintf(dumpMask, equalsDefault, format,
serialConfig->portConfigs[i].identifier,
serialConfig->portConfigs[i].functionMask,
baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
);
}
}
static void cliSerial(char *cmdline)
{
if (isEmpty(cmdline)) {
printSerial(DUMP_MASTER, serialConfig(), NULL);
return;
}
serialPortConfig_t portConfig;
memset(&portConfig, 0 , sizeof(portConfig));
serialPortConfig_t *currentConfig;
uint8_t validArgumentCount = 0;
const char *ptr = cmdline;
int val = atoi(ptr++);
currentConfig = serialFindPortConfiguration(val);
if (currentConfig) {
portConfig.identifier = val;
validArgumentCount++;
}
ptr = nextArg(ptr);
if (ptr) {
val = atoi(ptr);
portConfig.functionMask = val & 0xFFFF;
validArgumentCount++;
}
for (int i = 0; i < 4; i ++) {
ptr = nextArg(ptr);
if (!ptr) {
break;
}
val = atoi(ptr);
uint8_t baudRateIndex = lookupBaudRateIndex(val);
if (baudRates[baudRateIndex] != (uint32_t) val) {
break;
}
switch(i) {
case 0:
if (baudRateIndex < BAUD_1200 || baudRateIndex > BAUD_115200) {
continue;
}
portConfig.msp_baudrateIndex = baudRateIndex;
break;
case 1:
if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
continue;
}
portConfig.gps_baudrateIndex = baudRateIndex;
break;
case 2:
if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
continue;
}
portConfig.telemetry_baudrateIndex = baudRateIndex;
break;
case 3:
if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_250000) {
continue;
}
portConfig.blackbox_baudrateIndex = baudRateIndex;
break;
}
validArgumentCount++;
}
if (validArgumentCount < 6) {
cliShowParseError();
return;
}
memcpy(currentConfig, &portConfig, sizeof(portConfig));
}
static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
{
const char *format = "adjrange %u %u %u %u %u %u %u\r\n";
// print out adjustment ranges channel settings
for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
const adjustmentRange_t *ar = &adjustmentRanges[i];
bool equalsDefault = false;
if (defaultAdjustmentRanges) {
const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
&& ar->range.startStep == arDefault->range.startStep
&& ar->range.endStep == arDefault->range.endStep
&& ar->adjustmentFunction == arDefault->adjustmentFunction
&& ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
&& ar->adjustmentIndex == arDefault->adjustmentIndex;
cliDefaultPrintf(dumpMask, equalsDefault, format,
i,
arDefault->adjustmentIndex,
arDefault->auxChannelIndex,
MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
arDefault->adjustmentFunction,
arDefault->auxSwitchChannelIndex
);
}
cliDumpPrintf(dumpMask, equalsDefault, format,
i,
ar->adjustmentIndex,
ar->auxChannelIndex,
MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
ar->adjustmentFunction,
ar->auxSwitchChannelIndex
);
}
}
static void cliAdjustmentRange(char *cmdline)
{
int i, val = 0;
const char *ptr;
if (isEmpty(cmdline)) {
printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
} else {
ptr = cmdline;
i = atoi(ptr++);
if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
adjustmentRange_t *ar = adjustmentRangesMutable(i);
uint8_t validArgumentCount = 0;
ptr = nextArg(ptr);
if (ptr) {
val = atoi(ptr);
if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
ar->adjustmentIndex = val;
validArgumentCount++;
}
}
ptr = nextArg(ptr);
if (ptr) {
val = atoi(ptr);
if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
ar->auxChannelIndex = val;
validArgumentCount++;
}
}
ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
ptr = nextArg(ptr);
if (ptr) {
val = atoi(ptr);
if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
ar->adjustmentFunction = val;
validArgumentCount++;
}
}
ptr = nextArg(ptr);
if (ptr) {
val = atoi(ptr);
if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
ar->auxSwitchChannelIndex = val;
validArgumentCount++;
}
}
if (validArgumentCount != 6) {
memset(ar, 0, sizeof(adjustmentRange_t));
cliShowParseError();
}
} else {
cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
}
}
}
#ifndef USE_QUAD_MIXER_ONLY
static void printMotorMix(uint8_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer)
{
const char *format = "mmix %d %s %s %s %s\r\n";
char buf0[8];
char buf1[8];
char buf2[8];
char buf3[8];
for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
if (customMotorMixer[i].throttle == 0.0f)
break;
const float thr = customMotorMixer[i].throttle;
const float roll = customMotorMixer[i].roll;
const float pitch = customMotorMixer[i].pitch;
const float yaw = customMotorMixer[i].yaw;
bool equalsDefault = false;
if (defaultCustomMotorMixer) {
const float thrDefault = defaultCustomMotorMixer[i].throttle;
const float rollDefault = defaultCustomMotorMixer[i].roll;
const float pitchDefault = defaultCustomMotorMixer[i].pitch;
const float yawDefault = defaultCustomMotorMixer[i].yaw;
const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
cliDefaultPrintf(dumpMask, equalsDefault, format,
i,
ftoa(thrDefault, buf0),
ftoa(rollDefault, buf1),
ftoa(pitchDefault, buf2),
ftoa(yawDefault, buf3));
}
cliDumpPrintf(dumpMask, equalsDefault, format,
i,
ftoa(thr, buf0),
ftoa(roll, buf1),
ftoa(pitch, buf2),
ftoa(yaw, buf3));
}
}
#endif // USE_QUAD_MIXER_ONLY
static void cliMotorMix(char *cmdline)
{
#ifdef USE_QUAD_MIXER_ONLY
UNUSED(cmdline);
#else
int check = 0;
uint8_t len;
const char *ptr;
if (isEmpty(cmdline)) {
printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
} else if (strncasecmp(cmdline, "reset", 5) == 0) {
// erase custom mixer
for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
customMotorMixerMutable(i)->throttle = 0.0f;
}
} else if (strncasecmp(cmdline, "load", 4) == 0) {
ptr = nextArg(cmdline);
if (ptr) {
len = strlen(ptr);
for (uint32_t i = 0; ; i++) {
if (mixerNames[i] == NULL) {
cliPrint("Invalid name\r\n");
break;
}
if (strncasecmp(ptr, mixerNames[i], len) == 0) {
mixerLoadMix(i, customMotorMixerMutable(0));
cliPrintf("Loaded %s\r\n", mixerNames[i]);
cliMotorMix("");
break;
}
}
}
} else {
ptr = cmdline;
uint32_t i = atoi(ptr); // get motor number
if (i < MAX_SUPPORTED_MOTORS) {
ptr = nextArg(ptr);
if (ptr) {
customMotorMixerMutable(i)->throttle = fastA2F(ptr);
check++;
}
ptr = nextArg(ptr);
if (ptr) {
customMotorMixerMutable(i)->roll = fastA2F(ptr);
check++;
}
ptr = nextArg(ptr);
if (ptr) {
customMotorMixerMutable(i)->pitch = fastA2F(ptr);
check++;
}
ptr = nextArg(ptr);
if (ptr) {
customMotorMixerMutable(i)->yaw = fastA2F(ptr);
check++;
}
if (check != 4) {
cliShowParseError();
} else {
printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
}
} else {
cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
}
}
#endif
}
static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
{
const char *format = "rxrange %u %u %u\r\n";
for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
bool equalsDefault = false;
if (defaultChannelRangeConfigs) {
equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
&& channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
cliDefaultPrintf(dumpMask, equalsDefault, format,
i,
defaultChannelRangeConfigs[i].min,
defaultChannelRangeConfigs[i].max
);
}
cliDumpPrintf(dumpMask, equalsDefault, format,
i,
channelRangeConfigs[i].min,
channelRangeConfigs[i].max
);
}
}
static void cliRxRange(char *cmdline)
{
int i, validArgumentCount = 0;
const char *ptr;
if (isEmpty(cmdline)) {
printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
} else if (strcasecmp(cmdline, "reset") == 0) {
resetAllRxChannelRangeConfigurations();
} else {
ptr = cmdline;
i = atoi(ptr);
if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
int rangeMin, rangeMax;
ptr = nextArg(ptr);
if (ptr) {
rangeMin = atoi(ptr);
validArgumentCount++;
}
ptr = nextArg(ptr);
if (ptr) {
rangeMax = atoi(ptr);
validArgumentCount++;
}
if (validArgumentCount != 2) {
cliShowParseError();
} else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
cliShowParseError();
} else {
rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
channelRangeConfig->min = rangeMin;
channelRangeConfig->max = rangeMax;
}
} else {
cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
}
}
}
#ifdef LED_STRIP
static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
{
const char *format = "led %u %s\r\n";
char ledConfigBuffer[20];
char ledConfigDefaultBuffer[20];
for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
ledConfig_t ledConfig = ledConfigs[i];
generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
bool equalsDefault = false;
if (defaultLedConfigs) {
ledConfig_t ledConfigDefault = defaultLedConfigs[i];
equalsDefault = ledConfig == ledConfigDefault;
generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
cliDefaultPrintf(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
}
cliDumpPrintf(dumpMask, equalsDefault, format, i, ledConfigBuffer);
}
}
static void cliLed(char *cmdline)
{
int i;
const char *ptr;
if (isEmpty(cmdline)) {
printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
} else {
ptr = cmdline;
i = atoi(ptr);
if (i < LED_MAX_STRIP_LENGTH) {
ptr = nextArg(cmdline);
if (!parseLedStripConfig(i, ptr)) {
cliShowParseError();
}
} else {
cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
}
}
}
static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
{
const char *format = "color %u %d,%u,%u\r\n";
for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
const hsvColor_t *color = &colors[i];
bool equalsDefault = false;
if (defaultColors) {
const hsvColor_t *colorDefault = &defaultColors[i];
equalsDefault = color->h == colorDefault->h
&& color->s == colorDefault->s
&& color->v == colorDefault->v;
cliDefaultPrintf(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
}
cliDumpPrintf(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
}
}
static void cliColor(char *cmdline)
{
if (isEmpty(cmdline)) {
printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
} else {
const char *ptr = cmdline;
const int i = atoi(ptr);
if (i < LED_CONFIGURABLE_COLOR_COUNT) {
ptr = nextArg(cmdline);
if (!parseColor(i, ptr)) {
cliShowParseError();
}
} else {
cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
}
}
}
static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
{
const char *format = "mode_color %u %u %u\r\n";
for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
int colorIndex = ledStripConfig->modeColors[i].color[j];
bool equalsDefault = false;
if (defaultLedStripConfig) {
int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
equalsDefault = colorIndex == colorIndexDefault;
cliDefaultPrintf(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
}
cliDumpPrintf(dumpMask, equalsDefault, format, i, j, colorIndex);
}
}
for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
const int colorIndex = ledStripConfig->specialColors.color[j];
bool equalsDefault = false;
if (defaultLedStripConfig) {
const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
equalsDefault = colorIndex == colorIndexDefault;
cliDefaultPrintf(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
}
cliDumpPrintf(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
}
}
static void cliModeColor(char *cmdline)
{
if (isEmpty(cmdline)) {
printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
} else {
enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
int args[ARGS_COUNT];
int argNo = 0;
const char* ptr = strtok(cmdline, " ");
while (ptr && argNo < ARGS_COUNT) {
args[argNo++] = atoi(ptr);
ptr = strtok(NULL, " ");
}
if (ptr != NULL || argNo != ARGS_COUNT) {
cliShowParseError();
return;
}
int modeIdx = args[MODE];
int funIdx = args[FUNCTION];
int color = args[COLOR];
if(!setModeColor(modeIdx, funIdx, color)) {
cliShowParseError();
return;
}
// values are validated
cliPrintf("mode_color %u %u %u\r\n", modeIdx, funIdx, color);
}
}
#endif
#ifdef USE_SERVOS
static void printServo(uint8_t dumpMask, const servoParam_t *servoParam, const servoParam_t *defaultServoParam)
{
// print out servo settings
const char *format = "servo %u %d %d %d %d %d %d %d\r\n";
for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
const servoParam_t *servoConf = &servoParam[i];
bool equalsDefault = false;
if (defaultServoParam) {
const servoParam_t *servoConfDefault = &defaultServoParam[i];
equalsDefault = servoConf->min == servoConfDefault->min
&& servoConf->max == servoConfDefault->max
&& servoConf->middle == servoConfDefault->middle
&& servoConf->angleAtMin == servoConfDefault->angleAtMax
&& servoConf->rate == servoConfDefault->rate
&& servoConf->forwardFromChannel == servoConfDefault->forwardFromChannel;
cliDefaultPrintf(dumpMask, equalsDefault, format,
i,
servoConfDefault->min,
servoConfDefault->max,
servoConfDefault->middle,
servoConfDefault->angleAtMin,
servoConfDefault->angleAtMax,
servoConfDefault->rate,
servoConfDefault->forwardFromChannel
);
}
cliDumpPrintf(dumpMask, equalsDefault, format,
i,
servoConf->min,
servoConf->max,
servoConf->middle,
servoConf->angleAtMin,
servoConf->angleAtMax,
servoConf->rate,
servoConf->forwardFromChannel
);
}
// print servo directions
if (defaultServoParam) {
for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
const servoParam_t *servoConf = &servoParam[i];
const servoParam_t *servoConfDefault = &defaultServoParam[i];
bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
const char *format = "smix reverse %d %d r\r\n";
if (servoConfDefault->reversedSources & (1 << channel)) {
cliDefaultPrintf(dumpMask, equalsDefault, format, i , channel);
}
if (servoConf->reversedSources & (1 << channel)) {
cliDumpPrintf(dumpMask, equalsDefault, format, i , channel);
}
}
}
}
}
static void cliServo(char *cmdline)
{
enum { SERVO_ARGUMENT_COUNT = 8 };
int16_t arguments[SERVO_ARGUMENT_COUNT];
servoParam_t *servo;
int i;
const char *ptr;
if (isEmpty(cmdline)) {
printServo(DUMP_MASTER, servoParams(0), NULL);
} else {
int validArgumentCount = 0;
ptr = cmdline;
// Command line is integers (possibly negative) separated by spaces, no other characters allowed.
// If command line doesn't fit the format, don't modify the config
while (*ptr) {
if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
cliShowParseError();
return;
}
arguments[validArgumentCount++] = atoi(ptr);
do {
ptr++;
} while (*ptr >= '0' && *ptr <= '9');
} else if (*ptr == ' ') {
ptr++;
} else {
cliShowParseError();
return;
}
}
enum {INDEX = 0, MIN, MAX, MIDDLE, ANGLE_AT_MIN, ANGLE_AT_MAX, RATE, FORWARD};
i = arguments[INDEX];
// Check we got the right number of args and the servo index is correct (don't validate the other values)
if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
cliShowParseError();
return;
}
servo = servoParamsMutable(i);
if (
arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
arguments[RATE] < -100 || arguments[RATE] > 100 ||
arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT ||
arguments[ANGLE_AT_MIN] < 0 || arguments[ANGLE_AT_MIN] > 180 ||
arguments[ANGLE_AT_MAX] < 0 || arguments[ANGLE_AT_MAX] > 180
) {
cliShowParseError();
return;
}
servo->min = arguments[1];
servo->max = arguments[2];
servo->middle = arguments[3];
servo->angleAtMin = arguments[4];
servo->angleAtMax = arguments[5];
servo->rate = arguments[6];
servo->forwardFromChannel = arguments[7];
}
}
#endif
#ifdef USE_SERVOS
static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
{
const char *format = "smix %d %d %d %d %d %d %d\r\n";
for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
servoMixer_t customServoMixer = customServoMixers[i];
if (customServoMixer.rate == 0) {
break;
}
bool equalsDefault = false;
if (defaultCustomServoMixers) {
servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
&& customServoMixer.inputSource == customServoMixerDefault.inputSource
&& customServoMixer.rate == customServoMixerDefault.rate
&& customServoMixer.speed == customServoMixerDefault.speed
&& customServoMixer.min == customServoMixerDefault.min
&& customServoMixer.max == customServoMixerDefault.max;
cliDefaultPrintf(dumpMask, equalsDefault, format,
i,
customServoMixerDefault.targetChannel,
customServoMixerDefault.inputSource,
customServoMixerDefault.rate,
customServoMixerDefault.speed,
customServoMixerDefault.min,
customServoMixerDefault.max
);
}
cliDumpPrintf(dumpMask, equalsDefault, format,
i,
customServoMixer.targetChannel,
customServoMixer.inputSource,
customServoMixer.rate,
customServoMixer.speed,
customServoMixer.min,
customServoMixer.max
);
}
}
static void cliServoMix(char *cmdline)
{
uint8_t len;
const char *ptr;
int args[8], check = 0;
len = strlen(cmdline);
if (len == 0) {
printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
} else if (strncasecmp(cmdline, "reset", 5) == 0) {
// erase custom mixer
pgResetCopy(customServoMixersMutable(0), PG_SERVO_MIXER);
for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
servoParamsMutable(i)->reversedSources = 0;
}
} else if (strncasecmp(cmdline, "load", 4) == 0) {
ptr = nextArg(cmdline);
if (ptr) {
len = strlen(ptr);
for (uint32_t i = 0; ; i++) {
if (mixerNames[i] == NULL) {
cliPrintf("Invalid name\r\n");
break;
}
if (strncasecmp(ptr, mixerNames[i], len) == 0) {
servoMixerLoadMix(i);
cliPrintf("Loaded %s\r\n", mixerNames[i]);
cliServoMix("");
break;
}
}
}
} else if (strncasecmp(cmdline, "reverse", 7) == 0) {
enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
char *ptr = strchr(cmdline, ' ');
len = strlen(ptr);
if (len == 0) {
cliPrintf("s");
for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
cliPrintf("\ti%d", inputSource);
cliPrintf("\r\n");
for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
cliPrintf("%d", servoIndex);
for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
cliPrintf("\r\n");
}
return;
}
ptr = strtok(ptr, " ");
while (ptr != NULL && check < ARGS_COUNT - 1) {
args[check++] = atoi(ptr);
ptr = strtok(NULL, " ");
}
if (ptr == NULL || check != ARGS_COUNT - 1) {
cliShowParseError();
return;
}
if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
&& args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
&& (*ptr == 'r' || *ptr == 'n')) {
if (*ptr == 'r')
servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
else
servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
} else
cliShowParseError();
cliServoMix("reverse");
} else {
enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, ARGS_COUNT};
ptr = strtok(cmdline, " ");
while (ptr != NULL && check < ARGS_COUNT) {
args[check++] = atoi(ptr);
ptr = strtok(NULL, " ");
}
if (ptr != NULL || check != ARGS_COUNT) {
cliShowParseError();
return;
}
int32_t i = args[RULE];
if (i >= 0 && i < MAX_SERVO_RULES &&
args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
args[RATE] >= -125 && args[RATE] <= 125 &&
args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
args[MIN] >= 0 && args[MIN] <= 100 &&
args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX]) {
customServoMixersMutable(i)->targetChannel = args[TARGET];
customServoMixersMutable(i)->inputSource = args[INPUT];
customServoMixersMutable(i)->rate = args[RATE];
customServoMixersMutable(i)->speed = args[SPEED];
customServoMixersMutable(i)->min = args[MIN];
customServoMixersMutable(i)->max = args[MAX];
cliServoMix("");
} else {
cliShowParseError();
}
}
}
#endif
#ifdef USE_SDCARD
static void cliWriteBytes(const uint8_t *buffer, int count)
{
while (count > 0) {
cliWrite(*buffer);
buffer++;
count--;
}
}
static void cliSdInfo(char *cmdline)
{
UNUSED(cmdline);
cliPrint("SD card: ");
if (!sdcard_isInserted()) {
cliPrint("None inserted\r\n");
return;
}
if (!sdcard_isInitialized()) {
cliPrint("Startup failed\r\n");
return;
}
const sdcardMetadata_t *metadata = sdcard_getMetadata();
cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
metadata->manufacturerID,
metadata->numBlocks / 2, /* One block is half a kB */
metadata->productionMonth,
metadata->productionYear,
metadata->productRevisionMajor,
metadata->productRevisionMinor
);
cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
cliPrint("'\r\n" "Filesystem: ");
switch (afatfs_getFilesystemState()) {
case AFATFS_FILESYSTEM_STATE_READY:
cliPrint("Ready");
break;
case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
cliPrint("Initializing");
break;
case AFATFS_FILESYSTEM_STATE_UNKNOWN:
case AFATFS_FILESYSTEM_STATE_FATAL:
cliPrint("Fatal");
switch (afatfs_getLastError()) {
case AFATFS_ERROR_BAD_MBR:
cliPrint(" - no FAT MBR partitions");
break;
case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
cliPrint(" - bad FAT header");
break;
case AFATFS_ERROR_GENERIC:
case AFATFS_ERROR_NONE:
; // Nothing more detailed to print
break;
}
break;
}
cliPrint("\r\n");
}
#endif
#ifdef USE_FLASHFS
static void cliFlashInfo(char *cmdline)
{
const flashGeometry_t *layout = flashfsGetGeometry();
UNUSED(cmdline);
cliPrintf("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u\r\n",
layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
}
static void cliFlashErase(char *cmdline)
{
UNUSED(cmdline);
cliPrintf("Erasing...\r\n");
flashfsEraseCompletely();
while (!flashfsIsReady()) {
delay(100);
}
cliPrintf("Done.\r\n");
}
#ifdef USE_FLASH_TOOLS
static void cliFlashWrite(char *cmdline)
{
uint32_t address = atoi(cmdline);
char *text = strchr(cmdline, ' ');
if (!text) {
cliShowParseError();
} else {
flashfsSeekAbs(address);
flashfsWrite((uint8_t*)text, strlen(text), true);
flashfsFlushSync();
cliPrintf("Wrote %u bytes at %u.\r\n", strlen(text), address);
}
}
static void cliFlashRead(char *cmdline)
{
uint32_t address = atoi(cmdline);
uint32_t length;
uint8_t buffer[32];
char *nextArg = strchr(cmdline, ' ');
if (!nextArg) {
cliShowParseError();
} else {
length = atoi(nextArg);
cliPrintf("Reading %u bytes at %u:\r\n", length, address);
while (length > 0) {
int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
for (int i = 0; i < bytesRead; i++) {
cliWrite(buffer[i]);
}
length -= bytesRead;
address += bytesRead;
if (bytesRead == 0) {
//Assume we reached the end of the volume or something fatal happened
break;
}
}
cliPrintf("\r\n");
}
}
#endif
#endif
static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
{
uint32_t mask = featureConfig->enabledFeatures;
uint32_t defaultMask = featureConfigDefault->enabledFeatures;
for (uint32_t i = 0; ; i++) { // disable all feature first
if (featureNames[i] == NULL)
break;
if (featureNames[i][0] == '\0')
continue;
const char *format = "feature -%s\r\n";
cliDefaultPrintf(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
cliDumpPrintf(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
}
for (uint32_t i = 0; ; i++) { // reenable what we want.
if (featureNames[i] == NULL)
break;
if (featureNames[i][0] == '\0')
continue;
const char *format = "feature %s\r\n";
if (defaultMask & (1 << i)) {
cliDefaultPrintf(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
}
if (mask & (1 << i)) {
cliDumpPrintf(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
}
}
}
static void cliFeature(char *cmdline)
{
uint32_t len = strlen(cmdline);
uint32_t mask = featureMask();
if (len == 0) {
cliPrint("Enabled: ");
for (uint32_t i = 0; ; i++) {
if (featureNames[i] == NULL)
break;
if (featureNames[i][0] == '\0')
continue;
if (mask & (1 << i))
cliPrintf("%s ", featureNames[i]);
}
cliPrint("\r\n");
} else if (strncasecmp(cmdline, "list", len) == 0) {
cliPrint("Available: ");
for (uint32_t i = 0; ; i++) {
if (featureNames[i] == NULL)
break;
if (featureNames[i][0] == '\0')
continue;
cliPrintf("%s ", featureNames[i]);
}
cliPrint("\r\n");
return;
} else {
bool remove = false;
if (cmdline[0] == '-') {
// remove feature
remove = true;
cmdline++; // skip over -
len--;
}
for (uint32_t i = 0; ; i++) {
if (featureNames[i] == NULL) {
cliPrint("Invalid name\r\n");
break;
}
if (strncasecmp(cmdline, featureNames[i], len) == 0) {
mask = 1 << i;
#ifndef GPS
if (mask & FEATURE_GPS) {
cliPrint("unavailable\r\n");
break;
}
#endif
if (remove) {
featureClear(mask);
cliPrint("Disabled");
} else {
featureSet(mask);
cliPrint("Enabled");
}
cliPrintf(" %s\r\n", featureNames[i]);
break;
}
}
}
}
#ifdef BEEPER
static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
{
const uint8_t beeperCount = beeperTableEntryCount();
const uint32_t mask = beeperConfig->beeper_off_flags;
const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
for (int i = 0; i < beeperCount - 2; i++) {
const char *formatOff = "beeper -%s\r\n";
const char *formatOn = "beeper %s\r\n";
cliDefaultPrintf(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOn : formatOff, beeperNameForTableIndex(i));
cliDumpPrintf(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOff : formatOn, beeperNameForTableIndex(i));
}
}
static void cliBeeper(char *cmdline)
{
uint32_t len = strlen(cmdline);
uint8_t beeperCount = beeperTableEntryCount();
uint32_t mask = getBeeperOffMask();
if (len == 0) {
cliPrintf("Disabled:");
for (int32_t i = 0; ; i++) {
if (i == beeperCount - 2){
if (mask == 0)
cliPrint(" none");
break;
}
if (mask & (1 << (beeperModeForTableIndex(i) - 1)))
cliPrintf(" %s", beeperNameForTableIndex(i));
}
cliPrint("\r\n");
} else if (strncasecmp(cmdline, "list", len) == 0) {
cliPrint("Available:");
for (uint32_t i = 0; i < beeperCount; i++)
cliPrintf(" %s", beeperNameForTableIndex(i));
cliPrint("\r\n");
return;
} else {
bool remove = false;
if (cmdline[0] == '-') {
remove = true; // this is for beeper OFF condition
cmdline++;
len--;
}
for (uint32_t i = 0; ; i++) {
if (i == beeperCount) {
cliPrint("Invalid name\r\n");
break;
}
if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
if (remove) { // beeper off
if (i == BEEPER_ALL-1)
beeperOffSetAll(beeperCount-2);
else
if (i == BEEPER_PREFERENCE-1)
setBeeperOffMask(getPreferredBeeperOffMask());
else {
mask = 1 << (beeperModeForTableIndex(i) - 1);
beeperOffSet(mask);
}
cliPrint("Disabled");
}
else { // beeper on
if (i == BEEPER_ALL-1)
beeperOffClearAll();
else
if (i == BEEPER_PREFERENCE-1)
setPreferredBeeperOffMask(getBeeperOffMask());
else {
mask = 1 << (beeperModeForTableIndex(i) - 1);
beeperOffClear(mask);
}
cliPrint("Enabled");
}
cliPrintf(" %s\r\n", beeperNameForTableIndex(i));
break;
}
}
}
}
#endif
static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
{
bool equalsDefault = true;
char buf[16];
char bufDefault[16];
uint32_t i;
for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
if (defaultRxConfig) {
bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
}
}
buf[i] = '\0';
const char *formatMap = "map %s\r\n";
cliDefaultPrintf(dumpMask, equalsDefault, formatMap, bufDefault);
cliDumpPrintf(dumpMask, equalsDefault, formatMap, buf);
}
static void cliMap(char *cmdline)
{
uint32_t len;
char out[9];
len = strlen(cmdline);
if (len == 8) {
// uppercase it
for (uint32_t i = 0; i < 8; i++)
cmdline[i] = toupper((unsigned char)cmdline[i]);
for (uint32_t i = 0; i < 8; i++) {
if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i]))
continue;
cliShowParseError();
return;
}
parseRcChannels(cmdline);
}
cliPrint("Map: ");
uint32_t i;
for (i = 0; i < 8; i++)
out[rxConfig()->rcmap[i]] = rcChannelLetters[i];
out[i] = '\0';
cliPrintf("%s\r\n", out);
}
static const char *checkCommand(const char *cmdLine, const char *command)
{
if(!strncasecmp(cmdLine, command, strlen(command)) // command names match
&& !isalnum((unsigned)cmdLine[strlen(command)])) { // next characted in bufffer is not alphanumeric (command is correctly terminated)
return cmdLine + strlen(command) + 1;
} else {
return 0;
}
}
static void cliRebootEx(bool bootLoader)
{
cliPrint("\r\nRebooting");
bufWriterFlush(cliWriter);
waitForSerialPortToFinishTransmitting(cliPort);
stopMotors();
stopPwmAllMotors();
delay(1000);
if (bootLoader) {
systemResetToBootloader();
return;
}
systemReset();
}
static void cliReboot(void)
{
cliRebootEx(false);
}
static void cliDfu(char *cmdline)
{
UNUSED(cmdline);
#ifndef CLI_MINIMAL_VERBOSITY
cliPrint("\r\nRestarting in DFU mode");
#endif
cliRebootEx(true);
}
static void cliExit(char *cmdline)
{
UNUSED(cmdline);
#ifndef CLI_MINIMAL_VERBOSITY
cliPrint("\r\nLeaving CLI mode, unsaved changes lost.\r\n");
#endif
bufWriterFlush(cliWriter);
*cliBuffer = '\0';
bufferIndex = 0;
cliMode = 0;
// incase a motor was left running during motortest, clear it here
mixerResetDisarmedMotors();
cliReboot();
cliWriter = NULL;
}
#ifdef GPS
static void cliGpsPassthrough(char *cmdline)
{
UNUSED(cmdline);
gpsEnablePassthrough(cliPort);
}
#endif
#ifndef USE_QUAD_MIXER_ONLY
static void cliMixer(char *cmdline)
{
int len;
len = strlen(cmdline);
if (len == 0) {
cliPrintf("Mixer: %s\r\n", mixerNames[mixerConfigMutable()->mixerMode - 1]);
return;
} else if (strncasecmp(cmdline, "list", len) == 0) {
cliPrint("Available mixers: ");
for (uint32_t i = 0; ; i++) {
if (mixerNames[i] == NULL)
break;
cliPrintf("%s ", mixerNames[i]);
}
cliPrint("\r\n");
return;
}
for (uint32_t i = 0; ; i++) {
if (mixerNames[i] == NULL) {
cliPrint("Invalid name\r\n");
return;
}
if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
mixerConfigMutable()->mixerMode = i + 1;
break;
}
}
cliMixer("");
}
#endif
static void cliMotor(char *cmdline)
{
int motor_index = 0;
int motor_value = 0;
int index = 0;
char *pch = NULL;
char *saveptr;
if (isEmpty(cmdline)) {
cliShowParseError();
return;
}
pch = strtok_r(cmdline, " ", &saveptr);
while (pch != NULL) {
switch (index) {
case 0:
motor_index = atoi(pch);
break;
case 1:
motor_value = atoi(pch);
break;
}
index++;
pch = strtok_r(NULL, " ", &saveptr);
}
if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
return;
}
if (index == 2) {
if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
cliShowArgumentRangeError("value", 1000, 2000);
return;
} else {
motor_disarmed[motor_index] = motor_value;
}
}
cliPrintf("motor %d: %d\r\n", motor_index, motor_disarmed[motor_index]);
}
#ifdef PLAY_SOUND
static void cliPlaySound(char *cmdline)
{
int i;
const char *name;
static int lastSoundIdx = -1;
if (isEmpty(cmdline)) {
i = lastSoundIdx + 1; //next sound index
if ((name=beeperNameForTableIndex(i)) == NULL) {
while (true) { //no name for index; try next one
if (++i >= beeperTableEntryCount())
i = 0; //if end then wrap around to first entry
if ((name=beeperNameForTableIndex(i)) != NULL)
break; //if name OK then play sound below
if (i == lastSoundIdx + 1) { //prevent infinite loop
cliPrintf("Error playing sound\r\n");
return;
}
}
}
} else { //index value was given
i = atoi(cmdline);
if ((name=beeperNameForTableIndex(i)) == NULL) {
cliPrintf("No sound for index %d\r\n", i);
return;
}
}
lastSoundIdx = i;
beeperSilence();
cliPrintf("Playing sound %d: %s\r\n", i, name);
beeper(beeperModeForTableIndex(i));
}
#endif
static void cliProfile(char *cmdline)
{
// CLI profile index is 1-based
if (isEmpty(cmdline)) {
cliPrintf("profile %d\r\n", getConfigProfile() + 1);
return;
} else {
const int i = atoi(cmdline) - 1;
if (i >= 0 && i < MAX_PROFILE_COUNT) {
setConfigProfileAndWriteEEPROM(i);
cliProfile("");
}
}
}
static void cliDumpProfile(uint8_t profileIndex, uint8_t dumpMask)
{
if (profileIndex >= MAX_PROFILE_COUNT) {
// Faulty values
return;
}
setConfigProfile(profileIndex);
cliPrintHashLine("profile");
cliPrintf("profile %d\r\n\r\n", getConfigProfile() + 1);
dumpAllValues(PROFILE_VALUE, dumpMask);
dumpAllValues(CONTROL_RATE_VALUE, dumpMask);
}
static void cliSave(char *cmdline)
{
UNUSED(cmdline);
cliPrint("Saving");
//copyCurrentProfileToProfileSlot(getConfigProfile();
writeEEPROM();
cliReboot();
}
static void cliDefaults(char *cmdline)
{
UNUSED(cmdline);
cliPrint("Resetting to defaults");
resetEEPROM();
if (!checkCommand(cmdline, "noreboot"))
cliReboot();
}
static void cliGet(char *cmdline)
{
const clivalue_t *val;
int matchedCommands = 0;
for (uint32_t i = 0; i < ARRAYLEN(valueTable); i++) {
if (strstr(valueTable[i].name, cmdline)) {
val = &valueTable[i];
cliPrintf("%s = ", valueTable[i].name);
cliPrintVar(val, 0);
cliPrint("\r\n");
cliPrintVarRange(val);
cliPrint("\r\n");
matchedCommands++;
}
}
if (matchedCommands) {
return;
}
cliPrint("Invalid name\r\n");
}
static void cliSet(char *cmdline)
{
uint32_t len;
const clivalue_t *val;
char *eqptr = NULL;
len = strlen(cmdline);
if (len == 0 || (len == 1 && cmdline[0] == '*')) {
cliPrint("Current settings: \r\n");
for (uint32_t i = 0; i < ARRAYLEN(valueTable); i++) {
val = &valueTable[i];
cliPrintf("%s = ", valueTable[i].name);
cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
cliPrint("\r\n");
}
} else if ((eqptr = strstr(cmdline, "=")) != NULL) {
// has equals
char *lastNonSpaceCharacter = eqptr;
while (*(lastNonSpaceCharacter - 1) == ' ') {
lastNonSpaceCharacter--;
}
uint8_t variableNameLength = lastNonSpaceCharacter - cmdline;
// skip the '=' and any ' ' characters
eqptr++;
while (*(eqptr) == ' ') {
eqptr++;
}
for (uint32_t i = 0; i < ARRAYLEN(valueTable); i++) {
val = &valueTable[i];
// ensure exact match when setting to prevent setting variables with shorter names
if (strncasecmp(cmdline, valueTable[i].name, strlen(valueTable[i].name)) == 0 && variableNameLength == strlen(valueTable[i].name)) {
bool changeValue = false;
int_float_value_t tmp = {0};
const int mode = valueTable[i].type & VALUE_MODE_MASK;
switch (mode) {
case MODE_MAX:
case MODE_DIRECT: {
if(*eqptr != 0 && strspn(eqptr, "0123456789.+-") == strlen(eqptr)) {
int32_t value = 0;
float valuef = 0;
value = atoi(eqptr);
valuef = fastA2F(eqptr);
// note: compare float values
if ((mode == MODE_DIRECT && (valuef >= valueTable[i].config.minmax.min && valuef <= valueTable[i].config.minmax.max))
|| (mode == MODE_MAX && (valuef >= 0 && valuef <= valueTable[i].config.max.max))) {
if ((valueTable[i].type & VALUE_TYPE_MASK) == VAR_FLOAT)
tmp.float_value = valuef;
else
tmp.int_value = value;
changeValue = true;
}
}
}
break;
case MODE_LOOKUP: {
const lookupTableEntry_t *tableEntry = &lookupTables[valueTable[i].config.lookup.tableIndex];
bool matched = false;
for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
if (matched) {
tmp.int_value = tableValueIndex;
changeValue = true;
}
}
}
break;
}
if (changeValue) {
cliSetVar(val, tmp);
cliPrintf("%s set to ", valueTable[i].name);
cliPrintVar(val, 0);
} else {
cliPrint("Invalid value.");
cliPrintVarRange(val);
cliPrint("\r\n");
}
return;
}
}
cliPrint("Invalid name\r\n");
} else {
// no equals, check for matching variables.
cliGet(cmdline);
}
}
static const char * getBatteryStateString(void)
{
static const char * const batteryStateStrings[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT"};
return batteryStateStrings[getBatteryState()];
}
static void cliStatus(char *cmdline)
{
UNUSED(cmdline);
cliPrintf("System Uptime: %d seconds\r\n", millis() / 1000, vbat);
cliPrintf("Voltage: %d * 0.1V (%dS battery - %s)\r\n", vbat, batteryCellCount, getBatteryStateString());
cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
#if (FLASH_SIZE > 64)
const uint32_t detectedSensorsMask = sensorsMask();
for (int i = 0; i < SENSOR_INDEX_COUNT; i++) {
const uint32_t mask = (1 << i);
if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
const int sensorHardwareIndex = detectedSensors[i];
const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
if (mask == SENSOR_ACC && acc.dev.revisionCode) {
cliPrintf(".%c", acc.dev.revisionCode);
}
}
}
cliPrint("\r\n");
cliPrintf("Sensor status: GYRO=%s, ACC=%s, MAG=%s, BARO=%s, SONAR=%s, GPS=%s\r\n",
hardwareSensorStatusNames[getHwGyroStatus()],
hardwareSensorStatusNames[getHwAccelerometerStatus()],
hardwareSensorStatusNames[getHwCompassStatus()],
hardwareSensorStatusNames[getHwBarometerStatus()],
hardwareSensorStatusNames[getHwRangefinderStatus()],
hardwareSensorStatusNames[getHwGPSStatus()]
);
#endif
#ifdef USE_SDCARD
cliSdInfo(NULL);
#endif
#ifdef USE_I2C
const uint16_t i2cErrorCounter = i2cGetErrorCounter();
#else
const uint16_t i2cErrorCounter = 0;
#endif
#ifdef STACK_CHECK
cliPrintf("Stack used: %d, ", stackUsedSize());
#endif
cliPrintf("Stack size: %d, Stack address: 0x%x\r\n", stackTotalSize(), stackHighMem());
cliPrintf("I2C Errors: %d, config size: %d, max available config: %d\r\n", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
cliPrintf("System load: %d", averageSystemLoadPercent);
#ifdef ASYNC_GYRO_PROCESSING
const timeDelta_t pidTaskDeltaTime = getTaskDeltaTime(TASK_PID);
#else
const timeDelta_t pidTaskDeltaTime = getTaskDeltaTime(TASK_GYROPID);
#endif
const int pidRate = pidTaskDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)pidTaskDeltaTime));
const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
cliPrintf(", cycle time: %d, PID rate: %d, RX rate: %d, System rate: %d\r\n", (uint16_t)cycleTime, pidRate, rxRate, systemRate);
}
#ifndef SKIP_TASK_STATISTICS
static void cliTasks(char *cmdline)
{
UNUSED(cmdline);
int maxLoadSum = 0;
int averageLoadSum = 0;
cfCheckFuncInfo_t checkFuncInfo;
cliPrintf("Task list rate/hz max/us avg/us maxload avgload total/ms\r\n");
for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
cfTaskInfo_t taskInfo;
getTaskInfo(taskId, &taskInfo);
if (taskInfo.isEnabled) {
const int taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
const int maxLoad = (taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
const int averageLoad = (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
if (taskId != TASK_SERIAL) {
maxLoadSum += maxLoad;
averageLoadSum += averageLoad;
}
cliPrintf("%2d - %12s %6d %5d %5d %4d.%1d%% %4d.%1d%% %8d\r\n",
taskId, taskInfo.taskName, taskFrequency, (uint32_t)taskInfo.maxExecutionTime, (uint32_t)taskInfo.averageExecutionTime,
maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, (uint32_t)taskInfo.totalExecutionTime / 1000);
}
}
getCheckFuncInfo(&checkFuncInfo);
cliPrintf("Task check function %13d %7d %25d\r\n", (uint32_t)checkFuncInfo.maxExecutionTime, (uint32_t)checkFuncInfo.averageExecutionTime, (uint32_t)checkFuncInfo.totalExecutionTime / 1000);
cliPrintf("Total (excluding SERIAL) %21d.%1d%% %4d.%1d%%\r\n", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
}
#endif
static void cliVersion(char *cmdline)
{
UNUSED(cmdline);
cliPrintf("# %s/%s %s %s / %s (%s)\r\n",
FC_FIRMWARE_NAME,
targetName,
FC_VERSION_STRING,
buildDate,
buildTime,
shortGitRevision
);
}
#if !defined(SKIP_TASK_STATISTICS) && !defined(SKIP_CLI_RESOURCES)
static void cliResource(char *cmdline)
{
UNUSED(cmdline);
cliPrintf("IO:\r\n----------------------\r\n");
for (unsigned i = 0; i < DEFIO_IO_USED_COUNT; i++) {
const char* owner;
owner = ownerNames[ioRecs[i].owner];
const char* resource;
resource = resourceNames[ioRecs[i].resource];
if (ioRecs[i].index > 0) {
cliPrintf("%c%02d: %s%d %s\r\n", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, ioRecs[i].index, resource);
} else {
cliPrintf("%c%02d: %s %s\r\n", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner, resource);
}
}
}
#endif
static void backupConfigs(void)
{
// make copies of configs to do differencing
PG_FOREACH(reg) {
// currentConfig is the copy
const cliCurrentAndDefaultConfig_t *cliCurrentAndDefaultConfig = getCurrentAndDefaultConfigs(pgN(reg));
if (cliCurrentAndDefaultConfig->currentConfig) {
if (pgIsProfile(reg)) {
//memcpy((uint8_t *)cliCurrentAndDefaultConfig->currentConfig, reg->address, reg->size * MAX_PROFILE_COUNT);
} else {
memcpy((uint8_t *)cliCurrentAndDefaultConfig->currentConfig, reg->address, reg->size);
}
#ifdef SERIAL_CLI_DEBUG
} else {
cliPrintf("BACKUP %d SET UP INCORRECTLY\r\n", pgN(reg));
#endif
}
}
const pgRegistry_t* reg = pgFind(PG_PID_PROFILE);
memcpy(&pidProfileCopy[0], reg->address, sizeof(pidProfile_t) * MAX_PROFILE_COUNT);
}
static void restoreConfigs(void)
{
PG_FOREACH(reg) {
// currentConfig is the copy
const cliCurrentAndDefaultConfig_t *cliCurrentAndDefaultConfig = getCurrentAndDefaultConfigs(pgN(reg));
if (cliCurrentAndDefaultConfig->currentConfig) {
if (pgIsProfile(reg)) {
//memcpy(reg->address, (uint8_t *)cliCurrentAndDefaultConfig->currentConfig, reg->size * MAX_PROFILE_COUNT);
} else {
memcpy(reg->address, (uint8_t *)cliCurrentAndDefaultConfig->currentConfig, reg->size);
}
#ifdef SERIAL_CLI_DEBUG
} else {
cliPrintf("RESTORE %d SET UP INCORRECTLY\r\n", pgN(reg));
#endif
}
}
const pgRegistry_t* reg = pgFind(PG_PID_PROFILE);
memcpy(reg->address, &pidProfileCopy[0], sizeof(pidProfile_t) * MAX_PROFILE_COUNT);
}
static void printConfig(const char *cmdline, bool doDiff)
{
uint8_t dumpMask = DUMP_MASTER;
const char *options;
if ((options = checkCommand(cmdline, "master"))) {
dumpMask = DUMP_MASTER; // only
} else if ((options = checkCommand(cmdline, "profile"))) {
dumpMask = DUMP_PROFILE; // only
} else if ((options = checkCommand(cmdline, "all"))) {
dumpMask = DUMP_ALL; // all profiles and rates
} else {
options = cmdline;
}
if (doDiff) {
dumpMask = dumpMask | DO_DIFF;
}
const int currentProfileIndexSave = getConfigProfile();
backupConfigs();
// reset all configs to defaults to do differencing
resetConfigs();
// restore the profile indices, since they should not be reset for proper comparison
setConfigProfile(currentProfileIndexSave);
if (checkCommand(options, "showdefaults")) {
dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
}
if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
cliPrintHashLine("version");
cliVersion(NULL);
if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
#ifndef CLI_MINIMAL_VERBOSITY
cliPrintHashLine("reset configuration to default settings\r\ndefaults noreboot");
#else
cliPrintf("defaults noreboot\r\n");
#endif
}
cliPrintHashLine("resources");
//printResource(dumpMask, &defaultConfig);
#ifndef USE_QUAD_MIXER_ONLY
cliPrintHashLine("mixer");
const bool equalsDefault = mixerConfigCopy.mixerMode == mixerConfig()->mixerMode;
const char *formatMixer = "mixer %s\r\n";
cliDefaultPrintf(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
cliDumpPrintf(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfigCopy.mixerMode - 1]);
cliDumpPrintf(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n\r\n");
printMotorMix(dumpMask, customMotorMixerCopy, customMotorMixer(0));
#ifdef USE_SERVOS
cliPrintHashLine("servo");
printServo(dumpMask, servoParamsCopy, servoParams(0));
cliPrintHashLine("servo mix");
// print custom servo mixer if exists
cliDumpPrintf(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n\r\n");
printServoMix(dumpMask, customServoMixersCopy, customServoMixers(0));
#endif
#endif
cliPrintHashLine("feature");
printFeature(dumpMask, &featureConfigCopy, featureConfig());
#ifdef BEEPER
cliPrintHashLine("beeper");
printBeeper(dumpMask, &beeperConfigCopy, beeperConfig());
#endif
cliPrintHashLine("map");
printMap(dumpMask, &rxConfigCopy, rxConfig());
cliPrintHashLine("serial");
printSerial(dumpMask, &serialConfigCopy, serialConfig());
#ifdef LED_STRIP
cliPrintHashLine("led");
printLed(dumpMask, ledStripConfigCopy.ledConfigs, ledStripConfig()->ledConfigs);
cliPrintHashLine("color");
printColor(dumpMask, ledStripConfigCopy.colors, ledStripConfig()->colors);
cliPrintHashLine("mode_color");
printModeColor(dumpMask, &ledStripConfigCopy, ledStripConfig());
#endif
cliPrintHashLine("aux");
printAux(dumpMask, modeActivationConditionsCopy, modeActivationConditions(0));
cliPrintHashLine("adjrange");
printAdjustmentRange(dumpMask, adjustmentRangesCopy, adjustmentRanges(0));
cliPrintHashLine("rxrange");
printRxRange(dumpMask, rxChannelRangeConfigsCopy, rxChannelRangeConfigs(0));
cliPrintHashLine("master");
dumpAllValues(MASTER_VALUE, dumpMask);
if (dumpMask & DUMP_ALL) {
// dump all profiles
const int currentProfileIndexSave = getConfigProfile();
for (int ii = 0; ii < MAX_PROFILE_COUNT; ++ii) {
cliDumpProfile(ii, dumpMask);
}
setConfigProfile(currentProfileIndexSave);
cliPrintHashLine("restore original profile selection");
cliPrintf("profile %d\r\n", currentProfileIndexSave + 1);
cliPrintHashLine("save configuration\r\nsave");
} else {
// dump just the current profile
cliDumpProfile(getConfigProfile(), dumpMask);
}
}
if (dumpMask & DUMP_PROFILE) {
cliDumpProfile(getConfigProfile(), dumpMask);
}
// restore configs from copies
restoreConfigs();
}
static void cliDump(char *cmdline)
{
printConfig(cmdline, false);
}
static void cliDiff(char *cmdline)
{
printConfig(cmdline, true);
}
typedef struct {
const char *name;
#ifndef SKIP_CLI_COMMAND_HELP
const char *description;
const char *args;
#endif
void (*func)(char *cmdline);
} clicmd_t;
#ifndef SKIP_CLI_COMMAND_HELP
#define CLI_COMMAND_DEF(name, description, args, method) \
{ \
name , \
description , \
args , \
method \
}
#else
#define CLI_COMMAND_DEF(name, description, args, method) \
{ \
name, \
method \
}
#endif
static void cliHelp(char *cmdline);
// should be sorted a..z for bsearch()
const clicmd_t cmdTable[] = {
CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
#if defined(USE_ASSERT)
CLI_COMMAND_DEF("assert", "", NULL, cliAssert),
#endif
CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
#ifdef BEEPER
CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
"\t<+|->[name]", cliBeeper),
#endif
#if defined(BOOTLOG)
CLI_COMMAND_DEF("bootlog", "show boot events", NULL, cliBootlog),
#endif
#ifdef LED_STRIP
CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
#endif
CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
CLI_COMMAND_DEF("dfu", "DFU mode on reboot", NULL, cliDfu),
CLI_COMMAND_DEF("diff", "list configuration changes from default",
"[master|profile|rates|all] {showdefaults}", cliDiff),
CLI_COMMAND_DEF("dump", "dump configuration",
"[master|profile|rates|all] {showdefaults}", cliDump),
CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
CLI_COMMAND_DEF("feature", "configure features",
"list\r\n"
"\t<+|->[name]", cliFeature),
#ifdef USE_FLASHFS
CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
#ifdef USE_FLASH_TOOLS
CLI_COMMAND_DEF("flash_read", NULL, " ", cliFlashRead),
CLI_COMMAND_DEF("flash_write", NULL, " ", cliFlashWrite),
#endif
#endif
CLI_COMMAND_DEF("get", "get variable value",
"[name]", cliGet),
#ifdef GPS
CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
#endif
CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
#ifdef LED_STRIP
CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
#endif
CLI_COMMAND_DEF("map", "configure rc channel order",
"[