/* * 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" #include "version.h" #include "build_config.h" #include "common/axis.h" #include "common/maths.h" #include "common/color.h" #include "common/typeconversion.h" #include "drivers/system.h" #include "drivers/sensor.h" #include "drivers/accgyro.h" #include "drivers/compass.h" #include "drivers/serial.h" #include "drivers/bus_i2c.h" #include "drivers/gpio.h" #include "drivers/timer.h" #include "drivers/pwm_rx.h" #include "flight/flight.h" #include "flight/mixer.h" #include "flight/navigation.h" #include "flight/failsafe.h" #include "rx/rx.h" #include "io/escservo.h" #include "io/gps.h" #include "io/gimbal.h" #include "io/rc_controls.h" #include "io/serial.h" #include "io/ledstrip.h" #include "io/flashfs.h" #include "rx/spektrum.h" #include "sensors/battery.h" #include "sensors/boardalignment.h" #include "sensors/sensors.h" #include "sensors/acceleration.h" #include "sensors/gyro.h" #include "sensors/compass.h" #include "sensors/barometer.h" #include "telemetry/telemetry.h" #include "config/runtime_config.h" #include "config/config.h" #include "config/config_profile.h" #include "config/config_master.h" #include "common/printf.h" #include "serial_cli.h" extern uint16_t cycleTime; // FIXME dependency on mw.c static serialPort_t *cliPort; static void cliAux(char *cmdline); static void cliAdjustmentRange(char *cmdline); static void cliCMix(char *cmdline); static void cliDefaults(char *cmdline); static void cliDump(char *cmdLine); static void cliExit(char *cmdline); static void cliFeature(char *cmdline); static void cliMotor(char *cmdline); static void cliProfile(char *cmdline); static void cliRateProfile(char *cmdline); static void cliReboot(void); static void cliSave(char *cmdline); static void cliSet(char *cmdline); static void cliGet(char *cmdline); static void cliStatus(char *cmdline); static void cliVersion(char *cmdline); #ifdef GPS static void cliGpsPassthrough(char *cmdline); #endif static void cliHelp(char *cmdline); static void cliMap(char *cmdline); #ifdef LED_STRIP static void cliLed(char *cmdline); static void cliColor(char *cmdline); #endif #ifndef USE_QUAD_MIXER_ONLY static void cliMixer(char *cmdline); #endif #ifdef FLASHFS static void cliFlashIdent(char *cmdline); static void cliFlashErase(char *cmdline); static void cliFlashWrite(char *cmdline); static void cliFlashRead(char *cmdline); #endif // signal that we're in cli mode uint8_t cliMode = 0; // buffer static char cliBuffer[48]; 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", NULL }; #endif // sync this with features_e static const char * const featureNames[] = { "RX_PPM", "VBAT", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP", "SERVO_TILT", "SOFTSERIAL", "GPS", "FAILSAFE", "SONAR", "TELEMETRY", "CURRENT_METER", "3D", "RX_PARALLEL_PWM", "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "ONESHOT125", "BLACKBOX", NULL }; // sync this with sensors_e static const char * const sensorNames[] = { "GYRO", "ACC", "BARO", "MAG", "SONAR", "GPS", "GPS+MAG", NULL }; static const char * const accNames[] = { "", "ADXL345", "MPU6050", "MMA845x", "BMA280", "LSM303DLHC", "MPU6000", "MPU6500", "FAKE", "None", NULL }; typedef struct { const char *name; const char *param; void (*func)(char *cmdline); } clicmd_t; // should be sorted a..z for bsearch() const clicmd_t cmdTable[] = { { "adjrange", "show/set adjustment ranges settings", cliAdjustmentRange }, { "aux", "show/set aux settings", cliAux }, { "cmix", "design custom mixer", cliCMix }, #ifdef LED_STRIP { "color", "configure colors", cliColor }, #endif { "defaults", "reset to defaults and reboot", cliDefaults }, { "dump", "print configurable settings in a pastable form", cliDump }, { "exit", "", cliExit }, { "feature", "list or -val or val", cliFeature }, #ifdef FLASHFS { "flash_erase", "erase flash chip", cliFlashErase }, { "flash_ident", "get flash chip details", cliFlashIdent }, { "flash_read", "read text from the given address", cliFlashRead }, { "flash_write", "write text to the given address", cliFlashWrite }, #endif { "get", "get variable value", cliGet }, #ifdef GPS { "gpspassthrough", "passthrough gps to serial", cliGpsPassthrough }, #endif { "help", "", cliHelp }, #ifdef LED_STRIP { "led", "configure leds", cliLed }, #endif { "map", "mapping of rc channel order", cliMap }, #ifndef USE_QUAD_MIXER_ONLY { "mixer", "mixer name or list", cliMixer }, #endif { "motor", "get/set motor output value", cliMotor }, { "profile", "index (0 to 2)", cliProfile }, { "rateprofile", "index (0 to 2)", cliRateProfile }, { "save", "save and reboot", cliSave }, { "set", "name=value or blank or * for list", cliSet }, { "status", "show system status", cliStatus }, { "version", "", cliVersion }, }; #define CMD_COUNT (sizeof(cmdTable) / sizeof(clicmd_t)) typedef enum { VAR_UINT8 = (1 << 0), VAR_INT8 = (1 << 1), VAR_UINT16 = (1 << 2), VAR_INT16 = (1 << 3), VAR_UINT32 = (1 << 4), VAR_FLOAT = (1 << 5), MASTER_VALUE = (1 << 6), PROFILE_VALUE = (1 << 7), CONTROL_RATE_VALUE = (1 << 8) } cliValueFlag_e; #define VALUE_TYPE_MASK (VAR_UINT8 | VAR_INT8 | VAR_UINT16 | VAR_INT16 | VAR_UINT32 | VAR_FLOAT) #define SECTION_MASK (MASTER_VALUE | PROFILE_VALUE | CONTROL_RATE_VALUE) typedef struct { const char *name; const uint16_t type; // cliValueFlag_e - specify one of each from VALUE_TYPE_MASK and SECTION_MASK void *ptr; const int32_t min; const int32_t max; } clivalue_t; const clivalue_t valueTable[] = { { "looptime", VAR_UINT16 | MASTER_VALUE, &masterConfig.looptime, 0, 9000 }, { "emf_avoidance", VAR_UINT8 | MASTER_VALUE, &masterConfig.emf_avoidance, 0, 1 }, { "mid_rc", VAR_UINT16 | MASTER_VALUE, &masterConfig.rxConfig.midrc, 1200, 1700 }, { "min_check", VAR_UINT16 | MASTER_VALUE, &masterConfig.rxConfig.mincheck, PWM_RANGE_ZERO, PWM_RANGE_MAX }, { "max_check", VAR_UINT16 | MASTER_VALUE, &masterConfig.rxConfig.maxcheck, PWM_RANGE_ZERO, PWM_RANGE_MAX }, { "rssi_channel", VAR_INT8 | MASTER_VALUE, &masterConfig.rxConfig.rssi_channel, 0, MAX_SUPPORTED_RC_CHANNEL_COUNT }, { "rssi_scale", VAR_UINT8 | MASTER_VALUE, &masterConfig.rxConfig.rssi_scale, RSSI_SCALE_MIN, RSSI_SCALE_MAX }, { "input_filtering_mode", VAR_INT8 | MASTER_VALUE, &masterConfig.inputFilteringMode, 0, 1 }, { "min_throttle", VAR_UINT16 | MASTER_VALUE, &masterConfig.escAndServoConfig.minthrottle, PWM_RANGE_ZERO, PWM_RANGE_MAX }, { "max_throttle", VAR_UINT16 | MASTER_VALUE, &masterConfig.escAndServoConfig.maxthrottle, PWM_RANGE_ZERO, PWM_RANGE_MAX }, { "min_command", VAR_UINT16 | MASTER_VALUE, &masterConfig.escAndServoConfig.mincommand, PWM_RANGE_ZERO, PWM_RANGE_MAX }, { "servo_center_pulse", VAR_UINT16 | MASTER_VALUE, &masterConfig.escAndServoConfig.servoCenterPulse, PWM_RANGE_ZERO, PWM_RANGE_MAX }, { "3d_deadband_low", VAR_UINT16 | MASTER_VALUE, &masterConfig.flight3DConfig.deadband3d_low, PWM_RANGE_ZERO, PWM_RANGE_MAX }, // FIXME upper limit should match code in the mixer, 1500 currently { "3d_deadband_high", VAR_UINT16 | MASTER_VALUE, &masterConfig.flight3DConfig.deadband3d_high, PWM_RANGE_ZERO, PWM_RANGE_MAX }, // FIXME lower limit should match code in the mixer, 1500 currently, { "3d_neutral", VAR_UINT16 | MASTER_VALUE, &masterConfig.flight3DConfig.neutral3d, PWM_RANGE_ZERO, PWM_RANGE_MAX }, { "3d_deadband_throttle", VAR_UINT16 | MASTER_VALUE, &masterConfig.flight3DConfig.deadband3d_throttle, PWM_RANGE_ZERO, PWM_RANGE_MAX }, { "motor_pwm_rate", VAR_UINT16 | MASTER_VALUE, &masterConfig.motor_pwm_rate, 50, 32000 }, { "servo_pwm_rate", VAR_UINT16 | MASTER_VALUE, &masterConfig.servo_pwm_rate, 50, 498 }, { "retarded_arm", VAR_UINT8 | MASTER_VALUE, &masterConfig.retarded_arm, 0, 1 }, { "disarm_kill_switch", VAR_UINT8 | MASTER_VALUE, &masterConfig.disarm_kill_switch, 0, 1 }, { "auto_disarm_delay", VAR_UINT8 | MASTER_VALUE, &masterConfig.auto_disarm_delay, 0, 60 }, { "small_angle", VAR_UINT8 | MASTER_VALUE, &masterConfig.small_angle, 0, 180 }, { "flaps_speed", VAR_UINT8 | MASTER_VALUE, &masterConfig.airplaneConfig.flaps_speed, 0, 100 }, { "fixedwing_althold_dir", VAR_INT8 | MASTER_VALUE, &masterConfig.airplaneConfig.fixedwing_althold_dir, -1, 1 }, { "serial_port_1_scenario", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.serial_port_scenario[0], 0, SERIAL_PORT_SCENARIO_MAX }, { "serial_port_2_scenario", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.serial_port_scenario[1], 0, SERIAL_PORT_SCENARIO_MAX }, #if (SERIAL_PORT_COUNT > 2) { "serial_port_3_scenario", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.serial_port_scenario[2], 0, SERIAL_PORT_SCENARIO_MAX }, #if !defined(CC3D) { "serial_port_4_scenario", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.serial_port_scenario[3], 0, SERIAL_PORT_SCENARIO_MAX }, #if (SERIAL_PORT_COUNT > 4) { "serial_port_5_scenario", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.serial_port_scenario[4], 0, SERIAL_PORT_SCENARIO_MAX }, #endif #endif #endif { "reboot_character", VAR_UINT8 | MASTER_VALUE, &masterConfig.serialConfig.reboot_character, 48, 126 }, { "msp_baudrate", VAR_UINT32 | MASTER_VALUE, &masterConfig.serialConfig.msp_baudrate, 1200, 115200 }, { "cli_baudrate", VAR_UINT32 | MASTER_VALUE, &masterConfig.serialConfig.cli_baudrate, 1200, 115200 }, #ifdef GPS { "gps_baudrate", VAR_UINT32 | MASTER_VALUE, &masterConfig.serialConfig.gps_baudrate, 0, 115200 }, { "gps_passthrough_baudrate", VAR_UINT32 | MASTER_VALUE, &masterConfig.serialConfig.gps_passthrough_baudrate, 1200, 115200 }, { "gps_provider", VAR_UINT8 | MASTER_VALUE, &masterConfig.gpsConfig.provider, 0, GPS_PROVIDER_MAX }, { "gps_sbas_mode", VAR_UINT8 | MASTER_VALUE, &masterConfig.gpsConfig.sbasMode, 0, SBAS_MODE_MAX }, { "gps_auto_config", VAR_UINT8 | MASTER_VALUE, &masterConfig.gpsConfig.autoConfig, GPS_AUTOCONFIG_OFF, GPS_AUTOCONFIG_ON }, { "gps_auto_baud", VAR_UINT8 | MASTER_VALUE, &masterConfig.gpsConfig.autoBaud, GPS_AUTOBAUD_OFF, GPS_AUTOBAUD_ON }, { "gps_pos_p", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDPOS], 0, 200 }, { "gps_pos_i", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDPOS], 0, 200 }, { "gps_pos_d", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDPOS], 0, 200 }, { "gps_posr_p", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDPOSR], 0, 200 }, { "gps_posr_i", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDPOSR], 0, 200 }, { "gps_posr_d", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDPOSR], 0, 200 }, { "gps_nav_p", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDNAVR], 0, 200 }, { "gps_nav_i", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDNAVR], 0, 200 }, { "gps_nav_d", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDNAVR], 0, 200 }, { "gps_wp_radius", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.gps_wp_radius, 0, 2000 }, { "nav_controls_heading", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.nav_controls_heading, 0, 1 }, { "nav_speed_min", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.nav_speed_min, 10, 2000 }, { "nav_speed_max", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.nav_speed_max, 10, 2000 }, { "nav_slew_rate", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].gpsProfile.nav_slew_rate, 0, 100 }, #endif { "serialrx_provider", VAR_UINT8 | MASTER_VALUE, &masterConfig.rxConfig.serialrx_provider, 0, SERIALRX_PROVIDER_MAX }, { "spektrum_sat_bind", VAR_UINT8 | MASTER_VALUE, &masterConfig.rxConfig.spektrum_sat_bind, SPEKTRUM_SAT_BIND_DISABLED, SPEKTRUM_SAT_BIND_MAX}, { "telemetry_provider", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetryConfig.telemetry_provider, 0, TELEMETRY_PROVIDER_MAX }, { "telemetry_switch", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetryConfig.telemetry_switch, 0, 1 }, { "telemetry_inversion", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetryConfig.telemetry_inversion, 0, 1 }, { "frsky_default_lattitude", VAR_FLOAT | MASTER_VALUE, &masterConfig.telemetryConfig.gpsNoFixLatitude, -90.0, 90.0 }, { "frsky_default_longitude", VAR_FLOAT | MASTER_VALUE, &masterConfig.telemetryConfig.gpsNoFixLongitude, -180.0, 180.0 }, { "frsky_coordinates_format", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetryConfig.frsky_coordinate_format, 0, FRSKY_FORMAT_NMEA }, { "frsky_unit", VAR_UINT8 | MASTER_VALUE, &masterConfig.telemetryConfig.frsky_unit, 0, FRSKY_UNIT_IMPERIALS }, { "battery_capacity", VAR_UINT16 | MASTER_VALUE, &masterConfig.batteryConfig.batteryCapacity, 0, 20000 }, { "vbat_scale", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.vbatscale, VBAT_SCALE_MIN, VBAT_SCALE_MAX }, { "vbat_max_cell_voltage", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.vbatmaxcellvoltage, 10, 50 }, { "vbat_min_cell_voltage", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.vbatmincellvoltage, 10, 50 }, { "vbat_warning_cell_voltage", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.vbatwarningcellvoltage, 10, 50 }, { "current_meter_scale", VAR_INT16 | MASTER_VALUE, &masterConfig.batteryConfig.currentMeterScale, -10000, 10000 }, { "current_meter_offset", VAR_UINT16 | MASTER_VALUE, &masterConfig.batteryConfig.currentMeterOffset, 0, 3300 }, { "multiwii_current_meter_output", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.multiwiiCurrentMeterOutput, 0, 1 }, { "current_meter_type", VAR_UINT8 | MASTER_VALUE, &masterConfig.batteryConfig.currentMeterType, 0, CURRENT_SENSOR_MAX }, { "align_gyro", VAR_UINT8 | MASTER_VALUE, &masterConfig.sensorAlignmentConfig.gyro_align, 0, 8 }, { "align_acc", VAR_UINT8 | MASTER_VALUE, &masterConfig.sensorAlignmentConfig.acc_align, 0, 8 }, { "align_mag", VAR_UINT8 | MASTER_VALUE, &masterConfig.sensorAlignmentConfig.mag_align, 0, 8 }, { "align_board_roll", VAR_INT16 | MASTER_VALUE, &masterConfig.boardAlignment.rollDegrees, -180, 360 }, { "align_board_pitch", VAR_INT16 | MASTER_VALUE, &masterConfig.boardAlignment.pitchDegrees, -180, 360 }, { "align_board_yaw", VAR_INT16 | MASTER_VALUE, &masterConfig.boardAlignment.yawDegrees, -180, 360 }, { "max_angle_inclination", VAR_UINT16 | MASTER_VALUE, &masterConfig.max_angle_inclination, 100, 900 }, { "gyro_lpf", VAR_UINT16 | MASTER_VALUE, &masterConfig.gyro_lpf, 0, 256 }, { "moron_threshold", VAR_UINT8 | MASTER_VALUE, &masterConfig.gyroConfig.gyroMovementCalibrationThreshold, 0, 128 }, { "gyro_cmpf_factor", VAR_UINT16 | MASTER_VALUE, &masterConfig.gyro_cmpf_factor, 100, 1000 }, { "gyro_cmpfm_factor", VAR_UINT16 | MASTER_VALUE, &masterConfig.gyro_cmpfm_factor, 100, 1000 }, { "alt_hold_deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].rcControlsConfig.alt_hold_deadband, 1, 250 }, { "alt_hold_fast_change", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].rcControlsConfig.alt_hold_fast_change, 0, 1 }, { "deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].rcControlsConfig.deadband, 0, 32 }, { "yaw_deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].rcControlsConfig.yaw_deadband, 0, 100 }, { "throttle_correction_value", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].throttle_correction_value, 0, 150 }, { "throttle_correction_angle", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].throttle_correction_angle, 1, 900 }, { "yaw_control_direction", VAR_INT8 | MASTER_VALUE, &masterConfig.yaw_control_direction, -1, 1 }, { "yaw_direction", VAR_INT8 | PROFILE_VALUE, &masterConfig.profile[0].mixerConfig.yaw_direction, -1, 1 }, { "tri_unarmed_servo", VAR_INT8 | PROFILE_VALUE, &masterConfig.profile[0].mixerConfig.tri_unarmed_servo, 0, 1 }, { "default_rate_profile", VAR_UINT8 | PROFILE_VALUE , &masterConfig.profile[0].defaultRateProfileIndex, 0, MAX_CONTROL_RATE_PROFILE_COUNT - 1 }, { "rc_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcRate8, 0, 250 }, { "rc_expo", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rcExpo8, 0, 100 }, { "thr_mid", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].thrMid8, 0, 100 }, { "thr_expo", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].thrExpo8, 0, 100 }, { "roll_pitch_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].rollPitchRate, 0, 100 }, { "yaw_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].yawRate, 0, 100 }, { "tpa_rate", VAR_UINT8 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].dynThrPID, 0, 100}, { "tpa_breakpoint", VAR_UINT16 | CONTROL_RATE_VALUE, &masterConfig.controlRateProfiles[0].tpa_breakpoint, PWM_RANGE_MIN, PWM_RANGE_MAX}, { "failsafe_delay", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_delay, 0, 200 }, { "failsafe_off_delay", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_off_delay, 0, 200 }, { "failsafe_throttle", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_throttle, PWM_RANGE_MIN, PWM_RANGE_MAX }, { "failsafe_min_usec", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_min_usec, 100, PWM_RANGE_MAX }, { "failsafe_max_usec", VAR_UINT16 | PROFILE_VALUE, &masterConfig.profile[0].failsafeConfig.failsafe_max_usec, 100, PWM_RANGE_MAX + (PWM_RANGE_MAX - PWM_RANGE_MIN) }, { "gimbal_flags", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].gimbalConfig.gimbal_flags, 0, 255}, { "acc_hardware", VAR_UINT8 | MASTER_VALUE, &masterConfig.acc_hardware, 0, ACC_NONE }, { "acc_lpf_factor", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].acc_lpf_factor, 0, 250 }, { "accxy_deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].accDeadband.xy, 0, 100 }, { "accz_deadband", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].accDeadband.z, 0, 100 }, { "accz_lpf_cutoff", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].accz_lpf_cutoff, 1, 20 }, { "acc_unarmedcal", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].acc_unarmedcal, 0, 1 }, { "acc_trim_pitch", VAR_INT16 | PROFILE_VALUE, &masterConfig.profile[0].accelerometerTrims.values.pitch, -300, 300 }, { "acc_trim_roll", VAR_INT16 | PROFILE_VALUE, &masterConfig.profile[0].accelerometerTrims.values.roll, -300, 300 }, { "baro_tab_size", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].barometerConfig.baro_sample_count, 0, BARO_SAMPLE_COUNT_MAX }, { "baro_noise_lpf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].barometerConfig.baro_noise_lpf, 0, 1 }, { "baro_cf_vel", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].barometerConfig.baro_cf_vel, 0, 1 }, { "baro_cf_alt", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].barometerConfig.baro_cf_alt, 0, 1 }, { "mag_hardware", VAR_UINT8 | MASTER_VALUE, &masterConfig.mag_hardware, 0, MAG_NONE }, { "mag_declination", VAR_INT16 | PROFILE_VALUE, &masterConfig.profile[0].mag_declination, -18000, 18000 }, { "pid_controller", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidController, 0, 5 }, { "p_pitch", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PITCH], 0, 200 }, { "i_pitch", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PITCH], 0, 200 }, { "d_pitch", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PITCH], 0, 200 }, { "p_roll", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[ROLL], 0, 200 }, { "i_roll", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[ROLL], 0, 200 }, { "d_roll", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[ROLL], 0, 200 }, { "p_yaw", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[YAW], 0, 200 }, { "i_yaw", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[YAW], 0, 200 }, { "d_yaw", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[YAW], 0, 200 }, { "p_pitchf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P_f[PITCH], 0, 100 }, { "i_pitchf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I_f[PITCH], 0, 100 }, { "d_pitchf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D_f[PITCH], 0, 100 }, { "p_rollf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P_f[ROLL], 0, 100 }, { "i_rollf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I_f[ROLL], 0, 100 }, { "d_rollf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D_f[ROLL], 0, 100 }, { "p_yawf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P_f[YAW], 0, 100 }, { "i_yawf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I_f[YAW], 0, 100 }, { "d_yawf", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D_f[YAW], 0, 100 }, { "level_horizon", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.H_level, 0, 10 }, { "level_angle", VAR_FLOAT | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.A_level, 0, 10 }, { "sensitivity_horizon", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.H_sensitivity, 0, 250 }, { "p_alt", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDALT], 0, 200 }, { "i_alt", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDALT], 0, 200 }, { "d_alt", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDALT], 0, 200 }, { "p_level", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDLEVEL], 0, 200 }, { "i_level", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDLEVEL], 0, 200 }, { "d_level", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDLEVEL], 0, 200 }, { "p_vel", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.P8[PIDVEL], 0, 200 }, { "i_vel", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.I8[PIDVEL], 0, 200 }, { "d_vel", VAR_UINT8 | PROFILE_VALUE, &masterConfig.profile[0].pidProfile.D8[PIDVEL], 0, 200 }, { "blackbox_device", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_device, 0, 1 }, { "blackbox_rate_num", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_rate_num, 1, 32 }, { "blackbox_rate_denom", VAR_UINT8 | MASTER_VALUE, &masterConfig.blackbox_rate_denom, 1, 32 }, }; #define VALUE_COUNT (sizeof(valueTable) / sizeof(clivalue_t)) 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); static void cliPrintVar(const clivalue_t *var, uint32_t full); static void cliPrint(const char *str); static void cliWrite(uint8_t ch); static void cliPrompt(void) { cliPrint("\r\n# "); } static int cliCompare(const void *a, const void *b) { const clicmd_t *ca = a, *cb = b; return strncasecmp(ca->name, cb->name, strlen(cb->name)); } static char *processChannelRangeArgs(char *ptr, channelRange_t *range, uint8_t *validArgumentCount) { int val; ptr = strchr(ptr, ' '); if (ptr) { val = atoi(++ptr); val = CHANNEL_VALUE_TO_STEP(val); if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) { range->startStep = val; (*validArgumentCount)++; } } ptr = strchr(ptr, ' '); if (ptr) { val = atoi(++ptr); val = CHANNEL_VALUE_TO_STEP(val); if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) { range->endStep = val; (*validArgumentCount)++; } } return ptr; } static void cliAux(char *cmdline) { int i, val = 0; uint8_t len; char *ptr; len = strlen(cmdline); if (len == 0) { // print out aux channel settings for (i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) { modeActivationCondition_t *mac = ¤tProfile->modeActivationConditions[i]; printf("aux %u %u %u %u %u\r\n", i, mac->modeId, mac->auxChannelIndex, MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep), MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep) ); } } else { ptr = cmdline; i = atoi(ptr++); if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) { modeActivationCondition_t *mac = ¤tProfile->modeActivationConditions[i]; uint8_t validArgumentCount = 0; ptr = strchr(ptr, ' '); if (ptr) { val = atoi(++ptr); if (val >= 0 && val < CHECKBOX_ITEM_COUNT) { mac->modeId = val; validArgumentCount++; } } ptr = strchr(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 { printf("index: must be < %u\r\n", MAX_MODE_ACTIVATION_CONDITION_COUNT); } } } static void cliAdjustmentRange(char *cmdline) { int i, val = 0; uint8_t len; char *ptr; len = strlen(cmdline); if (len == 0) { // print out adjustment ranges channel settings for (i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) { adjustmentRange_t *ar = ¤tProfile->adjustmentRanges[i]; printf("adjrange %u %u %u %u %u %u %u\r\n", 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 ); } } else { ptr = cmdline; i = atoi(ptr++); if (i < MAX_ADJUSTMENT_RANGE_COUNT) { adjustmentRange_t *ar = ¤tProfile->adjustmentRanges[i]; uint8_t validArgumentCount = 0; ptr = strchr(ptr, ' '); if (ptr) { val = atoi(++ptr); if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) { ar->adjustmentIndex = val; validArgumentCount++; } } ptr = strchr(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 = strchr(ptr, ' '); if (ptr) { val = atoi(++ptr); if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) { ar->adjustmentFunction = val; validArgumentCount++; } } ptr = strchr(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)); } } else { printf("index: must be < %u\r\n", MAX_ADJUSTMENT_RANGE_COUNT); } } } static void cliCMix(char *cmdline) { #ifdef USE_QUAD_MIXER_ONLY UNUSED(cmdline); #else int i, check = 0; int num_motors = 0; uint8_t len; char buf[16]; float mixsum[3]; char *ptr; len = strlen(cmdline); if (len == 0) { cliPrint("Custom mixer: \r\nMotor\tThr\tRoll\tPitch\tYaw\r\n"); for (i = 0; i < MAX_SUPPORTED_MOTORS; i++) { if (masterConfig.customMixer[i].throttle == 0.0f) break; num_motors++; printf("#%d:\t", i + 1); printf("%s\t", ftoa(masterConfig.customMixer[i].throttle, buf)); printf("%s\t", ftoa(masterConfig.customMixer[i].roll, buf)); printf("%s\t", ftoa(masterConfig.customMixer[i].pitch, buf)); printf("%s\r\n", ftoa(masterConfig.customMixer[i].yaw, buf)); } mixsum[0] = mixsum[1] = mixsum[2] = 0.0f; for (i = 0; i < num_motors; i++) { mixsum[0] += masterConfig.customMixer[i].roll; mixsum[1] += masterConfig.customMixer[i].pitch; mixsum[2] += masterConfig.customMixer[i].yaw; } cliPrint("Sanity check:\t"); for (i = 0; i < 3; i++) cliPrint(fabsf(mixsum[i]) > 0.01f ? "NG\t" : "OK\t"); cliPrint("\r\n"); return; } else if (strncasecmp(cmdline, "reset", 5) == 0) { // erase custom mixer for (i = 0; i < MAX_SUPPORTED_MOTORS; i++) masterConfig.customMixer[i].throttle = 0.0f; } else if (strncasecmp(cmdline, "load", 4) == 0) { ptr = strchr(cmdline, ' '); if (ptr) { len = strlen(++ptr); for (i = 0; ; i++) { if (mixerNames[i] == NULL) { cliPrint("Invalid mixer type\r\n"); break; } if (strncasecmp(ptr, mixerNames[i], len) == 0) { mixerLoadMix(i, masterConfig.customMixer); printf("Loaded %s mix\r\n", mixerNames[i]); cliCMix(""); break; } } } } else { ptr = cmdline; i = atoi(ptr); // get motor number if (--i < MAX_SUPPORTED_MOTORS) { ptr = strchr(ptr, ' '); if (ptr) { masterConfig.customMixer[i].throttle = fastA2F(++ptr); check++; } ptr = strchr(ptr, ' '); if (ptr) { masterConfig.customMixer[i].roll = fastA2F(++ptr); check++; } ptr = strchr(ptr, ' '); if (ptr) { masterConfig.customMixer[i].pitch = fastA2F(++ptr); check++; } ptr = strchr(ptr, ' '); if (ptr) { masterConfig.customMixer[i].yaw = fastA2F(++ptr); check++; } if (check != 4) { cliPrint("Wrong number of arguments, needs idx thr roll pitch yaw\r\n"); } else { cliCMix(""); } } else { printf("Motor number must be between 1 and %d\r\n", MAX_SUPPORTED_MOTORS); } } #endif } #ifdef LED_STRIP static void cliLed(char *cmdline) { int i; uint8_t len; char *ptr; char ledConfigBuffer[20]; len = strlen(cmdline); if (len == 0) { for (i = 0; i < MAX_LED_STRIP_LENGTH; i++) { generateLedConfig(i, ledConfigBuffer, sizeof(ledConfigBuffer)); printf("led %u %s\r\n", i, ledConfigBuffer); } } else { ptr = cmdline; i = atoi(ptr); if (i < MAX_LED_STRIP_LENGTH) { ptr = strchr(cmdline, ' '); if (!parseLedStripConfig(i, ++ptr)) { printf("Parse error\r\n", MAX_LED_STRIP_LENGTH); } } else { printf("Invalid led index: must be < %u\r\n", MAX_LED_STRIP_LENGTH); } } } static void cliColor(char *cmdline) { int i; uint8_t len; char *ptr; len = strlen(cmdline); if (len == 0) { for (i = 0; i < CONFIGURABLE_COLOR_COUNT; i++) { printf("color %u %d,%u,%u\r\n", i, masterConfig.colors[i].h, masterConfig.colors[i].s, masterConfig.colors[i].v); } } else { ptr = cmdline; i = atoi(ptr); if (i < CONFIGURABLE_COLOR_COUNT) { ptr = strchr(cmdline, ' '); if (!parseColor(i, ++ptr)) { printf("Parse error\r\n", CONFIGURABLE_COLOR_COUNT); } } else { printf("Invalid color index: must be < %u\r\n", CONFIGURABLE_COLOR_COUNT); } } } #endif #ifdef FLASHFS static void cliFlashIdent(char *cmdline) { const flashGeometry_t *layout = flashfsGetGeometry(); UNUSED(cmdline); printf("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); printf("Erasing, please wait...\r\n"); flashfsEraseCompletely(); printf("Erased flash chip.\r\n"); } static void cliFlashWrite(char *cmdline) { uint32_t address = atoi(cmdline); char *text = strchr(cmdline, ' '); if (!text) { printf("Missing text to write.\r\n"); } else { flashfsSeekAbs(address); flashfsWrite((uint8_t*)text, strlen(text)); flashfsFlushSync(); printf("Wrote %u bytes at %u.\r\n", strlen(text), address); } } static void cliFlashRead(char *cmdline) { uint32_t address = atoi(cmdline); uint32_t length; int i; uint8_t buffer[32]; char *nextArg = strchr(cmdline, ' '); if (!nextArg) { printf("Missing length argument.\r\n"); } else { length = atoi(nextArg); printf("Reading %u bytes at %u:\r\n", length, address); while (length > 0) { int bytesRead; bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer)); for (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; } } printf("\r\n"); } } #endif static void dumpValues(uint16_t mask) { uint32_t i; const clivalue_t *value; for (i = 0; i < VALUE_COUNT; i++) { value = &valueTable[i]; if ((value->type & mask) == 0) { continue; } printf("set %s = ", valueTable[i].name); cliPrintVar(value, 0); cliPrint("\r\n"); } } typedef enum { DUMP_MASTER = (1 << 0), DUMP_PROFILE = (1 << 1), DUMP_CONTROL_RATE_PROFILE = (1 << 2) } dumpFlags_e; #define DUMP_ALL (DUMP_MASTER | DUMP_PROFILE | DUMP_CONTROL_RATE_PROFILE) static const char* const sectionBreak = "\r\n"; #define printSectionBreak() printf((char *)sectionBreak) static void cliDump(char *cmdline) { unsigned int i; char buf[16]; uint32_t mask; #ifndef USE_QUAD_MIXER_ONLY float thr, roll, pitch, yaw; #endif uint8_t dumpMask = DUMP_ALL; if (strcasecmp(cmdline, "master") == 0) { dumpMask = DUMP_MASTER; // only } if (strcasecmp(cmdline, "profile") == 0) { dumpMask = DUMP_PROFILE; // only } if (strcasecmp(cmdline, "rates") == 0) { dumpMask = DUMP_CONTROL_RATE_PROFILE; // only } if (dumpMask & DUMP_MASTER) { printf("\r\n# version\r\n"); cliVersion(NULL); printf("\r\n# dump master\r\n"); printf("\r\n# mixer\r\n"); #ifndef USE_QUAD_MIXER_ONLY printf("mixer %s\r\n", mixerNames[masterConfig.mixerMode - 1]); if (masterConfig.customMixer[0].throttle != 0.0f) { for (i = 0; i < MAX_SUPPORTED_MOTORS; i++) { if (masterConfig.customMixer[i].throttle == 0.0f) break; thr = masterConfig.customMixer[i].throttle; roll = masterConfig.customMixer[i].roll; pitch = masterConfig.customMixer[i].pitch; yaw = masterConfig.customMixer[i].yaw; printf("cmix %d", i + 1); if (thr < 0) printf(" "); printf("%s", ftoa(thr, buf)); if (roll < 0) printf(" "); printf("%s", ftoa(roll, buf)); if (pitch < 0) printf(" "); printf("%s", ftoa(pitch, buf)); if (yaw < 0) printf(" "); printf("%s\r\n", ftoa(yaw, buf)); } printf("cmix %d 0 0 0 0\r\n", i + 1); } #endif printf("\r\n\r\n# feature\r\n"); mask = featureMask(); for (i = 0; ; i++) { // disable all feature first if (featureNames[i] == NULL) break; printf("feature -%s\r\n", featureNames[i]); } for (i = 0; ; i++) { // reenable what we want. if (featureNames[i] == NULL) break; if (mask & (1 << i)) printf("feature %s\r\n", featureNames[i]); } printf("\r\n\r\n# map\r\n"); for (i = 0; i < 8; i++) buf[masterConfig.rxConfig.rcmap[i]] = rcChannelLetters[i]; buf[i] = '\0'; printf("map %s\r\n", buf); #ifdef LED_STRIP printf("\r\n\r\n# led\r\n"); cliLed(""); printf("\r\n\r\n# color\r\n"); cliColor(""); #endif printSectionBreak(); dumpValues(MASTER_VALUE); } if (dumpMask & DUMP_PROFILE) { printf("\r\n# dump profile\r\n"); printf("\r\n# profile\r\n"); cliProfile(""); printf("\r\n# aux\r\n"); cliAux(""); printf("\r\n# adjrange\r\n"); cliAdjustmentRange(""); printSectionBreak(); dumpValues(PROFILE_VALUE); } if (dumpMask & DUMP_CONTROL_RATE_PROFILE) { printf("\r\n# dump rates\r\n"); printf("\r\n# rateprofile\r\n"); cliRateProfile(""); printSectionBreak(); dumpValues(CONTROL_RATE_VALUE); } } static void cliEnter(void) { cliMode = 1; beginSerialPortFunction(cliPort, FUNCTION_CLI); setPrintfSerialPort(cliPort); cliPrint("\r\nEntering CLI Mode, type 'exit' to return, or 'help'\r\n"); cliPrompt(); } static void cliExit(char *cmdline) { UNUSED(cmdline); cliPrint("\r\nLeaving CLI mode, unsaved changes lost.\r\n"); *cliBuffer = '\0'; bufferIndex = 0; cliMode = 0; // incase a motor was left running during motortest, clear it here mixerResetMotors(); cliReboot(); } static void cliFeature(char *cmdline) { uint32_t i; uint32_t len; uint32_t mask; len = strlen(cmdline); mask = featureMask(); if (len == 0) { cliPrint("Enabled features: "); for (i = 0; ; i++) { if (featureNames[i] == NULL) break; if (mask & (1 << i)) printf("%s ", featureNames[i]); } cliPrint("\r\n"); } else if (strncasecmp(cmdline, "list", len) == 0) { cliPrint("Available features: "); for (i = 0; ; i++) { if (featureNames[i] == NULL) break; printf("%s ", featureNames[i]); } cliPrint("\r\n"); return; } else { bool remove = false; if (cmdline[0] == '-') { // remove feature remove = true; cmdline++; // skip over - len--; } for (i = 0; ; i++) { if (featureNames[i] == NULL) { cliPrint("Invalid feature name\r\n"); break; } if (strncasecmp(cmdline, featureNames[i], len) == 0) { mask = 1 << i; #ifndef GPS if (mask & FEATURE_GPS) { cliPrint("GPS unavailable\r\n"); break; } #endif #ifndef SONAR if (mask & FEATURE_SONAR) { cliPrint("SONAR unavailable\r\n"); break; } #endif if (remove) { featureClear(mask); cliPrint("Disabled "); } else { featureSet(mask); cliPrint("Enabled "); } printf("%s\r\n", featureNames[i]); break; } } } } #ifdef GPS static void cliGpsPassthrough(char *cmdline) { UNUSED(cmdline); gpsEnablePassthroughResult_e result = gpsEnablePassthrough(); switch (result) { case GPS_PASSTHROUGH_NO_SERIAL_PORT: cliPrint("Error: Enable and plug in GPS first\r\n"); break; default: break; } } #endif static void cliHelp(char *cmdline) { uint32_t i = 0; UNUSED(cmdline); cliPrint("Available commands:\r\n"); for (i = 0; i < CMD_COUNT; i++) printf("%s\t%s\r\n", cmdTable[i].name, cmdTable[i].param); } static void cliMap(char *cmdline) { uint32_t len; uint32_t i; char out[9]; len = strlen(cmdline); if (len == 8) { // uppercase it for (i = 0; i < 8; i++) cmdline[i] = toupper((unsigned char)cmdline[i]); for (i = 0; i < 8; i++) { if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i])) continue; cliPrint("Must be any order of AETR1234\r\n"); return; } parseRcChannels(cmdline, &masterConfig.rxConfig); } cliPrint("Current assignment: "); for (i = 0; i < 8; i++) out[masterConfig.rxConfig.rcmap[i]] = rcChannelLetters[i]; out[i] = '\0'; printf("%s\r\n", out); } #ifndef USE_QUAD_MIXER_ONLY static void cliMixer(char *cmdline) { int i; int len; len = strlen(cmdline); if (len == 0) { printf("Current mixer: %s\r\n", mixerNames[masterConfig.mixerMode - 1]); return; } else if (strncasecmp(cmdline, "list", len) == 0) { cliPrint("Available mixers: "); for (i = 0; ; i++) { if (mixerNames[i] == NULL) break; printf("%s ", mixerNames[i]); } cliPrint("\r\n"); return; } for (i = 0; ; i++) { if (mixerNames[i] == NULL) { cliPrint("Invalid mixer type\r\n"); break; } if (strncasecmp(cmdline, mixerNames[i], len) == 0) { masterConfig.mixerMode = i + 1; printf("Mixer set to %s\r\n", mixerNames[i]); break; } } } #endif static void cliMotor(char *cmdline) { int motor_index = 0; int motor_value = 0; int len, index = 0; char *pch = NULL; len = strlen(cmdline); if (len == 0) { printf("Usage:\r\nmotor index [value] - show [or set] motor value\r\n"); return; } pch = strtok(cmdline, " "); while (pch != NULL) { switch (index) { case 0: motor_index = atoi(pch); break; case 1: motor_value = atoi(pch); break; } index++; pch = strtok(NULL, " "); } if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) { printf("No such motor, use a number [0, %d]\r\n", MAX_SUPPORTED_MOTORS); return; } if (index < 2) { printf("Motor %d is set at %d\r\n", motor_index, motor_disarmed[motor_index]); return; } if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) { printf("Invalid motor value, 1000..2000\r\n"); return; } printf("Setting motor %d to %d\r\n", motor_index, motor_value); motor_disarmed[motor_index] = motor_value; } static void cliProfile(char *cmdline) { uint8_t len; int i; len = strlen(cmdline); if (len == 0) { printf("profile %d\r\n", getCurrentProfile()); return; } else { i = atoi(cmdline); if (i >= 0 && i < MAX_PROFILE_COUNT) { masterConfig.current_profile_index = i; writeEEPROM(); readEEPROM(); cliProfile(""); } } } static void cliRateProfile(char *cmdline) { uint8_t len; int i; len = strlen(cmdline); if (len == 0) { printf("rateprofile %d\r\n", getCurrentControlRateProfile()); return; } else { i = atoi(cmdline); if (i >= 0 && i < MAX_CONTROL_RATE_PROFILE_COUNT) { changeControlRateProfile(i); cliRateProfile(""); } } } static void cliReboot(void) { cliPrint("\r\nRebooting"); waitForSerialPortToFinishTransmitting(cliPort); systemReset(); } static void cliSave(char *cmdline) { UNUSED(cmdline); cliPrint("Saving"); //copyCurrentProfileToProfileSlot(masterConfig.current_profile_index); writeEEPROM(); cliReboot(); } static void cliDefaults(char *cmdline) { UNUSED(cmdline); cliPrint("Resetting to defaults"); resetEEPROM(); cliReboot(); } static void cliPrint(const char *str) { while (*str) serialWrite(cliPort, *(str++)); } static void cliWrite(uint8_t ch) { serialWrite(cliPort, ch); } static void cliPrintVar(const clivalue_t *var, uint32_t full) { int32_t value = 0; char buf[8]; void *ptr = var->ptr; if (var->type & PROFILE_VALUE) { ptr = ((uint8_t *)ptr) + (sizeof(profile_t) * masterConfig.current_profile_index); } if (var->type & CONTROL_RATE_VALUE) { ptr = ((uint8_t *)ptr) + (sizeof(controlRateConfig_t) * getCurrentControlRateProfile()); } switch (var->type & VALUE_TYPE_MASK) { case VAR_UINT8: value = *(uint8_t *)ptr; break; case VAR_INT8: value = *(int8_t *)ptr; break; case VAR_UINT16: value = *(uint16_t *)ptr; break; case VAR_INT16: value = *(int16_t *)ptr; break; case VAR_UINT32: value = *(uint32_t *)ptr; break; case VAR_FLOAT: printf("%s", ftoa(*(float *)ptr, buf)); if (full) { printf(" %s", ftoa((float)var->min, buf)); printf(" %s", ftoa((float)var->max, buf)); } return; // return from case for float only } printf("%d", value); if (full) printf(" %d %d", var->min, var->max); } static void cliSetVar(const clivalue_t *var, const int_float_value_t value) { void *ptr = var->ptr; if (var->type & PROFILE_VALUE) { ptr = ((uint8_t *)ptr) + (sizeof(profile_t) * masterConfig.current_profile_index); } if (var->type & CONTROL_RATE_VALUE) { ptr = ((uint8_t *)ptr) + (sizeof(controlRateConfig_t) * getCurrentControlRateProfile()); } switch (var->type & VALUE_TYPE_MASK) { case VAR_UINT8: case VAR_INT8: *(char *)ptr = (char)value.int_value; break; case VAR_UINT16: case VAR_INT16: *(short *)ptr = (short)value.int_value; break; case VAR_UINT32: *(int *)ptr = (int)value.int_value; break; case VAR_FLOAT: *(float *)ptr = (float)value.float_value; break; } } static void cliSet(char *cmdline) { uint32_t i; uint32_t len; const clivalue_t *val; char *eqptr = NULL; int32_t value = 0; float valuef = 0; len = strlen(cmdline); if (len == 0 || (len == 1 && cmdline[0] == '*')) { cliPrint("Current settings: \r\n"); for (i = 0; i < VALUE_COUNT; i++) { val = &valueTable[i]; printf("%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 equal, set var char *lastNonSpaceCharacter = eqptr; while (*(lastNonSpaceCharacter - 1) == ' ') { lastNonSpaceCharacter--; } uint8_t variableNameLength = lastNonSpaceCharacter - cmdline; eqptr++; len--; value = atoi(eqptr); valuef = fastA2F(eqptr); for (i = 0; i < VALUE_COUNT; 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)) { if (valuef >= valueTable[i].min && valuef <= valueTable[i].max) { // here we compare the float value since... it should work, RIGHT? int_float_value_t tmp; if (valueTable[i].type & VAR_FLOAT) tmp.float_value = valuef; else tmp.int_value = value; cliSetVar(val, tmp); printf("%s set to ", valueTable[i].name); cliPrintVar(val, 0); } else { cliPrint("Value assignment out of range\r\n"); } return; } } cliPrint("Unknown variable name\r\n"); } else { // no equals, check for matching variables. cliGet(cmdline); } } static void cliGet(char *cmdline) { uint32_t i; const clivalue_t *val; int matchedCommands = 0; for (i = 0; i < VALUE_COUNT; i++) { if (strstr(valueTable[i].name, cmdline)) { val = &valueTable[i]; printf("%s = ", valueTable[i].name); cliPrintVar(val, 0); printf("\r\n"); matchedCommands++; } } if (matchedCommands) { return; } cliPrint("Unknown variable name\r\n"); } static void cliStatus(char *cmdline) { uint8_t i; uint32_t mask; UNUSED(cmdline); printf("System Uptime: %d seconds, Voltage: %d * 0.1V (%dS battery)\r\n", millis() / 1000, vbat, batteryCellCount); mask = sensorsMask(); printf("CPU %dMHz, detected sensors: ", (SystemCoreClock / 1000000)); for (i = 0; ; i++) { if (sensorNames[i] == NULL) break; if (mask & (1 << i)) printf("%s ", sensorNames[i]); } if (sensors(SENSOR_ACC)) { printf("ACCHW: %s", accNames[accHardware]); if (acc.revisionCode) printf(".%c", acc.revisionCode); } cliPrint("\r\n"); #ifdef USE_I2C uint16_t i2cErrorCounter = i2cGetErrorCounter(); #else uint16_t i2cErrorCounter = 0; #endif printf("Cycle Time: %d, I2C Errors: %d, config size: %d\r\n", cycleTime, i2cErrorCounter, sizeof(master_t)); } static void cliVersion(char *cmdline) { UNUSED(cmdline); printf("Cleanflight/%s %s / %s (%s)", targetName, buildDate, buildTime, shortGitRevision); } void cliProcess(void) { if (!cliMode) { cliEnter(); } while (serialTotalBytesWaiting(cliPort)) { uint8_t c = serialRead(cliPort); if (c == '\t' || c == '?') { // do tab completion const clicmd_t *cmd, *pstart = NULL, *pend = NULL; uint32_t i = bufferIndex; for (cmd = cmdTable; cmd < cmdTable + CMD_COUNT; cmd++) { if (bufferIndex && (strncasecmp(cliBuffer, cmd->name, bufferIndex) != 0)) continue; if (!pstart) pstart = cmd; pend = cmd; } if (pstart) { /* Buffer matches one or more commands */ for (; ; bufferIndex++) { if (pstart->name[bufferIndex] != pend->name[bufferIndex]) break; if (!pstart->name[bufferIndex] && bufferIndex < sizeof(cliBuffer) - 2) { /* Unambiguous -- append a space */ cliBuffer[bufferIndex++] = ' '; cliBuffer[bufferIndex] = '\0'; break; } cliBuffer[bufferIndex] = pstart->name[bufferIndex]; } } if (!bufferIndex || pstart != pend) { /* Print list of ambiguous matches */ cliPrint("\r\033[K"); for (cmd = pstart; cmd <= pend; cmd++) { cliPrint(cmd->name); cliWrite('\t'); } cliPrompt(); i = 0; /* Redraw prompt */ } for (; i < bufferIndex; i++) cliWrite(cliBuffer[i]); } else if (!bufferIndex && c == 4) { cliExit(cliBuffer); return; } else if (c == 12) { // clear screen cliPrint("\033[2J\033[1;1H"); cliPrompt(); } else if (bufferIndex && (c == '\n' || c == '\r')) { // enter pressed clicmd_t *cmd = NULL; clicmd_t target; cliPrint("\r\n"); cliBuffer[bufferIndex] = 0; // null terminate if (cliBuffer[0] != '#') { target.name = cliBuffer; target.param = NULL; cmd = bsearch(&target, cmdTable, CMD_COUNT, sizeof cmdTable[0], cliCompare); if (cmd) cmd->func(cliBuffer + strlen(cmd->name) + 1); else cliPrint("Unknown command, try 'help'"); } memset(cliBuffer, 0, sizeof(cliBuffer)); bufferIndex = 0; // 'exit' will reset this flag, so we don't need to print prompt again if (!cliMode) return; cliPrompt(); } else if (c == 127) { // backspace if (bufferIndex) { cliBuffer[--bufferIndex] = 0; cliPrint("\010 \010"); } } else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) { if (!bufferIndex && c == 32) continue; cliBuffer[bufferIndex++] = c; cliWrite(c); } } } void cliInit(serialConfig_t *serialConfig) { cliPort = findOpenSerialPort(FUNCTION_CLI); if (!cliPort) { cliPort = openSerialPort(FUNCTION_CLI, NULL, serialConfig->cli_baudrate, MODE_RXTX, SERIAL_NOT_INVERTED); } }