mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-12 19:10:32 +03:00
* REFACTOR: Adding DMA_FIRST_HANDLER dmaIdentifier_e could be zero based in the future and align to descriptor array index directly Also removed unused DMA_INPUT_STRING * Removed magic number following code rabbit review
6913 lines
231 KiB
C
6913 lines
231 KiB
C
/*
|
|
* This file is part of Cleanflight and Betaflight.
|
|
*
|
|
* Cleanflight and Betaflight are free software. You can redistribute
|
|
* this software and/or modify this software 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 and Betaflight are distributed in the hope that they
|
|
* 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 this software.
|
|
*
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
|
|
#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
|
|
bool cliMode = false;
|
|
|
|
#ifdef USE_CLI
|
|
|
|
#include "blackbox/blackbox.h"
|
|
|
|
#include "build/build_config.h"
|
|
#include "build/debug.h"
|
|
#include "build/version.h"
|
|
|
|
#include "cli/settings.h"
|
|
|
|
#include "cms/cms.h"
|
|
|
|
#include "common/axis.h"
|
|
#include "common/color.h"
|
|
#include "common/maths.h"
|
|
#include "common/printf.h"
|
|
#include "common/printf_serial.h"
|
|
#include "common/strtol.h"
|
|
#include "common/time.h"
|
|
#include "common/typeconversion.h"
|
|
#include "common/utils.h"
|
|
|
|
#include "config/config.h"
|
|
#include "config/config_eeprom.h"
|
|
#include "config/feature.h"
|
|
#include "config/simplified_tuning.h"
|
|
|
|
#include "drivers/accgyro/accgyro.h"
|
|
#include "drivers/adc.h"
|
|
#include "drivers/buf_writer.h"
|
|
#include "drivers/bus_i2c.h"
|
|
#include "drivers/bus_spi.h"
|
|
#include "drivers/dma.h"
|
|
#include "drivers/dma_reqmap.h"
|
|
#include "drivers/dshot.h"
|
|
#include "drivers/dshot_command.h"
|
|
#include "drivers/camera_control_impl.h"
|
|
#include "drivers/compass/compass.h"
|
|
#include "drivers/display.h"
|
|
#include "drivers/dma.h"
|
|
#include "drivers/flash/flash.h"
|
|
#include "drivers/inverter.h"
|
|
#include "drivers/io.h"
|
|
#include "drivers/io_impl.h"
|
|
#include "drivers/light_led.h"
|
|
#include "drivers/motor.h"
|
|
#include "drivers/rangefinder/rangefinder_hcsr04.h"
|
|
#include "drivers/resource.h"
|
|
#include "drivers/sdcard.h"
|
|
#include "drivers/sensor.h"
|
|
#include "drivers/serial.h"
|
|
#include "drivers/serial_escserial.h"
|
|
#include "drivers/sound_beeper.h"
|
|
#include "drivers/stack_check.h"
|
|
#include "drivers/system.h"
|
|
#include "drivers/time.h"
|
|
#include "drivers/timer.h"
|
|
#include "drivers/transponder_ir.h"
|
|
#include "drivers/usb_msc.h"
|
|
#include "drivers/vtx_common.h"
|
|
#include "drivers/vtx_table.h"
|
|
|
|
#include "fc/board_info.h"
|
|
#include "fc/controlrate_profile.h"
|
|
#include "fc/core.h"
|
|
#include "fc/rc.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/position.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/serial.h"
|
|
#include "io/transponder_ir.h"
|
|
#include "io/usb_msc.h"
|
|
#include "io/vtx_control.h"
|
|
#include "io/vtx.h"
|
|
|
|
#include "msp/msp.h"
|
|
#include "msp/msp_box.h"
|
|
#include "msp/msp_protocol.h"
|
|
|
|
#include "osd/osd.h"
|
|
|
|
#include "pg/adc.h"
|
|
#include "pg/beeper.h"
|
|
#include "pg/beeper_dev.h"
|
|
#include "pg/board.h"
|
|
#include "pg/bus_i2c.h"
|
|
#include "pg/bus_spi.h"
|
|
#include "pg/gyrodev.h"
|
|
#include "pg/max7456.h"
|
|
#include "pg/mco.h"
|
|
#include "pg/motor.h"
|
|
#include "pg/pilot.h"
|
|
#include "pg/pinio.h"
|
|
#include "pg/pin_pull_up_down.h"
|
|
#include "pg/pg.h"
|
|
#include "pg/pg_ids.h"
|
|
#include "pg/rx.h"
|
|
#include "pg/rx_pwm.h"
|
|
#include "pg/rx_spi_cc2500.h"
|
|
#include "pg/rx_spi_expresslrs.h"
|
|
#include "pg/serial_uart.h"
|
|
#include "pg/sdio.h"
|
|
#include "pg/timerio.h"
|
|
#include "pg/timerup.h"
|
|
#include "pg/usb.h"
|
|
#include "pg/vtx_table.h"
|
|
|
|
#include "rx/rx_bind.h"
|
|
#include "rx/rx_spi.h"
|
|
|
|
#include "scheduler/scheduler.h"
|
|
|
|
#include "sensors/acceleration.h"
|
|
#include "sensors/adcinternal.h"
|
|
#include "sensors/barometer.h"
|
|
#include "sensors/battery.h"
|
|
#include "sensors/boardalignment.h"
|
|
#include "sensors/compass.h"
|
|
#include "sensors/gyro.h"
|
|
#include "sensors/gyro_init.h"
|
|
#include "sensors/sensors.h"
|
|
|
|
#include "telemetry/frsky_hub.h"
|
|
#include "telemetry/telemetry.h"
|
|
|
|
#include "cli.h"
|
|
|
|
static serialPort_t *cliPort = NULL;
|
|
static bool cliInteractive = false;
|
|
static timeMs_t cliEntryTime = 0;
|
|
|
|
// Space required to set array parameters
|
|
#define CLI_IN_BUFFER_SIZE 256
|
|
#define CLI_OUT_BUFFER_SIZE 64
|
|
|
|
static bufWriter_t cliWriterDesc;
|
|
static bufWriter_t *cliWriter = NULL;
|
|
static bufWriter_t *cliErrorWriter = NULL;
|
|
static uint8_t cliWriteBuffer[CLI_OUT_BUFFER_SIZE];
|
|
|
|
static char cliBuffer[CLI_IN_BUFFER_SIZE];
|
|
static uint32_t bufferIndex = 0;
|
|
|
|
static bool configIsInCopy = false;
|
|
|
|
#define CURRENT_PROFILE_INDEX -1
|
|
static int8_t pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
|
|
static int8_t rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
|
|
|
|
#ifdef USE_CLI_BATCH
|
|
static bool commandBatchActive = false;
|
|
static bool commandBatchError = false;
|
|
#endif
|
|
|
|
#if defined(USE_BOARD_INFO)
|
|
static bool boardInformationUpdated = false;
|
|
#if defined(USE_SIGNATURE)
|
|
static bool signatureUpdated = false;
|
|
#endif
|
|
#endif // USE_BOARD_INFO
|
|
|
|
static const char* const emptyName = "-";
|
|
static const char* const invalidName = "INVALID";
|
|
|
|
#define MAX_CHANGESET_ID_LENGTH 8
|
|
#define MAX_DATE_LENGTH 20
|
|
|
|
#define ERROR_INVALID_NAME "INVALID NAME: %s"
|
|
#define ERROR_MESSAGE "%s CANNOT BE CHANGED. CURRENT VALUE: '%s'"
|
|
|
|
#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", "QUADX1234",
|
|
"OCTOX8P", NULL
|
|
};
|
|
#endif
|
|
|
|
// sync this with features_e
|
|
#define _R(_flag, _name) [LOG2(_flag)] = _name
|
|
static const char * const featureNames[] = {
|
|
_R(FEATURE_RX_PPM, "RX_PPM"),
|
|
_R(FEATURE_INFLIGHT_ACC_CAL, "INFLIGHT_ACC_CAL"),
|
|
_R(FEATURE_RX_SERIAL, "RX_SERIAL"),
|
|
_R(FEATURE_MOTOR_STOP, "MOTOR_STOP"),
|
|
_R(FEATURE_SERVO_TILT, "SERVO_TILT"),
|
|
_R(FEATURE_SOFTSERIAL, "SOFTSERIAL"),
|
|
_R(FEATURE_GPS, "GPS"),
|
|
_R(FEATURE_RANGEFINDER, "RANGEFINDER"),
|
|
_R(FEATURE_OPTICALFLOW, "OPTICALFLOW"),
|
|
_R(FEATURE_TELEMETRY, "TELEMETRY"),
|
|
_R(FEATURE_3D, "3D"),
|
|
_R(FEATURE_RX_PARALLEL_PWM, "RX_PARALLEL_PWM"),
|
|
_R(FEATURE_RSSI_ADC, "RSSI_ADC"),
|
|
_R(FEATURE_LED_STRIP, "LED_STRIP"),
|
|
_R(FEATURE_DASHBOARD, "DISPLAY"),
|
|
_R(FEATURE_OSD, "OSD"),
|
|
_R(FEATURE_CHANNEL_FORWARDING, "CHANNEL_FORWARDING"),
|
|
_R(FEATURE_TRANSPONDER, "TRANSPONDER"),
|
|
_R(FEATURE_AIRMODE, "AIRMODE"),
|
|
_R(FEATURE_RX_SPI, "RX_SPI"),
|
|
_R(FEATURE_ESC_SENSOR, "ESC_SENSOR"),
|
|
_R(FEATURE_ANTI_GRAVITY, "ANTI_GRAVITY"),
|
|
};
|
|
#undef _R
|
|
|
|
// sync this with rxFailsafeChannelMode_e
|
|
static const char rxFailsafeModeCharacters[] = "ahs";
|
|
|
|
static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
|
|
{ RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET },
|
|
{ RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
|
|
};
|
|
|
|
#if defined(USE_SENSOR_NAMES)
|
|
// sync this with sensors_e
|
|
static const char *const sensorTypeNames[] = {
|
|
"GYRO", "ACC", "BARO", "MAG", "RANGEFINDER", "GPS", "GPS+MAG", NULL
|
|
};
|
|
|
|
#define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG | SENSOR_RANGEFINDER)
|
|
|
|
static const char * const *sensorHardwareNames[] = {
|
|
lookupTableGyroHardware, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware, lookupTableRangefinderHardware, lookupTableOpticalflowHardware
|
|
};
|
|
#endif // USE_SENSOR_NAMES
|
|
|
|
static const char *configurationStates[] = {
|
|
[CONFIGURATION_STATE_UNCONFIGURED] = "UNCONFIGURED",
|
|
[CONFIGURATION_STATE_CONFIGURED] = "CONFIGURED"
|
|
};
|
|
|
|
typedef enum dumpFlags_e {
|
|
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),
|
|
HARDWARE_ONLY = (1 << 7),
|
|
BARE = (1 << 8),
|
|
} dumpFlags_t;
|
|
|
|
static void cliExit(const bool reboot);
|
|
typedef bool printFn(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...);
|
|
|
|
typedef enum {
|
|
REBOOT_TARGET_FIRMWARE,
|
|
REBOOT_TARGET_BOOTLOADER_ROM,
|
|
REBOOT_TARGET_BOOTLOADER_FLASH,
|
|
} rebootTarget_e;
|
|
|
|
typedef struct serialPassthroughPort_s {
|
|
serialPortIdentifier_e id;
|
|
uint32_t baud;
|
|
portMode_e mode;
|
|
portOptions_e options;
|
|
serialPort_t *port;
|
|
} serialPassthroughPort_t;
|
|
|
|
static void cliClearInputBuffer(void)
|
|
{
|
|
memset(cliBuffer, 0, sizeof(cliBuffer));
|
|
bufferIndex = 0;
|
|
}
|
|
|
|
static void cliWriterFlushInternal(bufWriter_t *writer)
|
|
{
|
|
if (writer) {
|
|
bufWriterFlush(writer);
|
|
}
|
|
}
|
|
|
|
static void cliPrintInternal(bufWriter_t *writer, const char *str)
|
|
{
|
|
if (writer) {
|
|
while (*str) {
|
|
bufWriterAppend(writer, *str++);
|
|
}
|
|
cliWriterFlushInternal(writer);
|
|
}
|
|
}
|
|
|
|
static void cliWriterFlush(void)
|
|
{
|
|
cliWriterFlushInternal(cliWriter);
|
|
}
|
|
|
|
#ifdef USE_CLI_DEBUG_PRINT
|
|
#define CLI_DEBUG_EXPORT /* empty */
|
|
#else
|
|
#define CLI_DEBUG_EXPORT static
|
|
#endif
|
|
|
|
CLI_DEBUG_EXPORT void cliPrint(const char *str)
|
|
{
|
|
cliPrintInternal(cliWriter, str);
|
|
}
|
|
|
|
CLI_DEBUG_EXPORT void cliPrintLinefeed(void)
|
|
{
|
|
cliPrint("\r\n");
|
|
}
|
|
|
|
CLI_DEBUG_EXPORT void cliPrintLine(const char *str)
|
|
{
|
|
cliPrint(str);
|
|
cliPrintLinefeed();
|
|
}
|
|
|
|
static void cliPrintHashLine(const char *str)
|
|
{
|
|
#ifndef MINIMAL_CLI
|
|
cliPrint("\r\n# ");
|
|
cliPrintLine(str);
|
|
#else
|
|
UNUSED(str);
|
|
#endif
|
|
}
|
|
|
|
static void cliPutp(void *p, char ch)
|
|
{
|
|
bufWriterAppend(p, ch);
|
|
}
|
|
|
|
static void cliPrintfva(const char *format, va_list va)
|
|
{
|
|
if (cliWriter) {
|
|
tfp_format(cliWriter, cliPutp, format, va);
|
|
cliWriterFlush();
|
|
}
|
|
}
|
|
|
|
static bool cliDumpPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
|
|
{
|
|
if (!((dumpMask & DO_DIFF) && equalsDefault)) {
|
|
va_list va;
|
|
va_start(va, format);
|
|
cliPrintfva(format, va);
|
|
va_end(va);
|
|
cliPrintLinefeed();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void cliWrite(uint8_t ch)
|
|
{
|
|
if (cliWriter) {
|
|
bufWriterAppend(cliWriter, ch);
|
|
}
|
|
}
|
|
|
|
static bool cliDefaultPrintLinef(dumpFlags_t dumpMask, bool equalsDefault, const char *format, ...)
|
|
{
|
|
if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
|
|
cliWrite('#');
|
|
|
|
va_list va;
|
|
va_start(va, format);
|
|
cliPrintfva(format, va);
|
|
va_end(va);
|
|
cliPrintLinefeed();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CLI_DEBUG_EXPORT void cliPrintf(const char *format, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, format);
|
|
cliPrintfva(format, va);
|
|
va_end(va);
|
|
}
|
|
|
|
CLI_DEBUG_EXPORT void cliPrintLinef(const char *format, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, format);
|
|
cliPrintfva(format, va);
|
|
va_end(va);
|
|
cliPrintLinefeed();
|
|
}
|
|
|
|
static void cliPrintErrorVa(const char *cmdName, const char *format, va_list va)
|
|
{
|
|
if (cliErrorWriter) {
|
|
cliPrintInternal(cliErrorWriter, "###ERROR IN ");
|
|
cliPrintInternal(cliErrorWriter, cmdName);
|
|
cliPrintInternal(cliErrorWriter, ": ");
|
|
|
|
tfp_format(cliErrorWriter, cliPutp, format, va);
|
|
va_end(va);
|
|
|
|
cliPrintInternal(cliErrorWriter, "###");
|
|
}
|
|
|
|
#ifdef USE_CLI_BATCH
|
|
if (commandBatchActive) {
|
|
commandBatchError = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void cliPrintError(const char *cmdName, const char *format, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, format);
|
|
cliPrintErrorVa(cmdName, format, va);
|
|
va_end(va);
|
|
|
|
if (!cliWriter) {
|
|
// Supply our own linefeed in case we are printing inside a custom defaults operation
|
|
// TODO: Fix this by rewriting the entire CLI to have self contained line feeds
|
|
// instead of expecting the directly following command to supply the line feed.
|
|
cliPrintInternal(cliErrorWriter, "\r\n");
|
|
}
|
|
}
|
|
|
|
static void cliPrintErrorLinef(const char *cmdName, const char *format, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, format);
|
|
cliPrintErrorVa(cmdName, format, va);
|
|
va_end(va);
|
|
cliPrintInternal(cliErrorWriter, "\r\n");
|
|
}
|
|
|
|
static void getMinMax(const clivalue_t *var, int *min, int *max)
|
|
{
|
|
switch (var->type & VALUE_TYPE_MASK) {
|
|
case VAR_UINT8:
|
|
case VAR_UINT16:
|
|
*min = var->config.minmaxUnsigned.min;
|
|
*max = var->config.minmaxUnsigned.max;
|
|
|
|
break;
|
|
default:
|
|
*min = var->config.minmax.min;
|
|
*max = var->config.minmax.max;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void printValuePointer(const char *cmdName, const clivalue_t *var, const void *valuePointer, bool full)
|
|
{
|
|
if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
|
|
for (int i = 0; i < var->config.array.length; i++) {
|
|
switch (var->type & VALUE_TYPE_MASK) {
|
|
default:
|
|
case VAR_UINT8:
|
|
// uint8_t array
|
|
cliPrintf("%d", ((uint8_t *)valuePointer)[i]);
|
|
break;
|
|
|
|
case VAR_INT8:
|
|
// int8_t array
|
|
cliPrintf("%d", ((int8_t *)valuePointer)[i]);
|
|
break;
|
|
|
|
case VAR_UINT16:
|
|
// uin16_t array
|
|
cliPrintf("%d", ((uint16_t *)valuePointer)[i]);
|
|
break;
|
|
|
|
case VAR_INT16:
|
|
// int16_t array
|
|
cliPrintf("%d", ((int16_t *)valuePointer)[i]);
|
|
break;
|
|
|
|
case VAR_UINT32:
|
|
// uin32_t array
|
|
cliPrintf("%u", ((uint32_t *)valuePointer)[i]);
|
|
break;
|
|
|
|
case VAR_INT32:
|
|
// in32_t array
|
|
cliPrintf("%d", ((int32_t *)valuePointer)[i]);
|
|
break;
|
|
}
|
|
|
|
if (i < var->config.array.length - 1) {
|
|
cliPrint(",");
|
|
}
|
|
}
|
|
} else {
|
|
int value = 0;
|
|
|
|
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_INT32:
|
|
value = *(int32_t *)valuePointer;
|
|
|
|
break;
|
|
}
|
|
|
|
bool valueIsCorrupted = false;
|
|
switch (var->type & VALUE_MODE_MASK) {
|
|
case MODE_DIRECT:
|
|
if ((var->type & VALUE_TYPE_MASK) == VAR_UINT32) {
|
|
cliPrintf("%u", (uint32_t)value);
|
|
if ((uint32_t)value > var->config.u32Max) {
|
|
valueIsCorrupted = true;
|
|
} else if (full) {
|
|
cliPrintf(" 0 %u", var->config.u32Max);
|
|
}
|
|
} else if ((var->type & VALUE_TYPE_MASK) == VAR_INT32) {
|
|
cliPrintf("%d", (int32_t)value);
|
|
if ((int32_t)value > var->config.d32Max || (int32_t)value < -var->config.d32Max) {
|
|
valueIsCorrupted = true;
|
|
} else if (full) {
|
|
cliPrintf(" 0 %u", var->config.u32Max);
|
|
}
|
|
} else {
|
|
int min;
|
|
int max;
|
|
getMinMax(var, &min, &max);
|
|
|
|
cliPrintf("%d", value);
|
|
if ((value < min) || (value > max)) {
|
|
valueIsCorrupted = true;
|
|
} else if (full) {
|
|
cliPrintf(" %d %d", min, max);
|
|
}
|
|
}
|
|
break;
|
|
case MODE_LOOKUP:
|
|
if (value < lookupTables[var->config.lookup.tableIndex].valueCount) {
|
|
cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
|
|
} else {
|
|
valueIsCorrupted = true;
|
|
}
|
|
break;
|
|
case MODE_BITSET:
|
|
if (value & 1 << var->config.bitpos) {
|
|
cliPrintf("ON");
|
|
} else {
|
|
cliPrintf("OFF");
|
|
}
|
|
break;
|
|
case MODE_STRING:
|
|
cliPrintf("%s", (strlen((char *)valuePointer) == 0) ? "-" : (char *)valuePointer);
|
|
break;
|
|
}
|
|
|
|
if (valueIsCorrupted) {
|
|
cliPrintLinefeed();
|
|
cliPrintError(cmdName, "CORRUPTED CONFIG: %s = %d", var->name, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool valuePtrEqualsDefault(const clivalue_t *var, const void *ptr, const void *ptrDefault)
|
|
{
|
|
bool result = true;
|
|
int elementCount = 1;
|
|
uint32_t mask = 0xffffffff;
|
|
|
|
if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
|
|
elementCount = var->config.array.length;
|
|
}
|
|
if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
|
|
mask = 1 << var->config.bitpos;
|
|
}
|
|
for (int i = 0; i < elementCount; i++) {
|
|
switch (var->type & VALUE_TYPE_MASK) {
|
|
case VAR_UINT8:
|
|
result = result && (((uint8_t *)ptr)[i] & mask) == (((uint8_t *)ptrDefault)[i] & mask);
|
|
break;
|
|
|
|
case VAR_INT8:
|
|
result = result && ((int8_t *)ptr)[i] == ((int8_t *)ptrDefault)[i];
|
|
break;
|
|
|
|
case VAR_UINT16:
|
|
result = result && (((uint16_t *)ptr)[i] & mask) == (((uint16_t *)ptrDefault)[i] & mask);
|
|
break;
|
|
case VAR_INT16:
|
|
result = result && ((int16_t *)ptr)[i] == ((int16_t *)ptrDefault)[i];
|
|
break;
|
|
case VAR_UINT32:
|
|
result = result && (((uint32_t *)ptr)[i] & mask) == (((uint32_t *)ptrDefault)[i] & mask);
|
|
break;
|
|
case VAR_INT32:
|
|
result = result && (((int32_t *)ptr)[i] & mask) == (((int32_t *)ptrDefault)[i] & mask);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static const char *cliPrintSectionHeading(dumpFlags_t dumpMask, bool outputFlag, const char *headingStr)
|
|
{
|
|
if (headingStr && (!(dumpMask & DO_DIFF) || outputFlag)) {
|
|
cliPrintHashLine(headingStr);
|
|
return NULL;
|
|
} else {
|
|
return headingStr;
|
|
}
|
|
}
|
|
|
|
static void backupPgConfig(const pgRegistry_t *pg)
|
|
{
|
|
memcpy(pg->copy, pg->address, pg->size);
|
|
}
|
|
|
|
static void restorePgConfig(const pgRegistry_t *pg, uint16_t notToRestoreGroupId)
|
|
{
|
|
if (!notToRestoreGroupId || pgN(pg) != notToRestoreGroupId) {
|
|
memcpy(pg->address, pg->copy, pg->size);
|
|
}
|
|
}
|
|
|
|
static void backupConfigs(void)
|
|
{
|
|
if (configIsInCopy) {
|
|
return;
|
|
}
|
|
|
|
PG_FOREACH(pg) {
|
|
backupPgConfig(pg);
|
|
}
|
|
|
|
configIsInCopy = true;
|
|
}
|
|
|
|
static void restoreConfigs(uint16_t notToRestoreGroupId)
|
|
{
|
|
if (!configIsInCopy) {
|
|
return;
|
|
}
|
|
|
|
PG_FOREACH(pg) {
|
|
restorePgConfig(pg, notToRestoreGroupId);
|
|
}
|
|
|
|
configIsInCopy = false;
|
|
}
|
|
|
|
#if defined(USE_RESOURCE_MGMT) || defined(USE_TIMER_MGMT)
|
|
static bool isReadingConfigFromCopy(void)
|
|
{
|
|
return configIsInCopy;
|
|
}
|
|
#endif
|
|
|
|
static bool isWritingConfigToCopy(void)
|
|
{
|
|
return configIsInCopy;
|
|
}
|
|
|
|
static void backupAndResetConfigs(void)
|
|
{
|
|
backupConfigs();
|
|
// reset all configs to defaults to do differencing
|
|
resetConfig();
|
|
}
|
|
|
|
static uint8_t getPidProfileIndexToUse(void)
|
|
{
|
|
return pidProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentPidProfileIndex() : pidProfileIndexToUse;
|
|
}
|
|
|
|
static uint8_t getRateProfileIndexToUse(void)
|
|
{
|
|
return rateProfileIndexToUse == CURRENT_PROFILE_INDEX ? getCurrentControlRateProfileIndex() : rateProfileIndexToUse;
|
|
}
|
|
|
|
static uint16_t getValueOffset(const clivalue_t *value)
|
|
{
|
|
switch (value->type & VALUE_SECTION_MASK) {
|
|
case MASTER_VALUE:
|
|
case HARDWARE_VALUE:
|
|
return value->offset;
|
|
case PROFILE_VALUE:
|
|
return value->offset + sizeof(pidProfile_t) * getPidProfileIndexToUse();
|
|
case PROFILE_RATE_VALUE:
|
|
return value->offset + sizeof(controlRateConfig_t) * getRateProfileIndexToUse();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
STATIC_UNIT_TESTED void *cliGetValuePointer(const clivalue_t *value)
|
|
{
|
|
const pgRegistry_t* rec = pgFind(value->pgn);
|
|
if (isWritingConfigToCopy()) {
|
|
return CONST_CAST(void *, rec->copy + getValueOffset(value));
|
|
} else {
|
|
return CONST_CAST(void *, rec->address + getValueOffset(value));
|
|
}
|
|
}
|
|
|
|
static const char *dumpPgValue(const char *cmdName, const clivalue_t *value, dumpFlags_t dumpMask, const char *headingStr)
|
|
{
|
|
const pgRegistry_t *pg = pgFind(value->pgn);
|
|
#ifdef DEBUG
|
|
if (!pg) {
|
|
cliPrintLinef("VALUE %s ERROR", value->name);
|
|
return headingStr; // if it's not found, the pgn shouldn't be in the value table!
|
|
}
|
|
#endif
|
|
|
|
const char *format = "set %s = ";
|
|
const char *defaultFormat = "#set %s = ";
|
|
const int valueOffset = getValueOffset(value);
|
|
const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
|
|
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
|
|
if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
|
|
cliPrintf(defaultFormat, value->name);
|
|
printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
|
|
cliPrintLinefeed();
|
|
}
|
|
cliPrintf(format, value->name);
|
|
printValuePointer(cmdName, value, pg->copy + valueOffset, false);
|
|
cliPrintLinefeed();
|
|
}
|
|
return headingStr;
|
|
}
|
|
|
|
static void dumpAllValues(const char *cmdName, uint16_t valueSection, dumpFlags_t dumpMask, const char *headingStr)
|
|
{
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
|
|
for (uint32_t i = 0; i < valueTableEntryCount; i++) {
|
|
const clivalue_t *value = &valueTable[i];
|
|
cliWriterFlush();
|
|
if ((value->type & VALUE_SECTION_MASK) == valueSection || ((valueSection == MASTER_VALUE) && (value->type & VALUE_SECTION_MASK) == HARDWARE_VALUE)) {
|
|
headingStr = dumpPgValue(cmdName, value, dumpMask, headingStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cliPrintVar(const char *cmdName, const clivalue_t *var, bool full)
|
|
{
|
|
const void *ptr = cliGetValuePointer(var);
|
|
|
|
printValuePointer(cmdName, var, ptr, full);
|
|
}
|
|
|
|
static void cliPrintVarRange(const clivalue_t *var)
|
|
{
|
|
switch (var->type & VALUE_MODE_MASK) {
|
|
case (MODE_DIRECT): {
|
|
switch (var->type & VALUE_TYPE_MASK) {
|
|
case VAR_UINT32:
|
|
cliPrintLinef("Allowed range: 0 - %u", var->config.u32Max);
|
|
|
|
break;
|
|
case VAR_INT32:
|
|
cliPrintLinef("Allowed range: %d - %d", -var->config.d32Max, var->config.d32Max);
|
|
|
|
break;
|
|
case VAR_UINT8:
|
|
case VAR_UINT16:
|
|
cliPrintLinef("Allowed range: %d - %d", var->config.minmaxUnsigned.min, var->config.minmaxUnsigned.max);
|
|
|
|
break;
|
|
default:
|
|
cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
|
|
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case (MODE_LOOKUP): {
|
|
const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
|
|
cliPrint("Allowed values: ");
|
|
bool firstEntry = true;
|
|
for (unsigned i = 0; i < tableEntry->valueCount; i++) {
|
|
if (tableEntry->values[i]) {
|
|
if (!firstEntry) {
|
|
cliPrint(", ");
|
|
}
|
|
cliPrintf("%s", tableEntry->values[i]);
|
|
firstEntry = false;
|
|
}
|
|
}
|
|
cliPrintLinefeed();
|
|
}
|
|
break;
|
|
case (MODE_ARRAY): {
|
|
cliPrintLinef("Array length: %d", var->config.array.length);
|
|
}
|
|
break;
|
|
case (MODE_STRING): {
|
|
cliPrintLinef("String length: %d - %d", var->config.string.minlength, var->config.string.maxlength);
|
|
}
|
|
break;
|
|
case (MODE_BITSET): {
|
|
cliPrintLinef("Allowed values: OFF, ON");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cliSetVar(const clivalue_t *var, const uint32_t value)
|
|
{
|
|
void *ptr = cliGetValuePointer(var);
|
|
uint32_t workValue;
|
|
uint32_t mask;
|
|
|
|
if ((var->type & VALUE_MODE_MASK) == MODE_BITSET) {
|
|
switch (var->type & VALUE_TYPE_MASK) {
|
|
case VAR_UINT8:
|
|
mask = (1 << var->config.bitpos) & 0xff;
|
|
if (value) {
|
|
workValue = *(uint8_t *)ptr | mask;
|
|
} else {
|
|
workValue = *(uint8_t *)ptr & ~mask;
|
|
}
|
|
*(uint8_t *)ptr = workValue;
|
|
break;
|
|
|
|
case VAR_UINT16:
|
|
mask = (1 << var->config.bitpos) & 0xffff;
|
|
if (value) {
|
|
workValue = *(uint16_t *)ptr | mask;
|
|
} else {
|
|
workValue = *(uint16_t *)ptr & ~mask;
|
|
}
|
|
*(uint16_t *)ptr = workValue;
|
|
break;
|
|
|
|
case VAR_UINT32:
|
|
mask = 1 << var->config.bitpos;
|
|
if (value) {
|
|
workValue = *(uint32_t *)ptr | mask;
|
|
} else {
|
|
workValue = *(uint32_t *)ptr & ~mask;
|
|
}
|
|
*(uint32_t *)ptr = workValue;
|
|
break;
|
|
|
|
case VAR_INT32:
|
|
mask = 1 << var->config.bitpos;
|
|
if (value) {
|
|
workValue = *(int32_t *)ptr | mask;
|
|
} else {
|
|
workValue = *(int32_t *)ptr & ~mask;
|
|
}
|
|
*(int32_t *)ptr = workValue;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (var->type & VALUE_TYPE_MASK) {
|
|
case VAR_UINT8:
|
|
*(uint8_t *)ptr = value;
|
|
break;
|
|
|
|
case VAR_INT8:
|
|
*(int8_t *)ptr = value;
|
|
break;
|
|
|
|
case VAR_UINT16:
|
|
*(uint16_t *)ptr = value;
|
|
break;
|
|
|
|
case VAR_INT16:
|
|
*(int16_t *)ptr = value;
|
|
break;
|
|
|
|
case VAR_UINT32:
|
|
*(uint32_t *)ptr = value;
|
|
break;
|
|
|
|
case VAR_INT32:
|
|
*(int32_t *)ptr = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
|
|
static void cliRepeat(char ch, uint8_t len)
|
|
{
|
|
if (cliWriter) {
|
|
for (int i = 0; i < len; i++) {
|
|
bufWriterAppend(cliWriter, ch);
|
|
}
|
|
cliPrintLinefeed();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void cliPrompt(void)
|
|
{
|
|
cliPrint("\r\n# ");
|
|
}
|
|
|
|
static void cliShowParseError(const char *cmdName)
|
|
{
|
|
cliPrintErrorLinef(cmdName, "PARSING FAILED");
|
|
}
|
|
|
|
static void cliShowInvalidArgumentCountError(const char *cmdName)
|
|
{
|
|
cliPrintErrorLinef(cmdName, "INVALID ARGUMENT COUNT");
|
|
}
|
|
|
|
static void cliShowArgumentRangeError(const char *cmdName, char *name, int min, int max)
|
|
{
|
|
if (name) {
|
|
cliPrintErrorLinef(cmdName, "%s NOT BETWEEN %d AND %d", name, min, max);
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "ARGUMENT OUT OF RANGE");
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void printRxFailsafe(dumpFlags_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs, const char *headingStr)
|
|
{
|
|
// print out rxConfig failsafe settings
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
|
|
const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
|
|
const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
|
|
const bool equalsDefault = !memcmp(channelFailsafeConfig, defaultChannelFailsafeConfig, sizeof(*channelFailsafeConfig));
|
|
const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
if (requireValue) {
|
|
const char *format = "rxfail %u %c %d";
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
channel,
|
|
rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
|
|
RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
|
|
);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
channel,
|
|
rxFailsafeModeCharacters[channelFailsafeConfig->mode],
|
|
RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
|
|
);
|
|
} else {
|
|
const char *format = "rxfail %u %c";
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
channel,
|
|
rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
|
|
);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
channel,
|
|
rxFailsafeModeCharacters[channelFailsafeConfig->mode]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cliRxFailsafe(const char *cmdName, char *cmdline)
|
|
{
|
|
uint8_t channel;
|
|
char buf[3];
|
|
|
|
if (isEmpty(cmdline)) {
|
|
// print out rxConfig failsafe settings
|
|
for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
|
|
cliRxFailsafe(cmdName, itoa(channel, buf, 10));
|
|
}
|
|
} else {
|
|
const char *ptr = cmdline;
|
|
channel = atoi(ptr++);
|
|
if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
|
|
|
|
rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
|
|
|
|
const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
|
|
rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
|
|
bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
|
|
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
|
|
if (p) {
|
|
const uint8_t requestedMode = p - rxFailsafeModeCharacters;
|
|
mode = rxFailsafeModesTable[type][requestedMode];
|
|
} else {
|
|
mode = RX_FAILSAFE_MODE_INVALID;
|
|
}
|
|
if (mode == RX_FAILSAFE_MODE_INVALID) {
|
|
cliShowParseError(cmdName);
|
|
return;
|
|
}
|
|
|
|
requireValue = mode == RX_FAILSAFE_MODE_SET;
|
|
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
if (!requireValue) {
|
|
cliShowParseError(cmdName);
|
|
return;
|
|
}
|
|
uint16_t value = atoi(ptr);
|
|
value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
|
|
if (value > MAX_RXFAIL_RANGE_STEP) {
|
|
cliPrintErrorLinef(cmdName, "value out of range: %d", value);
|
|
return;
|
|
}
|
|
|
|
channelFailsafeConfig->step = value;
|
|
} else if (requireValue) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
return;
|
|
}
|
|
channelFailsafeConfig->mode = mode;
|
|
}
|
|
|
|
char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
|
|
|
|
// double use of cliPrintf below
|
|
// 1. acknowledge interpretation on command,
|
|
// 2. query current setting on single item,
|
|
|
|
if (requireValue) {
|
|
cliPrintLinef("rxfail %u %c %d",
|
|
channel,
|
|
modeCharacter,
|
|
RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
|
|
);
|
|
} else {
|
|
cliPrintLinef("rxfail %u %c",
|
|
channel,
|
|
modeCharacter
|
|
);
|
|
}
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, "CHANNEL", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void printAux(dumpFlags_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions, const char *headingStr)
|
|
{
|
|
const char *format = "aux %u %u %u %u %u %u %u";
|
|
// print out aux channel settings
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
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 = !isModeActivationConditionConfigured(mac, macDefault);
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
const box_t *box = findBoxByBoxId(macDefault->modeId);
|
|
const box_t *linkedTo = findBoxByBoxId(macDefault->linkedTo);
|
|
if (box) {
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
box->permanentId,
|
|
macDefault->auxChannelIndex,
|
|
MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep),
|
|
macDefault->modeLogic,
|
|
linkedTo ? linkedTo->permanentId : 0
|
|
);
|
|
}
|
|
}
|
|
const box_t *box = findBoxByBoxId(mac->modeId);
|
|
const box_t *linkedTo = findBoxByBoxId(mac->linkedTo);
|
|
if (box) {
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
box->permanentId,
|
|
mac->auxChannelIndex,
|
|
MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
|
|
mac->modeLogic,
|
|
linkedTo ? linkedTo->permanentId : 0
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cliAux(const char *cmdName, char *cmdline)
|
|
{
|
|
int i, val = 0;
|
|
const char *ptr;
|
|
|
|
if (isEmpty(cmdline)) {
|
|
printAux(DUMP_MASTER, modeActivationConditions(0), NULL, 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);
|
|
const box_t *box = findBoxByPermanentId(val);
|
|
if (box) {
|
|
mac->modeId = box->boxId;
|
|
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);
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
val = atoi(ptr);
|
|
if (val == MODELOGIC_OR || val == MODELOGIC_AND) {
|
|
mac->modeLogic = val;
|
|
validArgumentCount++;
|
|
}
|
|
}
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
val = atoi(ptr);
|
|
const box_t *box = findBoxByPermanentId(val);
|
|
if (box) {
|
|
mac->linkedTo = box->boxId;
|
|
validArgumentCount++;
|
|
}
|
|
}
|
|
if (validArgumentCount == 4) { // for backwards compatibility
|
|
mac->modeLogic = MODELOGIC_OR;
|
|
mac->linkedTo = 0;
|
|
} else if (validArgumentCount == 5) { // for backwards compatibility
|
|
mac->linkedTo = 0;
|
|
} else if (validArgumentCount != 6) {
|
|
memset(mac, 0, sizeof(modeActivationCondition_t));
|
|
}
|
|
analyzeModeActivationConditions();
|
|
cliPrintLinef( "aux %u %u %u %u %u %u %u",
|
|
i,
|
|
findBoxByBoxId(mac->modeId)->permanentId,
|
|
mac->auxChannelIndex,
|
|
MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep),
|
|
mac->modeLogic,
|
|
findBoxByBoxId(mac->linkedTo)->permanentId
|
|
);
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void printSerial(dumpFlags_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault, const char *headingStr)
|
|
{
|
|
const char *format = "serial %s %d %ld %ld %ld %ld";
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (unsigned i = 0; i < ARRAYLEN(serialConfig->portConfigs); i++) {
|
|
if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
|
|
continue;
|
|
};
|
|
bool equalsDefault = false;
|
|
if (serialConfigDefault) {
|
|
equalsDefault = !memcmp(&serialConfig->portConfigs[i], &serialConfigDefault->portConfigs[i], sizeof(serialConfig->portConfigs[i]));
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
serialName(serialConfigDefault->portConfigs[i].identifier, invalidName),
|
|
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]
|
|
);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
serialName(serialConfig->portConfigs[i].identifier, invalidName),
|
|
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(const char *cmdName, char *cmdline)
|
|
{
|
|
const char *format = "serial %s %d %ld %ld %ld %ld";
|
|
if (isEmpty(cmdline)) {
|
|
printSerial(DUMP_MASTER, serialConfig(), NULL, NULL);
|
|
return;
|
|
}
|
|
|
|
serialPortConfig_t portConfig;
|
|
memset(&portConfig, 0 , sizeof(portConfig));
|
|
|
|
uint8_t validArgumentCount = 0;
|
|
|
|
char *ptr = cmdline;
|
|
char *tok = strsep(&ptr, " ");
|
|
serialPortIdentifier_e identifier = findSerialPortByName(tok, strcasecmp);
|
|
if (identifier == SERIAL_PORT_NONE) {
|
|
char *eptr;
|
|
identifier = strtoul(tok, &eptr, 10);
|
|
if (*eptr) {
|
|
// parsing ended before end of token indicating an invalid identifier
|
|
identifier = SERIAL_PORT_NONE;
|
|
} else {
|
|
// correction for legacy configuration where UART1 == 0
|
|
if (identifier >= SERIAL_PORT_LEGACY_START_IDENTIFIER && identifier < SERIAL_PORT_START_IDENTIFIER) {
|
|
identifier += SERIAL_PORT_UART1;
|
|
}
|
|
}
|
|
}
|
|
|
|
serialPortConfig_t *currentConfig = serialFindPortConfigurationMutable(identifier);
|
|
|
|
if (!currentConfig) {
|
|
cliShowParseError(cmdName);
|
|
return;
|
|
}
|
|
|
|
portConfig.identifier = identifier;
|
|
validArgumentCount++;
|
|
|
|
tok = strsep(&ptr, " ");
|
|
if (tok) {
|
|
int val = strtoul(tok, NULL, 10);
|
|
portConfig.functionMask = val;
|
|
validArgumentCount++;
|
|
}
|
|
|
|
for (int i = 0; i < 4; i ++) {
|
|
tok = strsep(&ptr, " ");
|
|
if (!tok) {
|
|
break;
|
|
}
|
|
|
|
int val = atoi(tok);
|
|
|
|
uint8_t baudRateIndex = lookupBaudRateIndex(val);
|
|
if (baudRates[baudRateIndex] != (uint32_t) val) {
|
|
break;
|
|
}
|
|
|
|
switch (i) {
|
|
case 0:
|
|
if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
|
|
continue;
|
|
}
|
|
portConfig.msp_baudrateIndex = baudRateIndex;
|
|
break;
|
|
case 1:
|
|
if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_230400) {
|
|
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_2470000) {
|
|
continue;
|
|
}
|
|
portConfig.blackbox_baudrateIndex = baudRateIndex;
|
|
break;
|
|
}
|
|
|
|
validArgumentCount++;
|
|
}
|
|
|
|
if (validArgumentCount < 6) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
return;
|
|
}
|
|
|
|
memcpy(currentConfig, &portConfig, sizeof(portConfig));
|
|
|
|
cliDumpPrintLinef(0, false, format,
|
|
serialName(portConfig.identifier, invalidName),
|
|
portConfig.functionMask,
|
|
baudRates[portConfig.msp_baudrateIndex],
|
|
baudRates[portConfig.gps_baudrateIndex],
|
|
baudRates[portConfig.telemetry_baudrateIndex],
|
|
baudRates[portConfig.blackbox_baudrateIndex]
|
|
);
|
|
}
|
|
|
|
#if defined(USE_SERIAL_PASSTHROUGH)
|
|
static void cbCtrlLine_reset(void *context, uint16_t ctrl)
|
|
{
|
|
UNUSED(context);
|
|
if (!(ctrl & CTRL_LINE_STATE_DTR)) {
|
|
systemReset();
|
|
}
|
|
}
|
|
|
|
#ifdef USE_PINIO
|
|
static void cbCtrlLine_pinIO(void *context, uint16_t ctrl)
|
|
{
|
|
pinioSet((intptr_t)context, !(ctrl & CTRL_LINE_STATE_DTR));
|
|
}
|
|
#endif
|
|
|
|
static portMode_e cliParseSerialMode(const char *tok)
|
|
{
|
|
portMode_e mode = 0;
|
|
|
|
if (strcasestr(tok, "rx")) {
|
|
mode |= MODE_RX;
|
|
}
|
|
if (strcasestr(tok, "tx")) {
|
|
mode |= MODE_TX;
|
|
}
|
|
|
|
return mode;
|
|
}
|
|
|
|
static portOptions_e cliParseSerialOptions(const char *tok)
|
|
{
|
|
struct {
|
|
const char* tag;
|
|
portOptions_e val;
|
|
} map[] = {
|
|
{"Invert", SERIAL_INVERTED},
|
|
{"Stop2", SERIAL_STOPBITS_2},
|
|
{"Even", SERIAL_PARITY_EVEN},
|
|
{"Bidir", SERIAL_BIDIR},
|
|
{"Pushpull", SERIAL_BIDIR_PP},
|
|
{"Saudio", SERIAL_PULL_SMARTAUDIO},
|
|
{"Check", SERIAL_CHECK_TX},
|
|
};
|
|
portOptions_e options = 0;
|
|
for (unsigned i = 0; i < ARRAYLEN(map); i++) {
|
|
if (strstr(tok, map[i].tag) != 0) {
|
|
options |= map[i].val;
|
|
}
|
|
}
|
|
return options;
|
|
}
|
|
|
|
static void cliSerialPassthrough(const char *cmdName, char *cmdline)
|
|
{
|
|
if (isEmpty(cmdline)) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
return;
|
|
}
|
|
|
|
serialPassthroughPort_t ports[2] = { {SERIAL_PORT_NONE, 0, 0, 0, NULL}, {cliPort->identifier, 0, 0, 0, cliPort} };
|
|
bool enableBaudCb = false;
|
|
#ifdef USE_PINIO
|
|
int port1PinioDtr = -1; // route port2 USB DTR to pinio
|
|
#endif
|
|
bool port1ResetOnDtr = false; // reset board with DTR
|
|
bool escSensorPassthrough = false;
|
|
|
|
char* nexttok = cmdline;
|
|
char* tok;
|
|
int index = 0;
|
|
while ((tok = strsep(&nexttok, " ")) != NULL) {
|
|
if (*tok == '\0') { // skip adjacent delimiters
|
|
continue;
|
|
}
|
|
unsigned portN = (index < 4) ? 0 : 1; // port1 / port2
|
|
switch (index) {
|
|
case 0: // port1 to open: esc_sensor, portName, port ID port1
|
|
case 4: // port2 to use (defaults to CLI serial if no more arguments)
|
|
{
|
|
serialPortIdentifier_e portId;
|
|
char* endptr;
|
|
if (portN == 0 && strcasestr(tok, "esc_sensor") != NULL) {
|
|
escSensorPassthrough = true;
|
|
const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_ESC_SENSOR);
|
|
portId = portConfig ? portConfig->identifier : SERIAL_PORT_NONE;
|
|
} else if (strcasecmp(tok, "cli") == 0) {
|
|
portId = cliPort->identifier;
|
|
} else if ((portId = findSerialPortByName(tok, strcasecmp)) >= 0) {
|
|
// empty
|
|
} else if ((portId = strtol(tok, &endptr, 10)) >= 0 && *endptr == '\0') {
|
|
// empty
|
|
} else {
|
|
cliPrintLinef("Failed parsing port%d (%s)", portN + 1, tok);
|
|
return;
|
|
}
|
|
if (portN == 1) { // port1 is specified, don't use CLI port
|
|
ports[portN].port = NULL;
|
|
}
|
|
ports[portN].id = portId;
|
|
break;
|
|
}
|
|
case 1: // baudrate
|
|
case 5: {
|
|
int baud = atoi(tok);
|
|
ports[portN].baud = baud;
|
|
break;
|
|
}
|
|
case 2: // port1 mode (rx/tx/rxtx) + options
|
|
case 6: // port2 mode + options
|
|
ports[portN].mode = cliParseSerialMode(tok);
|
|
ports[portN].options = cliParseSerialOptions(tok);
|
|
break;
|
|
case 3: // DTR action
|
|
if (strcasecmp(tok, "reset") == 0) {
|
|
port1ResetOnDtr = true;
|
|
break;
|
|
}
|
|
if (strcasecmp(tok, "none") == 0) {
|
|
break;
|
|
}
|
|
#ifdef USE_PINIO
|
|
port1PinioDtr = atoi(tok);
|
|
if (port1PinioDtr < 0 || port1PinioDtr >= PINIO_COUNT) {
|
|
cliPrintLinef("Invalid PinIO number %d", port1PinioDtr);
|
|
return;
|
|
}
|
|
#endif /* USE_PINIO */
|
|
break;
|
|
default:
|
|
cliPrintLinef("Unexpected argument %d (%s)", index + 1, tok);
|
|
return;
|
|
}
|
|
index++;
|
|
}
|
|
|
|
for (unsigned i = 0; i < ARRAYLEN(ports); i++) {
|
|
if (findSerialPortIndexByIdentifier(ports[i].id) < 0) {
|
|
cliPrintLinef("Invalid port%d %d", i + 1, ports[i].id);
|
|
return;
|
|
} else {
|
|
cliPrintLinef("Port%d: %s", i + 1, serialName(ports[i].id, invalidName));
|
|
}
|
|
}
|
|
|
|
// Port checks
|
|
if (ports[0].id == ports[1].id) {
|
|
cliPrintLinef("Port1 and port2 are same");
|
|
return ;
|
|
}
|
|
|
|
if (ports[0].baud == 0 && ports[1].id == SERIAL_PORT_USB_VCP) {
|
|
enableBaudCb = true;
|
|
}
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
serialPassthroughPort_t* cfg = &ports[i];
|
|
if (cfg->port != NULL) { // port already selected, don't touch it (used when port2 defaults to cli)
|
|
continue;
|
|
}
|
|
|
|
int portIndex = i + 1;
|
|
serialPortUsage_t *portUsage = findSerialPortUsageByIdentifier(cfg->id);
|
|
if (!portUsage || portUsage->serialPort == NULL) {
|
|
// serial port is not open yet
|
|
const bool isUseDefaultBaud = cfg->baud == 0;
|
|
if (isUseDefaultBaud) {
|
|
// Set default baud
|
|
cfg->baud = 57600;
|
|
}
|
|
|
|
if (!cfg->mode) {
|
|
cliPrintLinef("Using RXTX mode as default");
|
|
cfg->mode = MODE_RXTX;
|
|
}
|
|
|
|
if (cfg->options) {
|
|
cliPrintLinef("Port%d: using options 0x%x",
|
|
portIndex, cfg->options);
|
|
}
|
|
cfg->port = openSerialPort(cfg->id, FUNCTION_NONE,
|
|
NULL, NULL, // rxCallback
|
|
cfg->baud, cfg->mode, cfg->options);
|
|
if (!cfg->port) {
|
|
cliPrintLinef("Port%d could not be opened.", portIndex);
|
|
return;
|
|
}
|
|
|
|
cliPrintf("Port%d opened, %sbaud = %d.\r\n", portIndex, isUseDefaultBaud ? "default ":"", cfg->baud);
|
|
} else {
|
|
cfg->port = portUsage->serialPort;
|
|
// If the user supplied a mode, override the port's mode, otherwise
|
|
// leave the mode unchanged. serialPassthrough() handles one-way ports.
|
|
// Set the baud rate if specified
|
|
if (cfg->baud) {
|
|
serialSetBaudRate(cfg->port, cfg->baud);
|
|
}
|
|
cliPrintLinef("Port%d is already open, %sbaud = %d.", portIndex, cfg->baud ? "new " : "", cfg->port->baudRate);
|
|
|
|
if (cfg->mode && cfg->port->mode != cfg->mode) {
|
|
cliPrintLinef("Port%d mode changed from %d to %d.",
|
|
portIndex, cfg->port->mode, cfg->mode);
|
|
serialSetMode(cfg->port, cfg->mode);
|
|
}
|
|
|
|
if (cfg->options) {
|
|
cliPrintLinef("Port%d is open, can't change options from 0x%x to 0x%x",
|
|
portIndex, cfg->port->options, cfg->options);
|
|
}
|
|
|
|
// If this port has a rx callback associated we need to remove it now.
|
|
// Otherwise no data will be pushed in the serial port buffer!
|
|
if (cfg->port->rxCallback) {
|
|
cliPrintLinef("Port%d: Callback removed", portIndex);
|
|
cfg->port->rxCallback = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no baud rate is specified allow to be set via USB
|
|
if (enableBaudCb) {
|
|
cliPrintLine("Port1 baud rate change over USB enabled.");
|
|
// Register the right side baud rate setting routine with the left side which allows setting of the UART
|
|
// baud rate over USB without setting it using the serialpassthrough command
|
|
serialSetBaudRateCb(ports[1].port, serialSetBaudRate, ports[0].port);
|
|
}
|
|
|
|
const char *resetMessage = "";
|
|
if (port1ResetOnDtr && ports[1].id == SERIAL_PORT_USB_VCP) {
|
|
resetMessage = "or drop DTR ";
|
|
}
|
|
|
|
cliPrintLinef("Forwarding, power cycle %sto exit.", resetMessage);
|
|
|
|
if (ports[1].id == SERIAL_PORT_USB_VCP) {
|
|
do {
|
|
if (port1ResetOnDtr) {
|
|
serialSetCtrlLineStateCb(ports[1].port, cbCtrlLine_reset, NULL);
|
|
break;
|
|
}
|
|
#ifdef USE_PINIO
|
|
if (port1PinioDtr >= 0) {
|
|
serialSetCtrlLineStateCb(ports[1].port, cbCtrlLine_pinIO, (void *)(intptr_t)(port1PinioDtr));
|
|
break;
|
|
}
|
|
#endif /* USE_PINIO */
|
|
} while (0);
|
|
}
|
|
|
|
// XXX Review ESC pass through under refactored motor handling
|
|
#ifdef USE_PWM_OUTPUT
|
|
if (escSensorPassthrough) {
|
|
// pwmDisableMotors();
|
|
motorDisable();
|
|
delay(5);
|
|
for (unsigned i = 0; i < getMotorCount(); i++) {
|
|
const ioTag_t tag = motorConfig()->dev.ioTags[i];
|
|
if (tag) {
|
|
const timerHardware_t *timerHardware = timerGetConfiguredByTag(tag);
|
|
if (timerHardware) {
|
|
IO_t io = IOGetByTag(tag);
|
|
IOInit(io, OWNER_MOTOR, i);
|
|
IOConfigGPIO(io, IOCFG_OUT_PP);
|
|
if (timerHardware->output & TIMER_OUTPUT_INVERTED) {
|
|
IOLo(io);
|
|
} else {
|
|
IOHi(io);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
serialPassthrough(ports[0].port, ports[1].port, NULL, NULL);
|
|
}
|
|
#endif
|
|
|
|
static void printAdjustmentRange(dumpFlags_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges, const char *headingStr)
|
|
{
|
|
const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
|
|
// print out adjustment ranges channel settings
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
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 = !memcmp(ar, arDefault, sizeof(*ar));
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
arDefault->auxChannelIndex,
|
|
MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
|
|
arDefault->adjustmentConfig,
|
|
arDefault->auxSwitchChannelIndex,
|
|
arDefault->adjustmentCenter,
|
|
arDefault->adjustmentScale
|
|
);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
ar->auxChannelIndex,
|
|
MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
|
|
ar->adjustmentConfig,
|
|
ar->auxSwitchChannelIndex,
|
|
ar->adjustmentCenter,
|
|
ar->adjustmentScale
|
|
);
|
|
}
|
|
}
|
|
|
|
static void cliAdjustmentRange(const char *cmdName, char *cmdline)
|
|
{
|
|
const char *format = "adjrange %u 0 %u %u %u %u %u %u %u";
|
|
const char *ptr;
|
|
|
|
if (isEmpty(cmdline)) {
|
|
printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL, NULL);
|
|
} else {
|
|
ptr = cmdline;
|
|
int i = atoi(ptr++);
|
|
if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
|
|
adjustmentRange_t *ar = adjustmentRangesMutable(i);
|
|
uint8_t validArgumentCount = 0;
|
|
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
// Was: slot
|
|
// Keeping the parameter to retain backwards compatibility for the command format.
|
|
validArgumentCount++;
|
|
}
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
int 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) {
|
|
int val = atoi(ptr);
|
|
if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
|
|
ar->adjustmentConfig = val;
|
|
validArgumentCount++;
|
|
}
|
|
}
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
int val = atoi(ptr);
|
|
if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
|
|
ar->auxSwitchChannelIndex = val;
|
|
validArgumentCount++;
|
|
}
|
|
}
|
|
|
|
if (validArgumentCount != 6) {
|
|
memset(ar, 0, sizeof(adjustmentRange_t));
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
return;
|
|
}
|
|
|
|
// Optional arguments
|
|
ar->adjustmentCenter = 0;
|
|
ar->adjustmentScale = 0;
|
|
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
int val = atoi(ptr);
|
|
ar->adjustmentCenter = val;
|
|
validArgumentCount++;
|
|
}
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
int val = atoi(ptr);
|
|
ar->adjustmentScale = val;
|
|
validArgumentCount++;
|
|
}
|
|
|
|
activeAdjustmentRangeReset();
|
|
|
|
cliDumpPrintLinef(0, false, format,
|
|
i,
|
|
ar->auxChannelIndex,
|
|
MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
|
|
ar->adjustmentConfig,
|
|
ar->auxSwitchChannelIndex,
|
|
ar->adjustmentCenter,
|
|
ar->adjustmentScale
|
|
);
|
|
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef USE_QUAD_MIXER_ONLY
|
|
static void printMotorMix(dumpFlags_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer, const char *headingStr)
|
|
{
|
|
const char *format = "mmix %d %s %s %s %s";
|
|
char buf0[FTOA_BUFFER_LENGTH];
|
|
char buf1[FTOA_BUFFER_LENGTH];
|
|
char buf2[FTOA_BUFFER_LENGTH];
|
|
char buf3[FTOA_BUFFER_LENGTH];
|
|
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;
|
|
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
ftoa(thrDefault, buf0),
|
|
ftoa(rollDefault, buf1),
|
|
ftoa(pitchDefault, buf2),
|
|
ftoa(yawDefault, buf3));
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
ftoa(thr, buf0),
|
|
ftoa(roll, buf1),
|
|
ftoa(pitch, buf2),
|
|
ftoa(yaw, buf3));
|
|
}
|
|
}
|
|
#endif // USE_QUAD_MIXER_ONLY
|
|
|
|
static void cliMotorMix(const char *cmdName, char *cmdline)
|
|
{
|
|
#ifdef USE_QUAD_MIXER_ONLY
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
#else
|
|
int check = 0;
|
|
uint8_t len;
|
|
const char *ptr;
|
|
|
|
if (isEmpty(cmdline)) {
|
|
printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, 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) {
|
|
cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
|
|
break;
|
|
}
|
|
if (strncasecmp(ptr, mixerNames[i], len) == 0) {
|
|
mixerLoadMix(i, customMotorMixerMutable(0));
|
|
cliPrintLinef("Loaded %s", mixerNames[i]);
|
|
cliMotorMix(cmdName, "");
|
|
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) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
} else {
|
|
printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL, NULL);
|
|
}
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_SUPPORTED_MOTORS - 1);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void printRxRange(dumpFlags_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs, const char *headingStr)
|
|
{
|
|
const char *format = "rxrange %u %u %u";
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
|
|
bool equalsDefault = false;
|
|
if (defaultChannelRangeConfigs) {
|
|
equalsDefault = !memcmp(&channelRangeConfigs[i], &defaultChannelRangeConfigs[i], sizeof(channelRangeConfigs[i]));
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
defaultChannelRangeConfigs[i].min,
|
|
defaultChannelRangeConfigs[i].max
|
|
);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
channelRangeConfigs[i].min,
|
|
channelRangeConfigs[i].max
|
|
);
|
|
}
|
|
}
|
|
|
|
static void cliRxRange(const char *cmdName, char *cmdline)
|
|
{
|
|
const char *format = "rxrange %u %u %u";
|
|
int i, validArgumentCount = 0;
|
|
const char *ptr;
|
|
|
|
if (isEmpty(cmdline)) {
|
|
printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL, NULL);
|
|
} else if (strcasecmp(cmdline, "reset") == 0) {
|
|
resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
|
|
} else {
|
|
ptr = cmdline;
|
|
i = atoi(ptr);
|
|
if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
|
|
int rangeMin = PWM_PULSE_MIN, rangeMax = PWM_PULSE_MAX;
|
|
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
rangeMin = atoi(ptr);
|
|
validArgumentCount++;
|
|
}
|
|
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
rangeMax = atoi(ptr);
|
|
validArgumentCount++;
|
|
}
|
|
|
|
if (validArgumentCount != 2) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
} else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
|
|
cliShowArgumentRangeError(cmdName, "range min/max", PWM_PULSE_MIN, PWM_PULSE_MAX);
|
|
} else {
|
|
rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
|
|
channelRangeConfig->min = rangeMin;
|
|
channelRangeConfig->max = rangeMax;
|
|
cliDumpPrintLinef(0, false, format,
|
|
i,
|
|
channelRangeConfig->min,
|
|
channelRangeConfig->max
|
|
);
|
|
|
|
}
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, "CHANNEL", 0, NON_AUX_CHANNEL_COUNT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_LED_STRIP_STATUS_MODE
|
|
static void printLed(dumpFlags_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs, const char *headingStr)
|
|
{
|
|
const char *format = "led %u %s";
|
|
char ledConfigBuffer[20];
|
|
char ledConfigDefaultBuffer[20];
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (uint32_t i = 0; i < LED_STRIP_MAX_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;
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
|
|
}
|
|
}
|
|
|
|
static void cliLed(const char *cmdName, char *cmdline)
|
|
{
|
|
const char *format = "led %u %s";
|
|
char ledConfigBuffer[20];
|
|
int i;
|
|
const char *ptr;
|
|
|
|
if (isEmpty(cmdline)) {
|
|
printLed(DUMP_MASTER, ledStripStatusModeConfig()->ledConfigs, NULL, NULL);
|
|
} else {
|
|
ptr = cmdline;
|
|
i = atoi(ptr);
|
|
if (i >= 0 && i < LED_STRIP_MAX_LENGTH) {
|
|
ptr = nextArg(cmdline);
|
|
if (parseLedStripConfig(i, ptr)) {
|
|
generateLedConfig((ledConfig_t *)&ledStripStatusModeConfig()->ledConfigs[i], ledConfigBuffer, sizeof(ledConfigBuffer));
|
|
cliDumpPrintLinef(0, false, format, i, ledConfigBuffer);
|
|
} else {
|
|
cliShowParseError(cmdName);
|
|
}
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_STRIP_MAX_LENGTH - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void printColor(dumpFlags_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors, const char *headingStr)
|
|
{
|
|
const char *format = "color %u %d,%u,%u";
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
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 = !memcmp(color, colorDefault, sizeof(*color));
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
|
|
}
|
|
}
|
|
|
|
static void cliColor(const char *cmdName, char *cmdline)
|
|
{
|
|
const char *format = "color %u %d,%u,%u";
|
|
if (isEmpty(cmdline)) {
|
|
printColor(DUMP_MASTER, ledStripStatusModeConfig()->colors, NULL, NULL);
|
|
} else {
|
|
const char *ptr = cmdline;
|
|
const int i = atoi(ptr);
|
|
if (i < LED_CONFIGURABLE_COLOR_COUNT) {
|
|
ptr = nextArg(cmdline);
|
|
if (parseColor(i, ptr)) {
|
|
const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
|
|
cliDumpPrintLinef(0, false, format, i, color->h, color->s, color->v);
|
|
} else {
|
|
cliShowParseError(cmdName);
|
|
}
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, "INDEX", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void printModeColor(dumpFlags_t dumpMask, const ledStripStatusModeConfig_t *ledStripStatusModeConfig, const ledStripStatusModeConfig_t *defaultLedStripConfig, const char *headingStr)
|
|
{
|
|
const char *format = "mode_color %u %u %u";
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
|
|
for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
|
|
int colorIndex = ledStripStatusModeConfig->modeColors[i].color[j];
|
|
bool equalsDefault = false;
|
|
if (defaultLedStripConfig) {
|
|
int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
|
|
equalsDefault = colorIndex == colorIndexDefault;
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
|
|
}
|
|
}
|
|
|
|
for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
|
|
const int colorIndex = ledStripStatusModeConfig->specialColors.color[j];
|
|
bool equalsDefault = false;
|
|
if (defaultLedStripConfig) {
|
|
const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
|
|
equalsDefault = colorIndex == colorIndexDefault;
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
|
|
}
|
|
|
|
const int ledStripAuxChannel = ledStripStatusModeConfig->ledstrip_aux_channel;
|
|
bool equalsDefault = false;
|
|
if (defaultLedStripConfig) {
|
|
const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
|
|
equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
|
|
}
|
|
|
|
static void cliModeColor(const char *cmdName, char *cmdline)
|
|
{
|
|
if (isEmpty(cmdline)) {
|
|
printModeColor(DUMP_MASTER, ledStripStatusModeConfig(), NULL, NULL);
|
|
} else {
|
|
enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
|
|
int args[ARGS_COUNT];
|
|
int argNo = 0;
|
|
char *saveptr;
|
|
const char* ptr = strtok_r(cmdline, " ", &saveptr);
|
|
while (ptr && argNo < ARGS_COUNT) {
|
|
args[argNo++] = atoi(ptr);
|
|
ptr = strtok_r(NULL, " ", &saveptr);
|
|
}
|
|
|
|
if (ptr != NULL || argNo != ARGS_COUNT) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
return;
|
|
}
|
|
|
|
int modeIdx = args[MODE];
|
|
int funIdx = args[FUNCTION];
|
|
int color = args[COLOR];
|
|
if (!setModeColor(modeIdx, funIdx, color)) {
|
|
cliShowParseError(cmdName);
|
|
return;
|
|
}
|
|
// values are validated
|
|
cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SERVOS
|
|
static void printServo(dumpFlags_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams, const char *headingStr)
|
|
{
|
|
// print out servo settings
|
|
const char *format = "servo %u %d %d %d %d %d";
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
|
const servoParam_t *servoConf = &servoParams[i];
|
|
bool equalsDefault = false;
|
|
if (defaultServoParams) {
|
|
const servoParam_t *defaultServoConf = &defaultServoParams[i];
|
|
equalsDefault = !memcmp(servoConf, defaultServoConf, sizeof(*servoConf));
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
defaultServoConf->min,
|
|
defaultServoConf->max,
|
|
defaultServoConf->middle,
|
|
defaultServoConf->rate,
|
|
defaultServoConf->forwardFromChannel
|
|
);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
servoConf->min,
|
|
servoConf->max,
|
|
servoConf->middle,
|
|
servoConf->rate,
|
|
servoConf->forwardFromChannel
|
|
);
|
|
}
|
|
// print servo directions
|
|
for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
|
const char *format = "smix reverse %d %d r";
|
|
const servoParam_t *servoConf = &servoParams[i];
|
|
const servoParam_t *servoConfDefault = &defaultServoParams[i];
|
|
if (defaultServoParams) {
|
|
bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
|
|
for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
|
|
equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
|
|
if (servoConfDefault->reversedSources & (1 << channel)) {
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
|
|
}
|
|
if (servoConf->reversedSources & (1 << channel)) {
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
|
|
}
|
|
}
|
|
} else {
|
|
for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
|
|
if (servoConf->reversedSources & (1 << channel)) {
|
|
cliDumpPrintLinef(dumpMask, true, format, i , channel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cliServo(const char *cmdName, char *cmdline)
|
|
{
|
|
const char *format = "servo %u %d %d %d %d %d";
|
|
enum { SERVO_ARGUMENT_COUNT = 6 };
|
|
int16_t arguments[SERVO_ARGUMENT_COUNT];
|
|
|
|
servoParam_t *servo;
|
|
|
|
int i;
|
|
char *ptr;
|
|
|
|
if (isEmpty(cmdline)) {
|
|
printServo(DUMP_MASTER, servoParams(0), NULL, 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) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
return;
|
|
}
|
|
|
|
arguments[validArgumentCount++] = atoi(ptr);
|
|
|
|
do {
|
|
ptr++;
|
|
} while (*ptr >= '0' && *ptr <= '9');
|
|
} else if (*ptr == ' ') {
|
|
ptr++;
|
|
} else {
|
|
cliShowParseError(cmdName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
enum {INDEX = 0, MIN, MAX, MIDDLE, 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) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
return;
|
|
}
|
|
|
|
servo = servoParamsMutable(i);
|
|
|
|
if (
|
|
arguments[MIN] < PWM_SERVO_MIN || arguments[MIN] > PWM_SERVO_MAX ||
|
|
arguments[MAX] < PWM_SERVO_MIN || arguments[MAX] > PWM_SERVO_MAX ||
|
|
arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
|
|
arguments[MIN] > arguments[MAX] ||
|
|
arguments[RATE] < -100 || arguments[RATE] > 100 ||
|
|
arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
|
|
) {
|
|
cliShowArgumentRangeError(cmdName, NULL, 0, 0);
|
|
return;
|
|
}
|
|
|
|
servo->min = arguments[MIN];
|
|
servo->max = arguments[MAX];
|
|
servo->middle = arguments[MIDDLE];
|
|
servo->rate = arguments[RATE];
|
|
servo->forwardFromChannel = arguments[FORWARD];
|
|
|
|
cliDumpPrintLinef(0, false, format,
|
|
i,
|
|
servo->min,
|
|
servo->max,
|
|
servo->middle,
|
|
servo->rate,
|
|
servo->forwardFromChannel
|
|
);
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SERVOS
|
|
static void printServoMix(dumpFlags_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers, const char *headingStr)
|
|
{
|
|
const char *format = "smix %d %d %d %d %d %d %d %d";
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
|
|
const servoMixer_t customServoMixer = customServoMixers[i];
|
|
if (customServoMixer.rate == 0) {
|
|
break;
|
|
}
|
|
|
|
bool equalsDefault = false;
|
|
if (defaultCustomServoMixers) {
|
|
servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
|
|
equalsDefault = !memcmp(&customServoMixer, &customServoMixerDefault, sizeof(customServoMixer));
|
|
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
customServoMixerDefault.targetChannel,
|
|
customServoMixerDefault.inputSource,
|
|
customServoMixerDefault.rate,
|
|
customServoMixerDefault.speed,
|
|
customServoMixerDefault.min,
|
|
customServoMixerDefault.max,
|
|
customServoMixerDefault.box
|
|
);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
customServoMixer.targetChannel,
|
|
customServoMixer.inputSource,
|
|
customServoMixer.rate,
|
|
customServoMixer.speed,
|
|
customServoMixer.min,
|
|
customServoMixer.max,
|
|
customServoMixer.box
|
|
);
|
|
}
|
|
}
|
|
|
|
static void cliServoMix(const char *cmdName, char *cmdline)
|
|
{
|
|
int args[8], check = 0;
|
|
int len = strlen(cmdline);
|
|
|
|
if (len == 0) {
|
|
printServoMix(DUMP_MASTER, customServoMixers(0), NULL, NULL);
|
|
} else if (strncasecmp(cmdline, "reset", 5) == 0) {
|
|
// erase custom mixer
|
|
memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
|
|
for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
|
|
servoParamsMutable(i)->reversedSources = 0;
|
|
}
|
|
} else if (strncasecmp(cmdline, "load", 4) == 0) {
|
|
const char *ptr = nextArg(cmdline);
|
|
if (ptr) {
|
|
len = strlen(ptr);
|
|
for (uint32_t i = 0; ; i++) {
|
|
if (mixerNames[i] == NULL) {
|
|
cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
|
|
break;
|
|
}
|
|
if (strncasecmp(ptr, mixerNames[i], len) == 0) {
|
|
servoMixerLoadMix(i);
|
|
cliPrintLinef("Loaded %s", mixerNames[i]);
|
|
cliServoMix(cmdName, "");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (strncasecmp(cmdline, "reverse", 7) == 0) {
|
|
enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
|
|
char *ptr = strchr(cmdline, ' ');
|
|
|
|
if (ptr == NULL) {
|
|
cliPrintf("s");
|
|
for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
|
|
cliPrintf("\ti%d", inputSource);
|
|
cliPrintLinefeed();
|
|
|
|
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");
|
|
}
|
|
cliPrintLinefeed();
|
|
}
|
|
return;
|
|
}
|
|
|
|
char *saveptr;
|
|
ptr = strtok_r(ptr, " ", &saveptr);
|
|
while (ptr != NULL && check < ARGS_COUNT - 1) {
|
|
args[check++] = atoi(ptr);
|
|
ptr = strtok_r(NULL, " ", &saveptr);
|
|
}
|
|
|
|
if (ptr == NULL || check != ARGS_COUNT - 1) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
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 {
|
|
cliShowArgumentRangeError(cmdName, "servo", 0, MAX_SUPPORTED_SERVOS);
|
|
return;
|
|
}
|
|
|
|
cliServoMix(cmdName, "reverse");
|
|
} else {
|
|
enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
|
|
char *saveptr;
|
|
char *ptr = strtok_r(cmdline, " ", &saveptr);
|
|
while (ptr != NULL && check < ARGS_COUNT) {
|
|
args[check++] = atoi(ptr);
|
|
ptr = strtok_r(NULL, " ", &saveptr);
|
|
}
|
|
|
|
if (ptr != NULL || check != ARGS_COUNT) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
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] >= -100 && args[RATE] <= 100 &&
|
|
args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
|
|
args[MIN] >= 0 && args[MIN] <= 100 &&
|
|
args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
|
|
args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
|
|
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];
|
|
customServoMixersMutable(i)->box = args[BOX];
|
|
cliServoMix(cmdName, "");
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, NULL, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SDCARD
|
|
|
|
static void cliWriteBytes(const uint8_t *buffer, int count)
|
|
{
|
|
while (count > 0) {
|
|
cliWrite(*buffer);
|
|
buffer++;
|
|
count--;
|
|
}
|
|
}
|
|
|
|
static void cliSdInfo(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
cliPrint("SD card: ");
|
|
|
|
if (sdcardConfig()->mode == SDCARD_MODE_NONE) {
|
|
cliPrintLine("Not configured");
|
|
|
|
return;
|
|
}
|
|
|
|
if (!sdcard_isInserted()) {
|
|
cliPrintLine("None inserted");
|
|
return;
|
|
}
|
|
|
|
if (!sdcard_isFunctional() || !sdcard_isInitialized()) {
|
|
cliPrintLine("Startup failed");
|
|
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;
|
|
}
|
|
cliPrintLinefeed();
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef USE_FLASH_CHIP
|
|
static void cliFlashInfo(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
const flashGeometry_t *layout = flashGetGeometry();
|
|
|
|
cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u JEDEC ID=0x%08x",
|
|
layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, layout->jedecId);
|
|
|
|
for (uint8_t index = 0; index < FLASH_MAX_PARTITIONS; index++) {
|
|
const flashPartition_t *partition;
|
|
if (index == 0) {
|
|
cliPrintLine("Partitions:");
|
|
}
|
|
partition = flashPartitionFindByIndex(index);
|
|
if (!partition) {
|
|
break;
|
|
}
|
|
cliPrintLinef(" %d: %s %u %u", index, flashPartitionGetTypeName(partition->type), partition->startSector, partition->endSector);
|
|
}
|
|
#ifdef USE_FLASHFS
|
|
const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
|
|
|
|
cliPrintLinef("FlashFS size=%u, usedSize=%u",
|
|
FLASH_PARTITION_SECTOR_COUNT(flashPartition) * layout->sectorSize,
|
|
flashfsGetOffset()
|
|
);
|
|
#endif
|
|
}
|
|
#endif // USE_FLASH_CHIP
|
|
|
|
#if defined(USE_FLASHFS) && defined(USE_FLASH_CHIP)
|
|
static void cliFlashErase(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
if (!flashfsIsSupported()) {
|
|
return;
|
|
}
|
|
|
|
#ifndef MINIMAL_CLI
|
|
uint32_t i = 0;
|
|
cliPrintLine("Erasing, please wait ... ");
|
|
#else
|
|
cliPrintLine("Erasing,");
|
|
#endif
|
|
|
|
cliWriterFlush();
|
|
flashfsEraseCompletely();
|
|
|
|
while (!flashfsIsReady()) {
|
|
#ifndef MINIMAL_CLI
|
|
cliPrintf(".");
|
|
if (i++ > 120) {
|
|
i=0;
|
|
cliPrintLinefeed();
|
|
}
|
|
|
|
cliWriterFlush();
|
|
#endif
|
|
delay(100);
|
|
}
|
|
beeper(BEEPER_BLACKBOX_ERASE);
|
|
cliPrintLinefeed();
|
|
cliPrintLine("Done.");
|
|
}
|
|
|
|
#ifdef USE_FLASH_TOOLS
|
|
static void cliFlashVerify(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdline);
|
|
|
|
cliPrintLine("Verifying");
|
|
if (flashfsVerifyEntireFlash()) {
|
|
cliPrintLine("Success");
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "Failed");
|
|
}
|
|
}
|
|
|
|
static void cliFlashWrite(const char *cmdName, char *cmdline)
|
|
{
|
|
const uint32_t address = atoi(cmdline);
|
|
const char *text = strchr(cmdline, ' ');
|
|
|
|
if (!text) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
} else {
|
|
flashfsSeekAbs(address);
|
|
flashfsWrite((uint8_t*)text, strlen(text), true);
|
|
flashfsFlushSync();
|
|
|
|
cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
|
|
}
|
|
}
|
|
|
|
static void cliFlashRead(const char *cmdName, char *cmdline)
|
|
{
|
|
uint32_t address = atoi(cmdline);
|
|
|
|
const char *nextArg = strchr(cmdline, ' ');
|
|
|
|
if (!nextArg) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
} else {
|
|
uint32_t length = atoi(nextArg);
|
|
|
|
cliPrintLinef("Reading %u bytes at %u:", length, address);
|
|
|
|
uint8_t buffer[32];
|
|
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;
|
|
}
|
|
}
|
|
cliPrintLinefeed();
|
|
}
|
|
}
|
|
#endif // USE_FLASH_TOOLS
|
|
#endif // USE_FLASHFS
|
|
|
|
#ifdef USE_VTX_CONTROL
|
|
static void printVtx(dumpFlags_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault, const char *headingStr)
|
|
{
|
|
// print out vtx channel settings
|
|
const char *format = "vtx %u %u %u %u %u %u %u";
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
bool equalsDefault = false;
|
|
for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
|
|
const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
|
|
if (vtxConfigDefault) {
|
|
const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
|
|
equalsDefault = !memcmp(cac, cacDefault, sizeof(*cac));
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
cacDefault->auxChannelIndex,
|
|
cacDefault->band,
|
|
cacDefault->channel,
|
|
cacDefault->power,
|
|
MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
|
|
);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format,
|
|
i,
|
|
cac->auxChannelIndex,
|
|
cac->band,
|
|
cac->channel,
|
|
cac->power,
|
|
MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
|
|
);
|
|
}
|
|
}
|
|
|
|
static void cliVtx(const char *cmdName, char *cmdline)
|
|
{
|
|
const char *format = "vtx %u %u %u %u %u %u %u";
|
|
const char *ptr;
|
|
|
|
if (isEmpty(cmdline)) {
|
|
printVtx(DUMP_MASTER, vtxConfig(), NULL, NULL);
|
|
} else {
|
|
#ifdef USE_VTX_TABLE
|
|
const uint8_t maxBandIndex = vtxTableConfig()->bands;
|
|
const uint8_t maxChannelIndex = vtxTableConfig()->channels;
|
|
const uint8_t maxPowerIndex = vtxTableConfig()->powerLevels;
|
|
#else
|
|
const uint8_t maxBandIndex = VTX_TABLE_MAX_BANDS;
|
|
const uint8_t maxChannelIndex = VTX_TABLE_MAX_CHANNELS;
|
|
const uint8_t maxPowerIndex = VTX_TABLE_MAX_POWER_LEVELS;
|
|
#endif
|
|
ptr = cmdline;
|
|
int i = atoi(ptr++);
|
|
if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
|
|
vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
|
|
uint8_t validArgumentCount = 0;
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
int val = atoi(ptr);
|
|
if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
|
|
cac->auxChannelIndex = val;
|
|
validArgumentCount++;
|
|
}
|
|
}
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
int val = atoi(ptr);
|
|
if (val >= 0 && val <= maxBandIndex) {
|
|
cac->band = val;
|
|
validArgumentCount++;
|
|
}
|
|
}
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
int val = atoi(ptr);
|
|
if (val >= 0 && val <= maxChannelIndex) {
|
|
cac->channel = val;
|
|
validArgumentCount++;
|
|
}
|
|
}
|
|
ptr = nextArg(ptr);
|
|
if (ptr) {
|
|
int val = atoi(ptr);
|
|
if (val >= 0 && val <= maxPowerIndex) {
|
|
cac->power= val;
|
|
validArgumentCount++;
|
|
}
|
|
}
|
|
ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
|
|
|
|
if (validArgumentCount != 6) {
|
|
memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
} else {
|
|
cliDumpPrintLinef(0, false, format,
|
|
i,
|
|
cac->auxChannelIndex,
|
|
cac->band,
|
|
cac->channel,
|
|
cac->power,
|
|
MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
|
|
MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
|
|
);
|
|
}
|
|
} else {
|
|
cliShowArgumentRangeError(cmdName, "INDEX", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // VTX_CONTROL
|
|
|
|
#ifdef USE_VTX_TABLE
|
|
|
|
static char *formatVtxTableBandFrequency(const bool isFactory, const uint16_t *frequency, int channels)
|
|
{
|
|
static char freqbuf[5 * VTX_TABLE_MAX_CHANNELS + 8 + 1];
|
|
char freqtmp[5 + 1];
|
|
freqbuf[0] = 0;
|
|
strcat(freqbuf, isFactory ? " FACTORY" : " CUSTOM ");
|
|
for (int channel = 0; channel < channels; channel++) {
|
|
tfp_sprintf(freqtmp, " %4d", frequency[channel]);
|
|
strcat(freqbuf, freqtmp);
|
|
}
|
|
return freqbuf;
|
|
}
|
|
|
|
static const char *printVtxTableBand(dumpFlags_t dumpMask, int band, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
|
|
{
|
|
char *fmt = "vtxtable band %d %s %c%s";
|
|
bool equalsDefault = false;
|
|
|
|
if (defaultConfig) {
|
|
equalsDefault = true;
|
|
if (strcasecmp(currentConfig->bandNames[band], defaultConfig->bandNames[band])) {
|
|
equalsDefault = false;
|
|
}
|
|
if (currentConfig->bandLetters[band] != defaultConfig->bandLetters[band]) {
|
|
equalsDefault = false;
|
|
}
|
|
for (int channel = 0; channel < VTX_TABLE_MAX_CHANNELS; channel++) {
|
|
if (currentConfig->frequency[band][channel] != defaultConfig->frequency[band][channel]) {
|
|
equalsDefault = false;
|
|
}
|
|
}
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
char *freqbuf = formatVtxTableBandFrequency(defaultConfig->isFactoryBand[band], defaultConfig->frequency[band], defaultConfig->channels);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, band + 1, defaultConfig->bandNames[band], defaultConfig->bandLetters[band], freqbuf);
|
|
}
|
|
|
|
char *freqbuf = formatVtxTableBandFrequency(currentConfig->isFactoryBand[band], currentConfig->frequency[band], currentConfig->channels);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, fmt, band + 1, currentConfig->bandNames[band], currentConfig->bandLetters[band], freqbuf);
|
|
return headingStr;
|
|
}
|
|
|
|
static char *formatVtxTablePowerValues(const uint16_t *levels, int count)
|
|
{
|
|
// (max 4 digit + 1 space) per level
|
|
static char pwrbuf[5 * VTX_TABLE_MAX_POWER_LEVELS + 1];
|
|
char pwrtmp[5 + 1];
|
|
pwrbuf[0] = 0;
|
|
for (int pwrindex = 0; pwrindex < count; pwrindex++) {
|
|
tfp_sprintf(pwrtmp, " %d", levels[pwrindex]);
|
|
strcat(pwrbuf, pwrtmp);
|
|
}
|
|
return pwrbuf;
|
|
}
|
|
|
|
static const char *printVtxTablePowerValues(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
|
|
{
|
|
char *fmt = "vtxtable powervalues%s";
|
|
bool equalsDefault = false;
|
|
if (defaultConfig) {
|
|
equalsDefault = true;
|
|
for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
|
|
if (defaultConfig->powerValues[pwrindex] != currentConfig->powerValues[pwrindex]) {
|
|
equalsDefault = false;
|
|
}
|
|
}
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
char *pwrbuf = formatVtxTablePowerValues(defaultConfig->powerValues, VTX_TABLE_MAX_POWER_LEVELS);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
|
|
}
|
|
|
|
char *pwrbuf = formatVtxTablePowerValues(currentConfig->powerValues, currentConfig->powerLevels);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
|
|
return headingStr;
|
|
}
|
|
|
|
static char *formatVtxTablePowerLabels(const char labels[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1], int count)
|
|
{
|
|
static char pwrbuf[(VTX_TABLE_POWER_LABEL_LENGTH + 1) * VTX_TABLE_MAX_POWER_LEVELS + 1];
|
|
char pwrtmp[(VTX_TABLE_POWER_LABEL_LENGTH + 1) + 1];
|
|
pwrbuf[0] = 0;
|
|
for (int pwrindex = 0; pwrindex < count; pwrindex++) {
|
|
strcat(pwrbuf, " ");
|
|
strcpy(pwrtmp, labels[pwrindex]);
|
|
// trim trailing space
|
|
char *sp;
|
|
while ((sp = strchr(pwrtmp, ' '))) {
|
|
*sp = 0;
|
|
}
|
|
strcat(pwrbuf, pwrtmp);
|
|
}
|
|
return pwrbuf;
|
|
}
|
|
|
|
static const char *printVtxTablePowerLabels(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
|
|
{
|
|
char *fmt = "vtxtable powerlabels%s";
|
|
bool equalsDefault = false;
|
|
if (defaultConfig) {
|
|
equalsDefault = true;
|
|
for (int pwrindex = 0; pwrindex < VTX_TABLE_MAX_POWER_LEVELS; pwrindex++) {
|
|
if (strcasecmp(defaultConfig->powerLabels[pwrindex], currentConfig->powerLabels[pwrindex])) {
|
|
equalsDefault = false;
|
|
}
|
|
}
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
char *pwrbuf = formatVtxTablePowerLabels(defaultConfig->powerLabels, VTX_TABLE_MAX_POWER_LEVELS);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
|
|
}
|
|
|
|
char *pwrbuf = formatVtxTablePowerLabels(currentConfig->powerLabels, currentConfig->powerLevels);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, fmt, pwrbuf);
|
|
return headingStr;
|
|
}
|
|
|
|
static void printVtxTable(dumpFlags_t dumpMask, const vtxTableConfig_t *currentConfig, const vtxTableConfig_t *defaultConfig, const char *headingStr)
|
|
{
|
|
bool equalsDefault;
|
|
char *fmt;
|
|
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
|
|
// bands
|
|
equalsDefault = false;
|
|
fmt = "vtxtable bands %d";
|
|
if (defaultConfig) {
|
|
equalsDefault = (defaultConfig->bands == currentConfig->bands);
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->bands);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->bands);
|
|
|
|
// channels
|
|
equalsDefault = false;
|
|
fmt = "vtxtable channels %d";
|
|
if (defaultConfig) {
|
|
equalsDefault = (defaultConfig->channels == currentConfig->channels);
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->channels);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->channels);
|
|
|
|
// band
|
|
|
|
for (int band = 0; band < currentConfig->bands; band++) {
|
|
headingStr = printVtxTableBand(dumpMask, band, currentConfig, defaultConfig, headingStr);
|
|
}
|
|
|
|
// powerlevels
|
|
|
|
equalsDefault = false;
|
|
fmt = "vtxtable powerlevels %d";
|
|
if (defaultConfig) {
|
|
equalsDefault = (defaultConfig->powerLevels == currentConfig->powerLevels);
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, fmt, defaultConfig->powerLevels);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, fmt, currentConfig->powerLevels);
|
|
|
|
// powervalues
|
|
|
|
// powerlabels
|
|
headingStr = printVtxTablePowerValues(dumpMask, currentConfig, defaultConfig, headingStr);
|
|
headingStr = printVtxTablePowerLabels(dumpMask, currentConfig, defaultConfig, headingStr);
|
|
}
|
|
|
|
static void cliVtxTable(const char *cmdName, char *cmdline)
|
|
{
|
|
char *tok;
|
|
char *saveptr;
|
|
|
|
// Band number or nothing
|
|
tok = strtok_r(cmdline, " ", &saveptr);
|
|
|
|
if (!tok) {
|
|
printVtxTable(DUMP_MASTER | HIDE_UNUSED, vtxTableConfigMutable(), NULL, NULL);
|
|
return;
|
|
}
|
|
|
|
if (strcasecmp(tok, "bands") == 0) {
|
|
tok = strtok_r(NULL, " ", &saveptr);
|
|
int bands = atoi(tok);
|
|
if (bands < 0 || bands > VTX_TABLE_MAX_BANDS) {
|
|
cliShowArgumentRangeError(cmdName, "BAND COUNT", 0, VTX_TABLE_MAX_BANDS);
|
|
return;
|
|
}
|
|
if (bands < vtxTableConfigMutable()->bands) {
|
|
for (int i = bands; i < vtxTableConfigMutable()->bands; i++) {
|
|
vtxTableConfigClearBand(vtxTableConfigMutable(), i);
|
|
}
|
|
}
|
|
vtxTableConfigMutable()->bands = bands;
|
|
|
|
} else if (strcasecmp(tok, "channels") == 0) {
|
|
tok = strtok_r(NULL, " ", &saveptr);
|
|
|
|
int channels = atoi(tok);
|
|
if (channels < 0 || channels > VTX_TABLE_MAX_CHANNELS) {
|
|
cliShowArgumentRangeError(cmdName, "CHANNEL COUNT", 0, VTX_TABLE_MAX_CHANNELS);
|
|
return;
|
|
}
|
|
if (channels < vtxTableConfigMutable()->channels) {
|
|
for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
|
|
vtxTableConfigClearChannels(vtxTableConfigMutable(), i, channels);
|
|
}
|
|
}
|
|
vtxTableConfigMutable()->channels = channels;
|
|
|
|
} else if (strcasecmp(tok, "powerlevels") == 0) {
|
|
// Number of power levels
|
|
tok = strtok_r(NULL, " ", &saveptr);
|
|
if (tok) {
|
|
int levels = atoi(tok);
|
|
if (levels < 0 || levels > VTX_TABLE_MAX_POWER_LEVELS) {
|
|
cliShowArgumentRangeError(cmdName, "POWER LEVEL COUNT", 0, VTX_TABLE_MAX_POWER_LEVELS);
|
|
} else {
|
|
if (levels < vtxTableConfigMutable()->powerLevels) {
|
|
vtxTableConfigClearPowerValues(vtxTableConfigMutable(), levels);
|
|
vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), levels);
|
|
}
|
|
vtxTableConfigMutable()->powerLevels = levels;
|
|
}
|
|
} else {
|
|
// XXX Show current level count?
|
|
}
|
|
return;
|
|
|
|
} else if (strcasecmp(tok, "powervalues") == 0) {
|
|
// Power values
|
|
uint16_t power[VTX_TABLE_MAX_POWER_LEVELS];
|
|
int count;
|
|
int levels = vtxTableConfigMutable()->powerLevels;
|
|
|
|
memset(power, 0, sizeof(power));
|
|
|
|
for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
|
|
int value = atoi(tok);
|
|
power[count] = value;
|
|
}
|
|
|
|
// Check remaining tokens
|
|
|
|
if (count < levels) {
|
|
cliPrintErrorLinef(cmdName, "NOT ENOUGH VALUES (EXPECTED %d)", levels);
|
|
return;
|
|
} else if ((tok = strtok_r(NULL, " ", &saveptr))) {
|
|
cliPrintErrorLinef(cmdName, "TOO MANY VALUES (EXPECTED %d)", levels);
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < VTX_TABLE_MAX_POWER_LEVELS; i++) {
|
|
vtxTableConfigMutable()->powerValues[i] = power[i];
|
|
}
|
|
|
|
} else if (strcasecmp(tok, "powerlabels") == 0) {
|
|
// Power labels
|
|
char label[VTX_TABLE_MAX_POWER_LEVELS][VTX_TABLE_POWER_LABEL_LENGTH + 1];
|
|
int levels = vtxTableConfigMutable()->powerLevels;
|
|
int count;
|
|
for (count = 0; count < levels && (tok = strtok_r(NULL, " ", &saveptr)); count++) {
|
|
strncpy(label[count], tok, VTX_TABLE_POWER_LABEL_LENGTH);
|
|
for (unsigned i = 0; i < strlen(label[count]); i++) {
|
|
label[count][i] = toupper(label[count][i]);
|
|
}
|
|
}
|
|
|
|
// Check remaining tokens
|
|
|
|
if (count < levels) {
|
|
cliPrintErrorLinef(cmdName, "NOT ENOUGH LABELS (EXPECTED %d)", levels);
|
|
return;
|
|
} else if ((tok = strtok_r(NULL, " ", &saveptr))) {
|
|
cliPrintErrorLinef(cmdName, "TOO MANY LABELS (EXPECTED %d)", levels);
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[i], label[i], VTX_TABLE_POWER_LABEL_LENGTH);
|
|
}
|
|
} else if (strcasecmp(tok, "band") == 0) {
|
|
|
|
int bands = vtxTableConfigMutable()->bands;
|
|
|
|
tok = strtok_r(NULL, " ", &saveptr);
|
|
if (!tok) {
|
|
return;
|
|
}
|
|
|
|
int band = atoi(tok);
|
|
--band;
|
|
|
|
if (band < 0 || band >= bands) {
|
|
cliShowArgumentRangeError(cmdName, "BAND NUMBER", 1, bands);
|
|
return;
|
|
}
|
|
|
|
// Band name
|
|
tok = strtok_r(NULL, " ", &saveptr);
|
|
|
|
if (!tok) {
|
|
return;
|
|
}
|
|
|
|
char bandname[VTX_TABLE_BAND_NAME_LENGTH + 1];
|
|
memset(bandname, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
|
|
strncpy(bandname, tok, VTX_TABLE_BAND_NAME_LENGTH);
|
|
for (unsigned i = 0; i < strlen(bandname); i++) {
|
|
bandname[i] = toupper(bandname[i]);
|
|
}
|
|
|
|
// Band letter
|
|
tok = strtok_r(NULL, " ", &saveptr);
|
|
|
|
if (!tok) {
|
|
return;
|
|
}
|
|
|
|
char bandletter = toupper(tok[0]);
|
|
|
|
uint16_t bandfreq[VTX_TABLE_MAX_CHANNELS];
|
|
int channel = 0;
|
|
int channels = vtxTableConfigMutable()->channels;
|
|
bool isFactory = false;
|
|
|
|
for (channel = 0; channel < channels && (tok = strtok_r(NULL, " ", &saveptr)); channel++) {
|
|
if (channel == 0 && !isdigit(tok[0])) {
|
|
channel -= 1;
|
|
if (strcasecmp(tok, "FACTORY") == 0) {
|
|
isFactory = true;
|
|
} else if (strcasecmp(tok, "CUSTOM") == 0) {
|
|
isFactory = false;
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "INVALID FACTORY FLAG %s (EXPECTED FACTORY OR CUSTOM)", tok);
|
|
return;
|
|
}
|
|
}
|
|
int freq = atoi(tok);
|
|
if (freq < 0) {
|
|
cliPrintErrorLinef(cmdName, "INVALID FREQUENCY %s", tok);
|
|
return;
|
|
}
|
|
bandfreq[channel] = freq;
|
|
}
|
|
|
|
if (channel < channels) {
|
|
cliPrintErrorLinef(cmdName, "NOT ENOUGH FREQUENCIES (EXPECTED %d)", channels);
|
|
return;
|
|
} else if ((tok = strtok_r(NULL, " ", &saveptr))) {
|
|
cliPrintErrorLinef(cmdName, "TOO MANY FREQUENCIES (EXPECTED %d)", channels);
|
|
return;
|
|
}
|
|
|
|
vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band], bandname, VTX_TABLE_BAND_NAME_LENGTH);
|
|
vtxTableConfigMutable()->bandLetters[band] = bandletter;
|
|
|
|
for (int i = 0; i < channel; i++) {
|
|
vtxTableConfigMutable()->frequency[band][i] = bandfreq[i];
|
|
}
|
|
vtxTableConfigMutable()->isFactoryBand[band] = isFactory;
|
|
} else {
|
|
// Bad subcommand
|
|
cliPrintErrorLinef(cmdName, "INVALID SUBCOMMAND %s", tok);
|
|
}
|
|
}
|
|
|
|
static void cliVtxInfo(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdline);
|
|
|
|
// Display the available power levels
|
|
uint16_t levels[VTX_TABLE_MAX_POWER_LEVELS];
|
|
uint16_t powers[VTX_TABLE_MAX_POWER_LEVELS];
|
|
vtxDevice_t *vtxDevice = vtxCommonDevice();
|
|
if (vtxDevice) {
|
|
uint8_t level_count = vtxCommonGetVTXPowerLevels(vtxDevice, levels, powers);
|
|
|
|
if (level_count) {
|
|
for (int i = 0; i < level_count; i++) {
|
|
cliPrintLinef("level %d dBm, power %d mW", levels[i], powers[i]);
|
|
}
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "NO POWER VALUES DEFINED");
|
|
}
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "NO VTX");
|
|
}
|
|
}
|
|
#endif // USE_VTX_TABLE
|
|
|
|
#if defined(USE_SIMPLIFIED_TUNING)
|
|
static void applySimplifiedTuningAllProfiles(void)
|
|
{
|
|
for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
|
|
applySimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
|
|
}
|
|
}
|
|
|
|
static void cliSimplifiedTuning(const char *cmdName, char *cmdline)
|
|
{
|
|
if (strcasecmp(cmdline, "apply") == 0) {
|
|
applySimplifiedTuningAllProfiles();
|
|
|
|
cliPrintLine("Applied simplified tuning.");
|
|
} else if (strcasecmp(cmdline, "disable") == 0) {
|
|
for (unsigned pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
|
|
disableSimplifiedTuning(pidProfilesMutable(pidProfileIndex), gyroConfigMutable());
|
|
}
|
|
|
|
cliPrintLine("Disabled simplified tuning.");
|
|
} else {
|
|
cliShowParseError(cmdName);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void printCraftName(dumpFlags_t dumpMask, const pilotConfig_t *pilotConfig)
|
|
{
|
|
const bool equalsDefault = strlen(pilotConfig->craftName) == 0;
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, "\r\n# name: %s", equalsDefault ? emptyName : pilotConfig->craftName);
|
|
}
|
|
|
|
#if defined(USE_BOARD_INFO)
|
|
|
|
static void printBoardName(dumpFlags_t dumpMask)
|
|
{
|
|
if (!(dumpMask & DO_DIFF) || strlen(getBoardName())) {
|
|
cliPrintLinef("board_name %s", getBoardName());
|
|
}
|
|
}
|
|
|
|
static void cliBoardName(const char *cmdName, char *cmdline)
|
|
{
|
|
const unsigned int len = strlen(cmdline);
|
|
const char *boardName = getBoardName();
|
|
if (len > 0 && strlen(boardName) != 0 && boardInformationIsSet() && (len != strlen(boardName) || strncmp(boardName, cmdline, len))) {
|
|
cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "BOARD_NAME", boardName);
|
|
} else {
|
|
if (len > 0 && !configIsInCopy && setBoardName(cmdline)) {
|
|
boardInformationUpdated = true;
|
|
|
|
cliPrintHashLine("Set board_name.");
|
|
}
|
|
printBoardName(DUMP_ALL);
|
|
}
|
|
}
|
|
|
|
static void printManufacturerId(dumpFlags_t dumpMask)
|
|
{
|
|
if (!(dumpMask & DO_DIFF) || strlen(getManufacturerId())) {
|
|
cliPrintLinef("manufacturer_id %s", getManufacturerId());
|
|
}
|
|
}
|
|
|
|
static void cliManufacturerId(const char *cmdName, char *cmdline)
|
|
{
|
|
const unsigned int len = strlen(cmdline);
|
|
const char *manufacturerId = getManufacturerId();
|
|
if (len > 0 && boardInformationIsSet() && strlen(manufacturerId) != 0 && (len != strlen(manufacturerId) || strncmp(manufacturerId, cmdline, len))) {
|
|
cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "MANUFACTURER_ID", manufacturerId);
|
|
} else {
|
|
if (len > 0 && !configIsInCopy && setManufacturerId(cmdline)) {
|
|
boardInformationUpdated = true;
|
|
|
|
cliPrintHashLine("Set manufacturer_id.");
|
|
}
|
|
printManufacturerId(DUMP_ALL);
|
|
}
|
|
}
|
|
|
|
#if defined(USE_SIGNATURE)
|
|
static void writeSignature(char *signatureStr, const uint8_t *signature)
|
|
{
|
|
for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
|
|
tfp_sprintf(&signatureStr[2 * i], "%02x", signature[i]);
|
|
}
|
|
}
|
|
|
|
static void cliSignature(const char *cmdName, char *cmdline)
|
|
{
|
|
const int len = strlen(cmdline);
|
|
|
|
uint8_t signature[SIGNATURE_LENGTH] = {0};
|
|
if (len > 0) {
|
|
if (len != 2 * SIGNATURE_LENGTH) {
|
|
cliPrintErrorLinef(cmdName, "INVALID LENGTH: %d (EXPECTED: %d)", len, 2 * SIGNATURE_LENGTH);
|
|
|
|
return;
|
|
}
|
|
|
|
#define BLOCK_SIZE 2
|
|
for (unsigned i = 0; i < SIGNATURE_LENGTH; i++) {
|
|
char temp[BLOCK_SIZE + 1];
|
|
strncpy(temp, &cmdline[i * BLOCK_SIZE], BLOCK_SIZE);
|
|
temp[BLOCK_SIZE] = '\0';
|
|
char *end;
|
|
unsigned result = strtoul(temp, &end, 16);
|
|
if (end == &temp[BLOCK_SIZE]) {
|
|
signature[i] = result;
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "INVALID CHARACTER FOUND: %c", end[0]);
|
|
|
|
return;
|
|
}
|
|
}
|
|
#undef BLOCK_SIZE
|
|
}
|
|
|
|
char signatureStr[SIGNATURE_LENGTH * 2 + 1] = {0};
|
|
if (len > 0 && signatureIsSet() && memcmp(signature, getSignature(), SIGNATURE_LENGTH)) {
|
|
writeSignature(signatureStr, getSignature());
|
|
cliPrintErrorLinef(cmdName, ERROR_MESSAGE, "SIGNATURE", signatureStr);
|
|
} else {
|
|
if (len > 0 && !configIsInCopy && setSignature(signature)) {
|
|
signatureUpdated = true;
|
|
|
|
writeSignature(signatureStr, getSignature());
|
|
|
|
cliPrintHashLine("Set signature.");
|
|
} else if (signatureUpdated || signatureIsSet()) {
|
|
writeSignature(signatureStr, getSignature());
|
|
}
|
|
|
|
cliPrintLinef("signature %s", signatureStr);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#undef ERROR_MESSAGE
|
|
|
|
#endif // USE_BOARD_INFO
|
|
|
|
static void cliMcuId(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
cliPrintLinef("mcu_id %08x%08x%08x", U_ID_0, U_ID_1, U_ID_2);
|
|
}
|
|
|
|
static void printFeature(dumpFlags_t dumpMask, const uint32_t mask, const uint32_t defaultMask, const char *headingStr)
|
|
{
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) { // disabled features first
|
|
if (featureNames[i]) { //Skip unused
|
|
const char *format = "feature -%s";
|
|
const bool equalsDefault = (~defaultMask | mask) & (1U << i);
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1U << i), format, featureNames[i]);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
|
|
}
|
|
}
|
|
for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) { // enabled features
|
|
if (featureNames[i]) { //Skip unused
|
|
const char *format = "feature %s";
|
|
if (defaultMask & (1U << i)) {
|
|
cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1U << i), format, featureNames[i]);
|
|
}
|
|
if (mask & (1U << i)) {
|
|
const bool equalsDefault = (defaultMask | ~mask) & (1U << i);
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, featureNames[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void printFeatureList(const char* header, uint32_t mask, const char* delimiter, bool lineFeed)
|
|
{
|
|
if (header) {
|
|
cliPrint(header);
|
|
}
|
|
for (unsigned i = 0; i < ARRAYLEN(featureNames); i++) {
|
|
if (featureNames[i] && (mask & (1U << i))) {
|
|
cliPrintf("%s%s", i ? delimiter : "", featureNames[i]);
|
|
}
|
|
}
|
|
if (lineFeed) {
|
|
cliPrintLinefeed();
|
|
}
|
|
}
|
|
|
|
static void cliFeature(const char *cmdName, char *cmdline)
|
|
{
|
|
uint32_t len = strlen(cmdline);
|
|
const uint32_t mask = featureConfig()->enabledFeatures;
|
|
if (len == 0 // `feature`
|
|
|| strncasecmp(cmdline, "list", len) == 0) { // old `feature list` invocation
|
|
printFeatureList("Enabled: ", mask, " ", true);
|
|
printFeatureList("Available: ", ~mask & featuresSupportedByBuild, " ", true);
|
|
printFeatureList("Unavailable: ", ~featuresSupportedByBuild, " ", true);
|
|
} else {
|
|
bool remove = false;
|
|
if (cmdline[0] == '-') {
|
|
// remove feature
|
|
remove = true;
|
|
cmdline++; // skip over -
|
|
len--;
|
|
}
|
|
|
|
unsigned found = 0;
|
|
int featureIdx = -1;
|
|
for (unsigned i = 0; !found && i < ARRAYLEN(featureNames); i++) {
|
|
if (featureNames[i] && strncasecmp(cmdline, featureNames[i], len) == 0) {
|
|
found++;
|
|
featureIdx = i;
|
|
}
|
|
}
|
|
if (found == 1) {
|
|
uint32_t feature = 1U << featureIdx;
|
|
const char *verb;
|
|
if ((feature & featuresSupportedByBuild) == 0) {
|
|
verb = "Unavailable";
|
|
} else if (remove) {
|
|
featureConfigClear(feature);
|
|
verb = (mask & feature) ? "Disabled" : "AlreadyDisabled";
|
|
} else {
|
|
featureConfigSet(feature);
|
|
verb = (mask & feature) ? "AlreadyEnabled" : "Enabled";
|
|
}
|
|
cliPrintLinef("%s %s", verb, featureNames[featureIdx]);
|
|
} else if (found > 1) {
|
|
cliPrintErrorLinef(cmdName, "Multiple features match %s", cmdline);
|
|
} else /* found <= 0 */ {
|
|
cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(USE_BEEPER)
|
|
static void printBeeper(dumpFlags_t dumpMask, const uint32_t offFlags, const uint32_t offFlagsDefault, const char *name, const uint32_t allowedFlags, const char *headingStr)
|
|
{
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
const uint8_t beeperCount = beeperTableEntryCount();
|
|
for (int32_t i = 0; i < beeperCount - 1; i++) {
|
|
const uint32_t beeperModeMask = beeperModeMaskForTableIndex(i);
|
|
if (beeperModeMask & allowedFlags) {
|
|
const char *formatOff = "%s -%s";
|
|
const char *formatOn = "%s %s";
|
|
const bool equalsDefault = ~(offFlags ^ offFlagsDefault) & beeperModeMask;
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOn : formatOff, name, beeperNameForTableIndex(i));
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, offFlags & beeperModeMask ? formatOff : formatOn, name, beeperNameForTableIndex(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void processBeeperCommand(const char *cmdName, char *cmdline, uint32_t *offFlags, const uint32_t allowedFlags)
|
|
{
|
|
uint32_t len = strlen(cmdline);
|
|
uint8_t beeperCount = beeperTableEntryCount();
|
|
|
|
if (len == 0) {
|
|
cliPrintf("Disabled:");
|
|
for (int32_t i = 0; ; i++) {
|
|
if (i == beeperCount - 1) {
|
|
if (*offFlags == 0)
|
|
cliPrint(" none");
|
|
break;
|
|
}
|
|
|
|
if (beeperModeMaskForTableIndex(i) & *offFlags)
|
|
cliPrintf(" %s", beeperNameForTableIndex(i));
|
|
}
|
|
cliPrintLinefeed();
|
|
} else if (strncasecmp(cmdline, "list", len) == 0) {
|
|
cliPrint("Available:");
|
|
for (uint32_t i = 0; i < beeperCount; i++) {
|
|
if (beeperModeMaskForTableIndex(i) & allowedFlags) {
|
|
cliPrintf(" %s", beeperNameForTableIndex(i));
|
|
}
|
|
}
|
|
cliPrintLinefeed();
|
|
} 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) {
|
|
cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
|
|
break;
|
|
}
|
|
if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0 && beeperModeMaskForTableIndex(i) & (allowedFlags | BEEPER_GET_FLAG(BEEPER_ALL))) {
|
|
if (remove) { // beeper off
|
|
if (i == BEEPER_ALL - 1) {
|
|
*offFlags = allowedFlags;
|
|
} else {
|
|
*offFlags |= beeperModeMaskForTableIndex(i);
|
|
}
|
|
cliPrint("Disabled");
|
|
}
|
|
else { // beeper on
|
|
if (i == BEEPER_ALL - 1) {
|
|
*offFlags = 0;
|
|
} else {
|
|
*offFlags &= ~beeperModeMaskForTableIndex(i);
|
|
}
|
|
cliPrint("Enabled");
|
|
}
|
|
cliPrintLinef(" %s", beeperNameForTableIndex(i));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(USE_DSHOT)
|
|
static void cliBeacon(const char *cmdName, char *cmdline)
|
|
{
|
|
processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->dshotBeaconOffFlags), DSHOT_BEACON_ALLOWED_MODES);
|
|
}
|
|
#endif
|
|
|
|
static void cliBeeper(const char *cmdName, char *cmdline)
|
|
{
|
|
processBeeperCommand(cmdName, cmdline, &(beeperConfigMutable()->beeper_off_flags), BEEPER_ALLOWED_MODES);
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_RX_BIND)
|
|
static void cliRxBind(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdline);
|
|
if (!startRxBind()) {
|
|
cliPrintErrorLinef(cmdName, "Not supported.");
|
|
} else {
|
|
cliPrintLinef("Binding...");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void printMap(dumpFlags_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig, const char *headingStr)
|
|
{
|
|
bool equalsDefault = true;
|
|
char buf[16];
|
|
char bufDefault[16];
|
|
uint32_t i;
|
|
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; 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';
|
|
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
const char *formatMap = "map %s";
|
|
if (defaultRxConfig) {
|
|
bufDefault[i] = '\0';
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
|
|
}
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
|
|
}
|
|
|
|
static void cliMap(const char *cmdName, char *cmdline)
|
|
{
|
|
uint32_t i;
|
|
char buf[RX_MAPPABLE_CHANNEL_COUNT + 1];
|
|
|
|
uint32_t len = strlen(cmdline);
|
|
if (len == RX_MAPPABLE_CHANNEL_COUNT) {
|
|
|
|
for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
|
|
buf[i] = toupper((unsigned char)cmdline[i]);
|
|
}
|
|
buf[i] = '\0';
|
|
|
|
for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
|
|
buf[i] = toupper((unsigned char)cmdline[i]);
|
|
|
|
if (strchr(rcChannelLetters, buf[i]) && !strchr(buf + i + 1, buf[i]))
|
|
continue;
|
|
|
|
cliShowParseError(cmdName);
|
|
return;
|
|
}
|
|
parseRcChannels(buf, rxConfigMutable());
|
|
} else if (len > 0) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
|
|
buf[rxConfig()->rcmap[i]] = rcChannelLetters[i];
|
|
}
|
|
|
|
buf[i] = '\0';
|
|
cliPrintLinef("map %s", buf);
|
|
}
|
|
|
|
static char *skipSpace(char *buffer)
|
|
{
|
|
while (*(buffer) == ' ') {
|
|
buffer++;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static char *checkCommand(char *cmdline, const char *command)
|
|
{
|
|
if (!strncasecmp(cmdline, command, strlen(command)) // command names match
|
|
&& (isspace((unsigned)cmdline[strlen(command)]) || cmdline[strlen(command)] == 0)) {
|
|
return skipSpace(cmdline + strlen(command) + 1);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void cliRebootEx(rebootTarget_e rebootTarget)
|
|
{
|
|
cliPrint("\r\nRebooting");
|
|
cliWriterFlush();
|
|
waitForSerialPortToFinishTransmitting(cliPort);
|
|
motorShutdown();
|
|
|
|
switch (rebootTarget) {
|
|
case REBOOT_TARGET_BOOTLOADER_ROM:
|
|
systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
|
|
|
|
break;
|
|
#if defined(USE_FLASH_BOOT_LOADER)
|
|
case REBOOT_TARGET_BOOTLOADER_FLASH:
|
|
systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
|
|
|
|
break;
|
|
#endif
|
|
case REBOOT_TARGET_FIRMWARE:
|
|
default:
|
|
systemReset();
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cliReboot(void)
|
|
{
|
|
cliRebootEx(REBOOT_TARGET_FIRMWARE);
|
|
}
|
|
|
|
static void cliBootloader(const char *cmdName, char *cmdline)
|
|
{
|
|
rebootTarget_e rebootTarget;
|
|
if (
|
|
#if !defined(USE_FLASH_BOOT_LOADER)
|
|
isEmpty(cmdline) ||
|
|
#endif
|
|
strncasecmp(cmdline, "rom", 3) == 0) {
|
|
rebootTarget = REBOOT_TARGET_BOOTLOADER_ROM;
|
|
|
|
cliPrintHashLine("restarting in ROM bootloader mode");
|
|
#if defined(USE_FLASH_BOOT_LOADER)
|
|
} else if (isEmpty(cmdline) || strncasecmp(cmdline, "flash", 5) == 0) {
|
|
rebootTarget = REBOOT_TARGET_BOOTLOADER_FLASH;
|
|
|
|
cliPrintHashLine("restarting in flash bootloader mode");
|
|
#endif
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "Invalid option");
|
|
|
|
return;
|
|
}
|
|
|
|
cliRebootEx(rebootTarget);
|
|
}
|
|
|
|
static void cliExitCmd(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
|
|
const bool reboot = strcasecmp(cmdline, "noreboot") != 0;
|
|
if (reboot) {
|
|
cliPrintHashLine("leaving CLI mode, unsaved changes lost");
|
|
} else {
|
|
cliPrintHashLine("leaving CLI mode, no reboot");
|
|
}
|
|
cliExit(reboot);
|
|
}
|
|
|
|
#ifdef USE_GPS
|
|
static void cliGpsPassthrough(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
if (!gpsPassthrough(cliPort)) {
|
|
cliPrintErrorLinef(cmdName, "GPS forwarding failed");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
|
|
static void cliPrintGyroRegisters(uint8_t whichSensor)
|
|
{
|
|
cliPrintLinef("# WHO_AM_I 0x%X", gyroReadRegister(whichSensor, MPU_RA_WHO_AM_I));
|
|
cliPrintLinef("# CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_CONFIG));
|
|
cliPrintLinef("# GYRO_CONFIG 0x%X", gyroReadRegister(whichSensor, MPU_RA_GYRO_CONFIG));
|
|
}
|
|
|
|
static void cliDumpGyroRegisters(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
for (int i = 0; i < GYRO_COUNT; i++) {
|
|
if (gyroConfig()->gyro_enabled_bitmask & GYRO_MASK(i)) {
|
|
cliPrintLinef("\r\n# Gyro %d", i + 1);
|
|
cliPrintGyroRegisters(i); // assuming this takes a 0-based gyro index
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int parseOutputIndex(const char *cmdName, char *pch, bool allowAllEscs)
|
|
{
|
|
int outputIndex = atoi(pch);
|
|
if ((outputIndex >= 0) && (outputIndex < getMotorCount())) {
|
|
cliPrintLinef("Using output %d.", outputIndex);
|
|
} else if (allowAllEscs && outputIndex == ALL_MOTORS) {
|
|
cliPrintLinef("Using all outputs.");
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "INVALID OUTPUT NUMBER. RANGE: 0 - %d.", getMotorCount() - 1);
|
|
|
|
return -1;
|
|
}
|
|
|
|
return outputIndex;
|
|
}
|
|
|
|
#if defined(USE_DSHOT)
|
|
#if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
|
|
|
|
#define ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE 15
|
|
#define ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE 21
|
|
#define ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE 64
|
|
|
|
enum {
|
|
ESC_INFO_KISS_V1,
|
|
ESC_INFO_KISS_V2,
|
|
ESC_INFO_BLHELI32
|
|
};
|
|
|
|
#define ESC_INFO_VERSION_POSITION 12
|
|
|
|
static void printEscInfo(const char *cmdName, const uint8_t *escInfoBuffer, uint8_t bytesRead)
|
|
{
|
|
bool escInfoReceived = false;
|
|
if (bytesRead > ESC_INFO_VERSION_POSITION) {
|
|
uint8_t escInfoVersion;
|
|
uint8_t frameLength;
|
|
if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 254) {
|
|
escInfoVersion = ESC_INFO_BLHELI32;
|
|
frameLength = ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE;
|
|
} else if (escInfoBuffer[ESC_INFO_VERSION_POSITION] == 255) {
|
|
escInfoVersion = ESC_INFO_KISS_V2;
|
|
frameLength = ESC_INFO_KISS_V2_EXPECTED_FRAME_SIZE;
|
|
} else {
|
|
escInfoVersion = ESC_INFO_KISS_V1;
|
|
frameLength = ESC_INFO_KISS_V1_EXPECTED_FRAME_SIZE;
|
|
}
|
|
|
|
if (bytesRead == frameLength) {
|
|
escInfoReceived = true;
|
|
|
|
if (calculateCrc8(escInfoBuffer, frameLength - 1) == escInfoBuffer[frameLength - 1]) {
|
|
uint8_t firmwareVersion = 0;
|
|
uint8_t firmwareSubVersion = 0;
|
|
uint8_t escType = 0;
|
|
switch (escInfoVersion) {
|
|
case ESC_INFO_KISS_V1:
|
|
firmwareVersion = escInfoBuffer[12];
|
|
firmwareSubVersion = (escInfoBuffer[13] & 0x1f) + 97;
|
|
escType = (escInfoBuffer[13] & 0xe0) >> 5;
|
|
|
|
break;
|
|
case ESC_INFO_KISS_V2:
|
|
firmwareVersion = escInfoBuffer[13];
|
|
firmwareSubVersion = escInfoBuffer[14];
|
|
escType = escInfoBuffer[15];
|
|
|
|
break;
|
|
case ESC_INFO_BLHELI32:
|
|
firmwareVersion = escInfoBuffer[13];
|
|
firmwareSubVersion = escInfoBuffer[14];
|
|
escType = escInfoBuffer[15];
|
|
|
|
break;
|
|
}
|
|
|
|
cliPrint("ESC Type: ");
|
|
switch (escInfoVersion) {
|
|
case ESC_INFO_KISS_V1:
|
|
case ESC_INFO_KISS_V2:
|
|
switch (escType) {
|
|
case 1:
|
|
cliPrintLine("KISS8A");
|
|
|
|
break;
|
|
case 2:
|
|
cliPrintLine("KISS16A");
|
|
|
|
break;
|
|
case 3:
|
|
cliPrintLine("KISS24A");
|
|
|
|
break;
|
|
case 5:
|
|
cliPrintLine("KISS Ultralite");
|
|
|
|
break;
|
|
default:
|
|
cliPrintLine("unknown");
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case ESC_INFO_BLHELI32:
|
|
{
|
|
char *escType = (char *)(escInfoBuffer + 31);
|
|
escType[32] = 0;
|
|
cliPrintLine(escType);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
cliPrint("MCU Serial No: 0x");
|
|
for (int i = 0; i < 12; i++) {
|
|
if (i && (i % 3 == 0)) {
|
|
cliPrint("-");
|
|
}
|
|
cliPrintf("%02x", escInfoBuffer[i]);
|
|
}
|
|
cliPrintLinefeed();
|
|
|
|
switch (escInfoVersion) {
|
|
case ESC_INFO_KISS_V1:
|
|
case ESC_INFO_KISS_V2:
|
|
cliPrintLinef("Firmware Version: %d.%02d%c", firmwareVersion / 100, firmwareVersion % 100, (char)firmwareSubVersion);
|
|
|
|
break;
|
|
case ESC_INFO_BLHELI32:
|
|
cliPrintLinef("Firmware Version: %d.%02d%", firmwareVersion, firmwareSubVersion);
|
|
|
|
break;
|
|
}
|
|
if (escInfoVersion == ESC_INFO_KISS_V2 || escInfoVersion == ESC_INFO_BLHELI32) {
|
|
cliPrintLinef("Rotation Direction: %s", escInfoBuffer[16] ? "reversed" : "normal");
|
|
cliPrintLinef("3D: %s", escInfoBuffer[17] ? "on" : "off");
|
|
if (escInfoVersion == ESC_INFO_BLHELI32) {
|
|
uint8_t setting = escInfoBuffer[18];
|
|
cliPrint("Low voltage Limit: ");
|
|
switch (setting) {
|
|
case 0:
|
|
cliPrintLine("off");
|
|
|
|
break;
|
|
case 255:
|
|
cliPrintLine("unsupported");
|
|
|
|
break;
|
|
default:
|
|
cliPrintLinef("%d.%01d", setting / 10, setting % 10);
|
|
|
|
break;
|
|
}
|
|
|
|
setting = escInfoBuffer[19];
|
|
cliPrint("Current Limit: ");
|
|
switch (setting) {
|
|
case 0:
|
|
cliPrintLine("off");
|
|
|
|
break;
|
|
case 255:
|
|
cliPrintLine("unsupported");
|
|
|
|
break;
|
|
default:
|
|
cliPrintLinef("%d", setting);
|
|
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
setting = escInfoBuffer[i + 20];
|
|
cliPrintLinef("LED %d: %s", i, setting ? (setting == 255) ? "unsupported" : "on" : "off");
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "CHECKSUM ERROR.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!escInfoReceived) {
|
|
cliPrintLine("No Info.");
|
|
}
|
|
}
|
|
|
|
static void executeEscInfoCommand(const char *cmdName, uint8_t escIndex)
|
|
{
|
|
cliPrintLinef("Info for ESC %d:", escIndex);
|
|
|
|
uint8_t escInfoBuffer[ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE];
|
|
|
|
startEscDataRead(escInfoBuffer, ESC_INFO_BLHELI32_EXPECTED_FRAME_SIZE);
|
|
|
|
dshotCommandWrite(escIndex, getMotorCount(), DSHOT_CMD_ESC_INFO, DSHOT_CMD_TYPE_BLOCKING);
|
|
|
|
delay(10);
|
|
|
|
printEscInfo(cmdName, escInfoBuffer, getNumberEscBytesRead());
|
|
}
|
|
#endif // USE_ESC_SENSOR && USE_ESC_SENSOR_INFO
|
|
|
|
static void cliDshotProg(const char *cmdName, char *cmdline)
|
|
{
|
|
if (isEmpty(cmdline) || !isMotorProtocolDshot()) {
|
|
cliShowParseError(cmdName);
|
|
|
|
return;
|
|
}
|
|
|
|
char *saveptr;
|
|
char *pch = strtok_r(cmdline, " ", &saveptr);
|
|
int pos = 0;
|
|
int escIndex = 0;
|
|
bool firstCommand = true;
|
|
while (pch != NULL) {
|
|
switch (pos) {
|
|
case 0:
|
|
escIndex = parseOutputIndex(cmdName, pch, true);
|
|
if (escIndex == -1) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
{
|
|
int command = atoi(pch);
|
|
if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
|
|
if (firstCommand) {
|
|
// pwmDisableMotors();
|
|
motorDisable();
|
|
|
|
firstCommand = false;
|
|
}
|
|
|
|
if (command != DSHOT_CMD_ESC_INFO) {
|
|
dshotCommandWrite(escIndex, getMotorCount(), command, DSHOT_CMD_TYPE_BLOCKING);
|
|
} else {
|
|
#if defined(USE_ESC_SENSOR) && defined(USE_ESC_SENSOR_INFO)
|
|
if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
|
|
if (escIndex != ALL_MOTORS) {
|
|
executeEscInfoCommand(cmdName, escIndex);
|
|
} else {
|
|
for (uint8_t i = 0; i < getMotorCount(); i++) {
|
|
executeEscInfoCommand(cmdName, i);
|
|
}
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
cliPrintLine("Not supported.");
|
|
}
|
|
}
|
|
|
|
cliPrintLinef("Command Sent: %d", command);
|
|
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "INVALID COMMAND. RANGE: 1 - %d.", DSHOT_MIN_THROTTLE - 1);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
pos++;
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
}
|
|
|
|
motorEnable();
|
|
}
|
|
#endif // USE_DSHOT
|
|
|
|
#ifdef USE_ESCSERIAL
|
|
static void cliEscPassthrough(const char *cmdName, char *cmdline)
|
|
{
|
|
if (isEmpty(cmdline)) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
|
|
return;
|
|
}
|
|
|
|
char *saveptr;
|
|
char *pch = strtok_r(cmdline, " ", &saveptr);
|
|
int pos = 0;
|
|
uint8_t mode = 0;
|
|
int escIndex = 0;
|
|
while (pch != NULL) {
|
|
switch (pos) {
|
|
case 0:
|
|
if (strncasecmp(pch, "sk", strlen(pch)) == 0) {
|
|
mode = PROTOCOL_SIMONK;
|
|
} else if (strncasecmp(pch, "bl", strlen(pch)) == 0) {
|
|
mode = PROTOCOL_BLHELI;
|
|
} else if (strncasecmp(pch, "ki", strlen(pch)) == 0) {
|
|
mode = PROTOCOL_KISS;
|
|
} else if (strncasecmp(pch, "cc", strlen(pch)) == 0) {
|
|
mode = PROTOCOL_CASTLE;
|
|
} else {
|
|
cliShowParseError(cmdName);
|
|
|
|
return;
|
|
}
|
|
break;
|
|
case 1:
|
|
escIndex = parseOutputIndex(cmdName, pch, mode == PROTOCOL_KISS);
|
|
if (escIndex == -1) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
|
|
return;
|
|
|
|
break;
|
|
|
|
}
|
|
pos++;
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
}
|
|
|
|
if (!escEnablePassthrough(cliPort, &motorConfig()->dev, escIndex, mode)) {
|
|
cliPrintErrorLinef(cmdName, "Error starting ESC connection");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifndef USE_QUAD_MIXER_ONLY
|
|
static void cliMixer(const char *cmdName, char *cmdline)
|
|
{
|
|
int len;
|
|
|
|
len = strlen(cmdline);
|
|
|
|
if (len == 0) {
|
|
cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
|
|
return;
|
|
} else if (strncasecmp(cmdline, "list", len) == 0) {
|
|
cliPrint("Available:");
|
|
for (uint32_t i = 0; ; i++) {
|
|
if (mixerNames[i] == NULL)
|
|
break;
|
|
cliPrintf(" %s", mixerNames[i]);
|
|
}
|
|
cliPrintLinefeed();
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; ; i++) {
|
|
if (mixerNames[i] == NULL) {
|
|
cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
|
|
return;
|
|
}
|
|
if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
|
|
mixerConfigMutable()->mixerMode = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
cliMixer(cmdName, "");
|
|
}
|
|
#endif
|
|
|
|
static void cliMotor(const char *cmdName, char *cmdline)
|
|
{
|
|
if (isEmpty(cmdline)) {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
|
|
return;
|
|
}
|
|
|
|
int motorIndex = 0;
|
|
int motorValue = 0;
|
|
|
|
char *saveptr;
|
|
char *pch = strtok_r(cmdline, " ", &saveptr);
|
|
int index = 0;
|
|
while (pch != NULL) {
|
|
switch (index) {
|
|
case 0:
|
|
motorIndex = parseOutputIndex(cmdName, pch, true);
|
|
if (motorIndex == -1) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
case 1:
|
|
motorValue = atoi(pch);
|
|
|
|
break;
|
|
}
|
|
index++;
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
}
|
|
|
|
if (index == 2) {
|
|
if (motorValue < PWM_RANGE_MIN || motorValue > PWM_RANGE_MAX) {
|
|
cliShowArgumentRangeError(cmdName, "VALUE", 1000, 2000);
|
|
} else {
|
|
uint32_t motorOutputValue = motorConvertFromExternal(motorValue);
|
|
|
|
if (motorIndex != ALL_MOTORS) {
|
|
motor_disarmed[motorIndex] = motorOutputValue;
|
|
|
|
cliPrintLinef("motor %d: %d", motorIndex, motorOutputValue);
|
|
} else {
|
|
for (int i = 0; i < getMotorCount(); i++) {
|
|
motor_disarmed[i] = motorOutputValue;
|
|
}
|
|
|
|
cliPrintLinef("all motors: %d", motorOutputValue);
|
|
}
|
|
}
|
|
} else {
|
|
cliShowInvalidArgumentCountError(cmdName);
|
|
}
|
|
}
|
|
|
|
#ifndef MINIMAL_CLI
|
|
static void cliPlaySound(const char *cmdName, 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
|
|
cliPrintErrorLinef(cmdName, "ERROR PLAYING SOUND");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
} else { //index value was given
|
|
i = atoi(cmdline);
|
|
if ((name=beeperNameForTableIndex(i)) == NULL) {
|
|
cliPrintLinef("No sound for index %d", i);
|
|
return;
|
|
}
|
|
}
|
|
lastSoundIdx = i;
|
|
beeperSilence();
|
|
cliPrintLinef("Playing sound %d: %s", i, name);
|
|
beeper(beeperModeForTableIndex(i));
|
|
}
|
|
#endif
|
|
|
|
static void cliProfile(const char *cmdName, char *cmdline)
|
|
{
|
|
if (isEmpty(cmdline)) {
|
|
cliPrintLinef("profile %d", getPidProfileIndexToUse());
|
|
return;
|
|
} else {
|
|
const int i = atoi(cmdline);
|
|
if (i >= 0 && i < PID_PROFILE_COUNT) {
|
|
changePidProfile(i);
|
|
cliProfile(cmdName, "");
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "PROFILE OUTSIDE OF [0..%d]", PID_PROFILE_COUNT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cliRateProfile(const char *cmdName, char *cmdline)
|
|
{
|
|
if (isEmpty(cmdline)) {
|
|
cliPrintLinef("rateprofile %d", getRateProfileIndexToUse());
|
|
return;
|
|
} else {
|
|
const int i = atoi(cmdline);
|
|
if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
|
|
changeControlRateProfile(i);
|
|
cliRateProfile(cmdName, "");
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "RATE PROFILE OUTSIDE OF [0..%d]", CONTROL_RATE_PROFILE_COUNT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cliDumpPidProfile(const char *cmdName, uint8_t pidProfileIndex, dumpFlags_t dumpMask)
|
|
{
|
|
if (pidProfileIndex >= PID_PROFILE_COUNT) {
|
|
// Faulty values
|
|
return;
|
|
}
|
|
|
|
pidProfileIndexToUse = pidProfileIndex;
|
|
|
|
cliPrintLinefeed();
|
|
cliProfile(cmdName, "");
|
|
|
|
char profileStr[10];
|
|
tfp_sprintf(profileStr, "profile %d", pidProfileIndex);
|
|
dumpAllValues(cmdName, PROFILE_VALUE, dumpMask, profileStr);
|
|
|
|
pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
|
|
}
|
|
|
|
static void cliDumpRateProfile(const char *cmdName, uint8_t rateProfileIndex, dumpFlags_t dumpMask)
|
|
{
|
|
if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
|
|
// Faulty values
|
|
return;
|
|
}
|
|
|
|
rateProfileIndexToUse = rateProfileIndex;
|
|
|
|
cliPrintLinefeed();
|
|
cliRateProfile(cmdName, "");
|
|
|
|
char rateProfileStr[14];
|
|
tfp_sprintf(rateProfileStr, "rateprofile %d", rateProfileIndex);
|
|
dumpAllValues(cmdName, PROFILE_RATE_VALUE, dumpMask, rateProfileStr);
|
|
|
|
rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
|
|
}
|
|
|
|
#ifdef USE_CLI_BATCH
|
|
static void cliPrintCommandBatchWarning(const char *cmdName, const char *warning)
|
|
{
|
|
cliPrintErrorLinef(cmdName, "ERRORS WERE DETECTED - PLEASE REVIEW BEFORE CONTINUING");
|
|
if (warning) {
|
|
cliPrintErrorLinef(cmdName, warning);
|
|
}
|
|
}
|
|
|
|
static void resetCommandBatch(void)
|
|
{
|
|
commandBatchActive = false;
|
|
commandBatchError = false;
|
|
}
|
|
|
|
static void cliBatch(const char *cmdName, char *cmdline)
|
|
{
|
|
if (strncasecmp(cmdline, "start", 5) == 0) {
|
|
if (!commandBatchActive) {
|
|
commandBatchActive = true;
|
|
commandBatchError = false;
|
|
}
|
|
cliPrintLine("Command batch started");
|
|
} else if (strncasecmp(cmdline, "end", 3) == 0) {
|
|
if (commandBatchActive && commandBatchError) {
|
|
cliPrintCommandBatchWarning(cmdName, NULL);
|
|
} else {
|
|
cliPrintLine("Command batch ended");
|
|
}
|
|
resetCommandBatch();
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "Invalid option");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static bool prepareSave(void)
|
|
{
|
|
|
|
#ifdef USE_CLI_BATCH
|
|
if (commandBatchActive && commandBatchError) {
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_BOARD_INFO)
|
|
if (boardInformationUpdated) {
|
|
persistBoardInformation();
|
|
}
|
|
#if defined(USE_SIGNATURE)
|
|
if (signatureUpdated) {
|
|
persistSignature();
|
|
}
|
|
#endif
|
|
#endif // USE_BOARD_INFO
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool tryPrepareSave(const char *cmdName)
|
|
{
|
|
bool success = prepareSave();
|
|
#if defined(USE_CLI_BATCH)
|
|
if (!success) {
|
|
cliPrintCommandBatchWarning(cmdName, "PLEASE FIX ERRORS THEN 'SAVE'");
|
|
resetCommandBatch();
|
|
|
|
return false;
|
|
}
|
|
#else
|
|
UNUSED(cmdName);
|
|
UNUSED(success);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static void cliSave(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdline);
|
|
|
|
if (tryPrepareSave(cmdName)) {
|
|
writeEEPROM();
|
|
cliPrintHashLine("saving");
|
|
|
|
if (strcasecmp(cmdline, "noreboot") == 0) {
|
|
return;
|
|
}
|
|
cliReboot();
|
|
}
|
|
}
|
|
|
|
static void cliDefaults(const char *cmdName, char *cmdline)
|
|
{
|
|
bool saveConfigs = true;
|
|
uint16_t parameterGroupId = 0;
|
|
|
|
char *saveptr;
|
|
char* tok = strtok_r(cmdline, " ", &saveptr);
|
|
bool expectParameterGroupId = false;
|
|
while (tok != NULL) {
|
|
if (expectParameterGroupId) {
|
|
parameterGroupId = atoi(tok);
|
|
expectParameterGroupId = false;
|
|
|
|
if (!parameterGroupId) {
|
|
cliShowParseError(cmdName);
|
|
return;
|
|
}
|
|
} else if (strcasestr(tok, "group_id")) {
|
|
expectParameterGroupId = true;
|
|
} else if (strcasestr(tok, "nosave")) {
|
|
saveConfigs = false;
|
|
} else {
|
|
cliShowParseError(cmdName);
|
|
|
|
return;
|
|
}
|
|
|
|
tok = strtok_r(NULL, " ", &saveptr);
|
|
}
|
|
|
|
if (expectParameterGroupId) {
|
|
cliShowParseError(cmdName);
|
|
|
|
return;
|
|
}
|
|
|
|
if (parameterGroupId) {
|
|
cliPrintLinef("\r\n# resetting group %d to defaults", parameterGroupId);
|
|
backupConfigs();
|
|
} else {
|
|
cliPrintHashLine("resetting to defaults");
|
|
}
|
|
|
|
resetConfig();
|
|
|
|
#ifdef USE_CLI_BATCH
|
|
// Reset only the error state and allow the batch active state to remain.
|
|
// This way if a "defaults nosave" was issued after the "batch on" we'll
|
|
// only reset the current error state but the batch will still be active
|
|
// for subsequent commands.
|
|
commandBatchError = false;
|
|
#endif
|
|
|
|
#if defined(USE_SIMPLIFIED_TUNING)
|
|
applySimplifiedTuningAllProfiles();
|
|
#endif
|
|
|
|
if (parameterGroupId) {
|
|
restoreConfigs(parameterGroupId);
|
|
}
|
|
|
|
if (saveConfigs && tryPrepareSave(cmdName)) {
|
|
writeUnmodifiedConfigToEEPROM();
|
|
|
|
cliReboot();
|
|
}
|
|
}
|
|
|
|
static void cliPrintVarDefault(const char *cmdName, const clivalue_t *value)
|
|
{
|
|
const pgRegistry_t *pg = pgFind(value->pgn);
|
|
if (pg) {
|
|
const char *defaultFormat = "Default value: ";
|
|
const int valueOffset = getValueOffset(value);
|
|
const bool equalsDefault = valuePtrEqualsDefault(value, pg->copy + valueOffset, pg->address + valueOffset);
|
|
if (!equalsDefault) {
|
|
cliPrintf(defaultFormat, value->name);
|
|
printValuePointer(cmdName, value, (uint8_t*)pg->address + valueOffset, false);
|
|
cliPrintLinefeed();
|
|
}
|
|
}
|
|
}
|
|
|
|
STATIC_UNIT_TESTED void cliGet(const char *cmdName, char *cmdline)
|
|
{
|
|
const clivalue_t *val;
|
|
int matchedCommands = 0;
|
|
|
|
pidProfileIndexToUse = getCurrentPidProfileIndex();
|
|
rateProfileIndexToUse = getCurrentControlRateProfileIndex();
|
|
|
|
backupAndResetConfigs();
|
|
|
|
for (uint32_t i = 0; i < valueTableEntryCount; i++) {
|
|
if (strcasestr(valueTable[i].name, cmdline)) {
|
|
val = &valueTable[i];
|
|
if (matchedCommands > 0) {
|
|
cliPrintLinefeed();
|
|
}
|
|
cliPrintf("%s = ", valueTable[i].name);
|
|
cliPrintVar(cmdName, val, 0);
|
|
cliPrintLinefeed();
|
|
switch (val->type & VALUE_SECTION_MASK) {
|
|
case PROFILE_VALUE:
|
|
cliProfile(cmdName, "");
|
|
|
|
break;
|
|
case PROFILE_RATE_VALUE:
|
|
cliRateProfile(cmdName, "");
|
|
|
|
break;
|
|
default:
|
|
|
|
break;
|
|
}
|
|
cliPrintVarRange(val);
|
|
cliPrintVarDefault(cmdName, val);
|
|
|
|
matchedCommands++;
|
|
}
|
|
}
|
|
|
|
restoreConfigs(0);
|
|
|
|
pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
|
|
rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
|
|
|
|
if (!matchedCommands) {
|
|
cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
|
|
}
|
|
}
|
|
|
|
static uint8_t getWordLength(const char *bufBegin, const char *bufEnd)
|
|
{
|
|
while (*(bufEnd - 1) == ' ') {
|
|
bufEnd--;
|
|
}
|
|
|
|
return bufEnd - bufBegin;
|
|
}
|
|
|
|
STATIC_UNIT_TESTED uint16_t cliGetSettingIndex(const char *name, size_t length)
|
|
{
|
|
for (uint32_t i = 0; i < valueTableEntryCount; i++) {
|
|
const char *settingName = valueTable[i].name;
|
|
|
|
// ensure exact match when setting to prevent setting variables with longer names
|
|
if (strncasecmp(name, settingName, length) == 0 && length == strlen(settingName)) {
|
|
return i;
|
|
}
|
|
}
|
|
return valueTableEntryCount;
|
|
}
|
|
|
|
STATIC_UNIT_TESTED void cliSet(const char *cmdName, char *cmdline)
|
|
{
|
|
const uint32_t len = strlen(cmdline);
|
|
char *eqptr;
|
|
|
|
if (len == 0 || (len == 1 && cmdline[0] == '*')) {
|
|
cliPrintLine("Current settings: ");
|
|
|
|
for (uint32_t i = 0; i < valueTableEntryCount; i++) {
|
|
const clivalue_t *val = &valueTable[i];
|
|
cliPrintf("%s = ", valueTable[i].name);
|
|
cliPrintVar(cmdName, val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
|
|
cliPrintLinefeed();
|
|
}
|
|
} else if ((eqptr = strstr(cmdline, "=")) != NULL) {
|
|
// has equals
|
|
|
|
uint8_t variableNameLength = getWordLength(cmdline, eqptr);
|
|
|
|
// skip the '=' and any ' ' characters
|
|
eqptr++;
|
|
eqptr = skipSpace(eqptr);
|
|
|
|
const uint16_t index = cliGetSettingIndex(cmdline, variableNameLength);
|
|
if (index >= valueTableEntryCount) {
|
|
cliPrintErrorLinef(cmdName, ERROR_INVALID_NAME, cmdline);
|
|
return;
|
|
}
|
|
const clivalue_t *val = &valueTable[index];
|
|
|
|
bool valueChanged = false;
|
|
int16_t value = 0;
|
|
switch (val->type & VALUE_MODE_MASK) {
|
|
case MODE_DIRECT: {
|
|
if ((val->type & VALUE_TYPE_MASK) == VAR_UINT32) {
|
|
uint32_t value = strtoul(eqptr, NULL, 10);
|
|
|
|
if (value <= val->config.u32Max) {
|
|
cliSetVar(val, value);
|
|
valueChanged = true;
|
|
}
|
|
} else if ((val->type & VALUE_TYPE_MASK) == VAR_INT32) {
|
|
int32_t value = strtol(eqptr, NULL, 10);
|
|
|
|
// INT32s are limited to being symmetric, so we test both bounds with the same magnitude
|
|
if (value <= val->config.d32Max && value >= -val->config.d32Max) {
|
|
cliSetVar(val, value);
|
|
valueChanged = true;
|
|
}
|
|
} else {
|
|
int value = atoi(eqptr);
|
|
|
|
int min;
|
|
int max;
|
|
getMinMax(val, &min, &max);
|
|
|
|
if (value >= min && value <= max) {
|
|
cliSetVar(val, value);
|
|
valueChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
case MODE_LOOKUP:
|
|
case MODE_BITSET: {
|
|
int tableIndex;
|
|
if ((val->type & VALUE_MODE_MASK) == MODE_BITSET) {
|
|
tableIndex = TABLE_OFF_ON;
|
|
} else {
|
|
tableIndex = val->config.lookup.tableIndex;
|
|
}
|
|
const lookupTableEntry_t *tableEntry = &lookupTables[tableIndex];
|
|
bool matched = false;
|
|
for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
|
|
matched = tableEntry->values[tableValueIndex] && strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
|
|
|
|
if (matched) {
|
|
value = tableValueIndex;
|
|
|
|
cliSetVar(val, value);
|
|
valueChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case MODE_ARRAY: {
|
|
const uint8_t arrayLength = val->config.array.length;
|
|
char *valPtr = eqptr;
|
|
|
|
int i = 0;
|
|
while (i < arrayLength && valPtr != NULL) {
|
|
// skip spaces
|
|
valPtr = skipSpace(valPtr);
|
|
|
|
// process substring starting at valPtr
|
|
// note: no need to copy substrings for atoi()
|
|
// it stops at the first character that cannot be converted...
|
|
switch (val->type & VALUE_TYPE_MASK) {
|
|
default:
|
|
case VAR_UINT8:
|
|
{
|
|
// fetch data pointer
|
|
uint8_t *data = (uint8_t *)cliGetValuePointer(val) + i;
|
|
// store value
|
|
*data = (uint8_t)atoi((const char*) valPtr);
|
|
}
|
|
|
|
break;
|
|
case VAR_INT8:
|
|
{
|
|
// fetch data pointer
|
|
int8_t *data = (int8_t *)cliGetValuePointer(val) + i;
|
|
// store value
|
|
*data = (int8_t)atoi((const char*) valPtr);
|
|
}
|
|
|
|
break;
|
|
case VAR_UINT16:
|
|
{
|
|
// fetch data pointer
|
|
uint16_t *data = (uint16_t *)cliGetValuePointer(val) + i;
|
|
// store value
|
|
*data = (uint16_t)atoi((const char*) valPtr);
|
|
}
|
|
|
|
break;
|
|
case VAR_INT16:
|
|
{
|
|
// fetch data pointer
|
|
int16_t *data = (int16_t *)cliGetValuePointer(val) + i;
|
|
// store value
|
|
*data = (int16_t)atoi((const char*) valPtr);
|
|
}
|
|
|
|
break;
|
|
case VAR_UINT32:
|
|
{
|
|
// fetch data pointer
|
|
uint32_t *data = (uint32_t *)cliGetValuePointer(val) + i;
|
|
// store value
|
|
*data = (uint32_t)strtoul((const char*) valPtr, NULL, 10);
|
|
}
|
|
|
|
break;
|
|
case VAR_INT32:
|
|
{
|
|
// fetch data pointer
|
|
int32_t *data = (int32_t *)cliGetValuePointer(val) + i;
|
|
// store value
|
|
*data = (int32_t)strtol((const char*) valPtr, NULL, 10);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// find next comma (or end of string)
|
|
valPtr = strchr(valPtr, ',') + 1;
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// mark as changed
|
|
valueChanged = true;
|
|
|
|
break;
|
|
case MODE_STRING: {
|
|
char *valPtr = eqptr;
|
|
valPtr = skipSpace(valPtr);
|
|
|
|
const unsigned int len = strlen(valPtr);
|
|
const uint8_t min = val->config.string.minlength;
|
|
const uint8_t max = val->config.string.maxlength;
|
|
const bool updatable = ((val->config.string.flags & STRING_FLAGS_WRITEONCE) == 0 ||
|
|
strlen((char *)cliGetValuePointer(val)) == 0 ||
|
|
strncmp(valPtr, (char *)cliGetValuePointer(val), len) == 0);
|
|
|
|
if (updatable && len > 0 && len <= max) {
|
|
memset((char *)cliGetValuePointer(val), 0, max);
|
|
if (len >= min && strncmp(valPtr, emptyName, len)) {
|
|
memcpy((char *)cliGetValuePointer(val), valPtr, len);
|
|
}
|
|
valueChanged = true;
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "STRING MUST BE 1-%d CHARACTERS OR '-' FOR EMPTY", max);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (valueChanged) {
|
|
cliPrintf("%s set to ", val->name);
|
|
cliPrintVar(cmdName, val, 0);
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "INVALID VALUE");
|
|
cliPrintVarRange(val);
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
// no equals, check for matching variables.
|
|
cliGet(cmdName, cmdline);
|
|
}
|
|
}
|
|
|
|
static void cliStatus(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
// MCU type, clock, vrefint, core temperature
|
|
|
|
cliPrintf("MCU %s Clock=%dMHz", getMcuTypeName(), (SystemCoreClock / 1000000));
|
|
|
|
#if PLATFORM_TRAIT_CONFIG_HSE
|
|
// Only F4 and G4 is capable of switching between HSE/HSI (for now)
|
|
int sysclkSource = SystemSYSCLKSource();
|
|
|
|
const char *SYSCLKSource[] = { "HSI", "HSE", "PLLP", "PLLR" };
|
|
const char *PLLSource[] = { "-HSI", "-HSE" };
|
|
|
|
int pllSource;
|
|
|
|
if (sysclkSource >= 2) {
|
|
pllSource = SystemPLLSource();
|
|
}
|
|
|
|
cliPrintf(" (%s%s)", SYSCLKSource[sysclkSource], (sysclkSource < 2) ? "" : PLLSource[pllSource]);
|
|
#endif
|
|
|
|
#ifdef USE_ADC_INTERNAL
|
|
uint16_t vrefintMv = getVrefMv();
|
|
int16_t coretemp = getCoreTemperatureCelsius();
|
|
cliPrintLinef(", Vref=%d.%2dV, Core temp=%ddegC", vrefintMv / 1000, (vrefintMv % 1000) / 10, coretemp);
|
|
#else
|
|
cliPrintLinefeed();
|
|
#endif
|
|
|
|
// Stack and config sizes and usages
|
|
|
|
cliPrintf("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
|
|
#ifdef USE_STACK_CHECK
|
|
cliPrintf(", Stack used: %d", stackUsedSize());
|
|
#endif
|
|
cliPrintLinefeed();
|
|
|
|
cliPrintLinef("Configuration: %s, size: %d, max available: %d", configurationStates[systemConfigMutable()->configurationState], getEEPROMConfigSize(), getEEPROMStorageSize());
|
|
|
|
// Devices
|
|
#if defined(USE_SPI) || defined(USE_I2C)
|
|
cliPrint("Devices detected:");
|
|
#if defined(USE_SPI)
|
|
cliPrintf(" SPI:%d", spiGetRegisteredDeviceCount());
|
|
#if defined(USE_I2C)
|
|
cliPrint(",");
|
|
#endif
|
|
#endif
|
|
#if defined(USE_I2C)
|
|
cliPrintf(" I2C:%d", i2cGetRegisteredDeviceCount());
|
|
#endif
|
|
cliPrintLinefeed();
|
|
#endif
|
|
|
|
// Sensors
|
|
cliPrint("Gyros detected:");
|
|
bool found = false;
|
|
for (unsigned pos = 0; pos < 7; pos++) {
|
|
if (gyroConfig()->gyrosDetected & BIT(pos)) {
|
|
if (found) {
|
|
cliPrint(",");
|
|
} else {
|
|
found = true;
|
|
}
|
|
cliPrintf(" gyro %d", pos + 1);
|
|
}
|
|
}
|
|
#ifdef USE_SPI
|
|
if (gyroActiveDev()->gyroModeSPI != GYRO_EXTI_NO_INT) {
|
|
cliPrintf(" locked");
|
|
}
|
|
if (gyroActiveDev()->gyroModeSPI == GYRO_EXTI_INT_DMA) {
|
|
cliPrintf(" dma");
|
|
}
|
|
if (spiGetExtDeviceCount(&gyroActiveDev()->dev) > 1) {
|
|
cliPrintf(" shared");
|
|
}
|
|
#endif
|
|
cliPrintLinefeed();
|
|
|
|
#if defined(USE_SENSOR_NAMES)
|
|
const uint32_t detectedSensorsMask = sensorsMask();
|
|
for (uint32_t i = 0; ; i++) {
|
|
if (sensorTypeNames[i] == NULL) {
|
|
break;
|
|
}
|
|
const uint32_t mask = (1 << i);
|
|
if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
|
|
const uint8_t sensorHardwareIndex = detectedSensors[i];
|
|
const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
|
|
if (i) {
|
|
cliPrint(", ");
|
|
}
|
|
cliPrintf("%s=%s", sensorTypeNames[i], sensorHardware);
|
|
#if defined(USE_ACC)
|
|
if (mask == SENSOR_ACC && acc.dev.revisionCode) {
|
|
cliPrintf(".%c", acc.dev.revisionCode);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
cliPrintLinefeed();
|
|
#endif /* USE_SENSOR_NAMES */
|
|
|
|
#if defined(USE_OSD)
|
|
osdDisplayPortDevice_e displayPortDeviceType;
|
|
displayPort_t *osdDisplayPort = osdGetDisplayPort(&displayPortDeviceType);
|
|
|
|
cliPrintLinef("OSD: %s (%u x %u)", lookupTableOsdDisplayPortDevice[displayPortDeviceType], osdDisplayPort->cols, osdDisplayPort->rows);
|
|
#endif
|
|
|
|
if (buildKey) {
|
|
cliPrintf("BUILD KEY: %s", buildKey);
|
|
if (releaseName) {
|
|
cliPrintf(" (%s)", releaseName);
|
|
}
|
|
cliPrintLinefeed();
|
|
}
|
|
// Uptime and wall clock
|
|
|
|
cliPrintf("System Uptime: %d seconds", millis() / 1000);
|
|
|
|
#ifdef USE_RTC_TIME
|
|
char buf[FORMATTED_DATE_TIME_BUFSIZE];
|
|
dateTime_t dt;
|
|
if (rtcGetDateTime(&dt)) {
|
|
dateTimeFormatLocal(buf, &dt);
|
|
cliPrintf(", Current Time: %s", buf);
|
|
}
|
|
#endif
|
|
cliPrintLinefeed();
|
|
|
|
// Run status
|
|
|
|
const int gyroRate = getTaskDeltaTimeUs(TASK_GYRO) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_GYRO)));
|
|
|
|
int rxRate = getRxRateValid() ? getCurrentRxRateHz() : 0;
|
|
|
|
const int systemRate = getTaskDeltaTimeUs(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTimeUs(TASK_SYSTEM)));
|
|
cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
|
|
constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE), getTaskDeltaTimeUs(TASK_GYRO), gyroRate, rxRate, systemRate);
|
|
|
|
// Battery meter
|
|
|
|
cliPrintLinef("Voltage: %d * 0.01V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
|
|
|
|
// Other devices and status
|
|
|
|
#ifdef USE_I2C
|
|
const uint16_t i2cErrorCounter = i2cGetErrorCounter();
|
|
#else
|
|
const uint16_t i2cErrorCounter = 0;
|
|
#endif
|
|
cliPrintLinef("I2C Errors: %d", i2cErrorCounter);
|
|
|
|
#ifdef USE_SDCARD
|
|
cliSdInfo(cmdName, "");
|
|
#endif
|
|
|
|
#ifdef USE_FLASH_CHIP
|
|
const flashGeometry_t *layout = flashGetGeometry();
|
|
if (layout->jedecId != 0) {
|
|
cliPrintLinef("FLASH: JEDEC ID=0x%08x %uM", layout->jedecId, layout->totalSize >> 20);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_GPS
|
|
cliPrint("GPS: ");
|
|
if (featureIsEnabled(FEATURE_GPS)) {
|
|
if (gpsData.state >= GPS_STATE_CONFIGURE) {
|
|
cliPrint("connected, ");
|
|
} else {
|
|
cliPrint("NOT CONNECTED, ");
|
|
}
|
|
if (gpsConfig()->provider == GPS_MSP) {
|
|
cliPrint("MSP, ");
|
|
} else {
|
|
const serialPortConfig_t *gpsPortConfig = findSerialPortConfig(FUNCTION_GPS);
|
|
if (!gpsPortConfig) {
|
|
cliPrint("NO PORT, ");
|
|
} else {
|
|
cliPrintf("%s %ld (set to ", serialName(gpsPortConfig->identifier, invalidName), baudRates[getGpsPortActualBaudRateIndex()]);
|
|
if (gpsConfig()->autoBaud == GPS_AUTOBAUD_ON) {
|
|
cliPrint("AUTO");
|
|
} else {
|
|
cliPrintf("%ld", baudRates[gpsPortConfig->gps_baudrateIndex]);
|
|
}
|
|
cliPrint("), ");
|
|
}
|
|
}
|
|
if (gpsData.state <= GPS_STATE_CONFIGURE) {
|
|
cliPrint("NOT CONFIGURED");
|
|
} else {
|
|
if (gpsConfig()->autoConfig == GPS_AUTOCONFIG_OFF) {
|
|
cliPrint("auto config OFF");
|
|
} else {
|
|
cliPrint("configured");
|
|
}
|
|
}
|
|
cliPrintf(", version = %s", gpsData.platformVersion != UBX_VERSION_UNDEF ? ubloxVersionMap[gpsData.platformVersion].str : "unknown");
|
|
} else {
|
|
cliPrint("NOT ENABLED");
|
|
}
|
|
cliPrintLinefeed();
|
|
#endif // USE_GPS
|
|
|
|
cliPrint("Arming disable flags:");
|
|
armingDisableFlags_e flags = getArmingDisableFlags();
|
|
while (flags) {
|
|
const armingDisableFlags_e flag = 1 << (ffs(flags) - 1);
|
|
flags &= ~flag;
|
|
cliPrintf(" %s", getArmingDisableFlagName(flag));
|
|
}
|
|
cliPrintLinefeed();
|
|
}
|
|
|
|
static void cliTasks(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
int averageLoadSum = 0;
|
|
|
|
#ifndef MINIMAL_CLI
|
|
if (systemConfig()->task_statistics) {
|
|
#if defined(USE_LATE_TASK_STATISTICS)
|
|
cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms late run reqd/us");
|
|
#else
|
|
cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
|
|
#endif
|
|
} else {
|
|
cliPrintLine("Task list");
|
|
}
|
|
#endif
|
|
for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
|
|
taskInfo_t taskInfo;
|
|
getTaskInfo(taskId, &taskInfo);
|
|
if (taskInfo.isEnabled) {
|
|
int taskFrequency = taskInfo.averageDeltaTime10thUs == 0 ? 0 : lrintf(1e7f / taskInfo.averageDeltaTime10thUs);
|
|
cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
|
|
const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 : (taskInfo.maxExecutionTimeUs * taskFrequency) / 1000;
|
|
const int averageLoad = taskInfo.averageExecutionTime10thUs == 0 ? 0 : (taskInfo.averageExecutionTime10thUs * taskFrequency) / 10000;
|
|
if (taskId != TASK_SERIAL) {
|
|
averageLoadSum += averageLoad;
|
|
}
|
|
if (systemConfig()->task_statistics) {
|
|
#if defined(USE_LATE_TASK_STATISTICS)
|
|
cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d %6d %6d %7d",
|
|
taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
|
|
maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
|
|
taskInfo.totalExecutionTimeUs / 1000,
|
|
taskInfo.lateCount, taskInfo.runCount, taskInfo.execTime);
|
|
#else
|
|
cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
|
|
taskFrequency, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10,
|
|
maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10,
|
|
taskInfo.totalExecutionTimeUs / 1000);
|
|
#endif
|
|
} else {
|
|
cliPrintLinef("%6d", taskFrequency);
|
|
}
|
|
|
|
schedulerResetTaskMaxExecutionTime(taskId);
|
|
}
|
|
}
|
|
if (systemConfig()->task_statistics) {
|
|
cfCheckFuncInfo_t checkFuncInfo;
|
|
getCheckFuncInfo(&checkFuncInfo);
|
|
cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTimeUs, checkFuncInfo.averageExecutionTimeUs, checkFuncInfo.totalExecutionTimeUs / 1000);
|
|
cliPrintLinef("Total (excluding SERIAL) %33d.%1d%%", averageLoadSum/10, averageLoadSum%10);
|
|
if (debugMode == DEBUG_SCHEDULER_DETERMINISM) {
|
|
extern int32_t schedLoopStartCycles, taskGuardCycles;
|
|
|
|
cliPrintLinef("Scheduler start cycles %d guard cycles %d", schedLoopStartCycles, taskGuardCycles);
|
|
}
|
|
schedulerResetCheckFunctionMaxExecutionTime();
|
|
}
|
|
}
|
|
|
|
static void printVersion(bool printBoardInfo)
|
|
{
|
|
cliPrintf("# %s / %s (%s) %s %s / %s (%s) MSP API: %s",
|
|
FC_FIRMWARE_NAME,
|
|
targetName,
|
|
systemConfig()->boardIdentifier,
|
|
FC_VERSION_STRING,
|
|
buildDate,
|
|
buildTime,
|
|
shortGitRevision,
|
|
MSP_API_VERSION_STRING
|
|
);
|
|
|
|
cliPrintLinefeed();
|
|
|
|
#if defined(__CONFIG_REVISION__)
|
|
cliPrintLinef("# config rev: %s", shortConfigGitRevision);
|
|
#endif
|
|
|
|
#if defined(USE_BOARD_INFO)
|
|
if (printBoardInfo && strlen(getManufacturerId()) && strlen(getBoardName())) {
|
|
cliPrintLinef("# board: manufacturer_id: %s, board_name: %s", getManufacturerId(), getBoardName());
|
|
}
|
|
#else
|
|
UNUSED(printBoardInfo);
|
|
#endif
|
|
}
|
|
|
|
static void cliVersion(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
printVersion(true);
|
|
}
|
|
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
static void cliRcSmoothing(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
rcSmoothingFilter_t *rcSmoothingData = getRcSmoothingData();
|
|
cliPrint("# RC Smoothing Type: ");
|
|
if (rxConfig()->rc_smoothing_mode) {
|
|
cliPrintLine("FILTER");
|
|
if (rcSmoothingAutoCalculate()) {
|
|
cliPrint("# Detected Rx frequency: ");
|
|
if (getRxRateValid()) {
|
|
cliPrintLinef("%dHz", lrintf(rcSmoothingData->smoothedRxRateHz));
|
|
} else {
|
|
cliPrintLine("NO SIGNAL");
|
|
}
|
|
}
|
|
cliPrintf("# Active setpoint cutoff: %dhz ", rcSmoothingData->setpointCutoffFrequency);
|
|
if (rcSmoothingData->setpointCutoffSetting) {
|
|
cliPrintLine("(manual)");
|
|
} else {
|
|
cliPrintLine("(auto)");
|
|
}
|
|
cliPrintf("# Active FF cutoff: %dhz ", rcSmoothingData->feedforwardCutoffFrequency);
|
|
if (rcSmoothingData->feedforwardCutoffSetting) {
|
|
cliPrintLine("(manual)");
|
|
} else {
|
|
cliPrintLine("(auto)");
|
|
}
|
|
cliPrintf("# Active throttle cutoff: %dhz ", rcSmoothingData->throttleCutoffFrequency);
|
|
if (rcSmoothingData->throttleCutoffSetting) {
|
|
cliPrintLine("(manual)");
|
|
} else {
|
|
cliPrintLine("(auto)");
|
|
}
|
|
} else {
|
|
cliPrintLine("OFF");
|
|
}
|
|
}
|
|
#endif // USE_RC_SMOOTHING_FILTER
|
|
|
|
#if defined(USE_RESOURCE_MGMT)
|
|
|
|
#define RESOURCE_VALUE_MAX_INDEX(x) ((x) == 0 ? 1 : (x))
|
|
|
|
typedef struct {
|
|
const uint8_t owner;
|
|
pgn_t pgn;
|
|
uint8_t stride;
|
|
uint8_t offset;
|
|
const uint8_t maxIndex;
|
|
} cliResourceValue_t;
|
|
|
|
// Handy macros for keeping the table tidy.
|
|
// DEFS : Single entry
|
|
// DEFA : Array of uint8_t (stride = 1)
|
|
// DEFW : Wider stride case; array of structs.
|
|
|
|
#define DEFS(owner, pgn, type, member) \
|
|
{ owner, pgn, 0, offsetof(type, member), 0 }
|
|
|
|
#define DEFA(owner, pgn, type, member, max) \
|
|
{ owner, pgn, sizeof(ioTag_t), offsetof(type, member), max }
|
|
|
|
#define DEFW(owner, pgn, type, member, max) \
|
|
{ owner, pgn, sizeof(type), offsetof(type, member), max }
|
|
|
|
const cliResourceValue_t resourceTable[] = {
|
|
#if defined(USE_BEEPER)
|
|
DEFS( OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, beeperDevConfig_t, ioTag ),
|
|
#endif
|
|
DEFA( OWNER_MOTOR, PG_MOTOR_CONFIG, motorConfig_t, dev.ioTags[0], MAX_SUPPORTED_MOTORS ),
|
|
#if defined(USE_SERVOS)
|
|
DEFA( OWNER_SERVO, PG_SERVO_CONFIG, servoConfig_t, dev.ioTags[0], MAX_SUPPORTED_SERVOS ),
|
|
#endif
|
|
#if defined(USE_RX_PPM)
|
|
DEFS( OWNER_PPMINPUT, PG_PPM_CONFIG, ppmConfig_t, ioTag ),
|
|
#endif
|
|
#if defined(USE_RX_PWM)
|
|
DEFA( OWNER_PWMINPUT, PG_PWM_CONFIG, pwmConfig_t, ioTags[0], PWM_INPUT_PORT_COUNT ),
|
|
#endif
|
|
#if defined(USE_RANGEFINDER_HCSR04)
|
|
DEFS( OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, sonarConfig_t, triggerTag ),
|
|
DEFS( OWNER_SONAR_ECHO, PG_SONAR_CONFIG, sonarConfig_t, echoTag ),
|
|
#endif
|
|
#if defined(USE_LED_STRIP)
|
|
DEFS( OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, ledStripConfig_t, ioTag ),
|
|
#endif
|
|
#if defined(USE_UART)
|
|
DEFA( OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
|
|
DEFA( OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
|
|
#endif
|
|
#if defined(USE_INVERTER)
|
|
DEFA( OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagInverter[RESOURCE_UART_OFFSET], RESOURCE_UART_COUNT ),
|
|
// LPUART and SOFTSERIAL don't need external inversion
|
|
#endif
|
|
#if defined(USE_SOFTSERIAL)
|
|
DEFA( OWNER_SOFTSERIAL_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_SOFTSERIAL_OFFSET], RESOURCE_SOFTSERIAL_COUNT ),
|
|
DEFA( OWNER_SOFTSERIAL_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_SOFTSERIAL_OFFSET], RESOURCE_SOFTSERIAL_COUNT ),
|
|
#endif
|
|
#if defined(USE_LPUART)
|
|
DEFA( OWNER_LPUART_TX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagTx[RESOURCE_LPUART_OFFSET], RESOURCE_LPUART_COUNT ),
|
|
DEFA( OWNER_LPUART_RX, PG_SERIAL_PIN_CONFIG, serialPinConfig_t, ioTagRx[RESOURCE_LPUART_OFFSET], RESOURCE_LPUART_COUNT ),
|
|
#endif
|
|
#ifdef USE_I2C
|
|
DEFW( OWNER_I2C_SCL, PG_I2C_CONFIG, i2cConfig_t, ioTagScl, I2CDEV_COUNT ),
|
|
DEFW( OWNER_I2C_SDA, PG_I2C_CONFIG, i2cConfig_t, ioTagSda, I2CDEV_COUNT ),
|
|
#endif
|
|
#if !defined(USE_VIRTUAL_LED)
|
|
DEFA( OWNER_LED, PG_STATUS_LED_CONFIG, statusLedConfig_t, ioTags[0], STATUS_LED_COUNT ),
|
|
#endif
|
|
#ifdef USE_SPEKTRUM_BIND
|
|
DEFS( OWNER_RX_BIND, PG_RX_CONFIG, rxConfig_t, spektrum_bind_pin_override_ioTag ),
|
|
DEFS( OWNER_RX_BIND_PLUG, PG_RX_CONFIG, rxConfig_t, spektrum_bind_plug_ioTag ),
|
|
#endif
|
|
#ifdef USE_TRANSPONDER
|
|
DEFS( OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, transponderConfig_t, ioTag ),
|
|
#endif
|
|
#ifdef USE_SPI
|
|
DEFW( OWNER_SPI_SCK, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagSck, SPIDEV_COUNT ),
|
|
DEFW( OWNER_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMiso, SPIDEV_COUNT ),
|
|
DEFW( OWNER_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, ioTagMosi, SPIDEV_COUNT ),
|
|
#endif
|
|
#ifdef USE_ESCSERIAL
|
|
DEFS( OWNER_ESCSERIAL, PG_ESCSERIAL_CONFIG, escSerialConfig_t, ioTag ),
|
|
#endif
|
|
#ifdef USE_CAMERA_CONTROL
|
|
DEFS( OWNER_CAMERA_CONTROL, PG_CAMERA_CONTROL_CONFIG, cameraControlConfig_t, ioTag ),
|
|
#endif
|
|
#ifdef USE_ADC
|
|
DEFS( OWNER_ADC_BATT, PG_ADC_CONFIG, adcConfig_t, vbat.ioTag ),
|
|
DEFS( OWNER_ADC_RSSI, PG_ADC_CONFIG, adcConfig_t, rssi.ioTag ),
|
|
DEFS( OWNER_ADC_CURR, PG_ADC_CONFIG, adcConfig_t, current.ioTag ),
|
|
DEFS( OWNER_ADC_EXT, PG_ADC_CONFIG, adcConfig_t, external1.ioTag ),
|
|
#endif
|
|
#ifdef USE_BARO
|
|
DEFS( OWNER_BARO_CS, PG_BAROMETER_CONFIG, barometerConfig_t, baro_spi_csn ),
|
|
DEFS( OWNER_BARO_EOC, PG_BAROMETER_CONFIG, barometerConfig_t, baro_eoc_tag ),
|
|
DEFS( OWNER_BARO_XCLR, PG_BAROMETER_CONFIG, barometerConfig_t, baro_xclr_tag ),
|
|
#endif
|
|
#ifdef USE_MAG
|
|
DEFS( OWNER_COMPASS_CS, PG_COMPASS_CONFIG, compassConfig_t, mag_spi_csn ),
|
|
#ifdef USE_MAG_DATA_READY_SIGNAL
|
|
DEFS( OWNER_COMPASS_EXTI, PG_COMPASS_CONFIG, compassConfig_t, interruptTag ),
|
|
#endif
|
|
#endif
|
|
#ifdef USE_SDCARD_SPI
|
|
DEFS( OWNER_SDCARD_CS, PG_SDCARD_CONFIG, sdcardConfig_t, chipSelectTag ),
|
|
#endif
|
|
#ifdef USE_SDCARD
|
|
DEFS( OWNER_SDCARD_DETECT, PG_SDCARD_CONFIG, sdcardConfig_t, cardDetectTag ),
|
|
#endif
|
|
#if defined(STM32H7) && defined(USE_SDCARD_SDIO)
|
|
DEFS( OWNER_SDIO_CK, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CKPin ),
|
|
DEFS( OWNER_SDIO_CMD, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, CMDPin ),
|
|
DEFS( OWNER_SDIO_D0, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D0Pin ),
|
|
DEFS( OWNER_SDIO_D1, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D1Pin ),
|
|
DEFS( OWNER_SDIO_D2, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D2Pin ),
|
|
DEFS( OWNER_SDIO_D3, PG_SDIO_PIN_CONFIG, sdioPinConfig_t, D3Pin ),
|
|
#endif
|
|
#ifdef USE_PINIO
|
|
DEFA( OWNER_PINIO, PG_PINIO_CONFIG, pinioConfig_t, ioTag, PINIO_COUNT ),
|
|
#endif
|
|
#if defined(USE_USB_MSC)
|
|
DEFS( OWNER_USB_MSC_PIN, PG_USB_CONFIG, usbDev_t, mscButtonPin ),
|
|
#endif
|
|
#ifdef USE_FLASH_CHIP
|
|
DEFS( OWNER_FLASH_CS, PG_FLASH_CONFIG, flashConfig_t, csTag ),
|
|
#endif
|
|
#ifdef USE_MAX7456
|
|
DEFS( OWNER_OSD_CS, PG_MAX7456_CONFIG, max7456Config_t, csTag ),
|
|
#endif
|
|
#ifdef USE_RX_SPI
|
|
DEFS( OWNER_RX_SPI_CS, PG_RX_SPI_CONFIG, rxSpiConfig_t, csnTag ),
|
|
DEFS( OWNER_RX_SPI_EXTI, PG_RX_SPI_CONFIG, rxSpiConfig_t, extiIoTag ),
|
|
DEFS( OWNER_RX_SPI_BIND, PG_RX_SPI_CONFIG, rxSpiConfig_t, bindIoTag ),
|
|
DEFS( OWNER_RX_SPI_LED, PG_RX_SPI_CONFIG, rxSpiConfig_t, ledIoTag ),
|
|
#if defined(USE_RX_CC2500) && defined(USE_RX_CC2500_SPI_PA_LNA)
|
|
DEFS( OWNER_RX_SPI_CC2500_TX_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, txEnIoTag ),
|
|
DEFS( OWNER_RX_SPI_CC2500_LNA_EN, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, lnaEnIoTag ),
|
|
#if defined(USE_RX_CC2500_SPI_DIVERSITY)
|
|
DEFS( OWNER_RX_SPI_CC2500_ANT_SEL, PG_RX_CC2500_SPI_CONFIG, rxCc2500SpiConfig_t, antSelIoTag ),
|
|
#endif
|
|
#endif
|
|
#if defined(USE_RX_EXPRESSLRS)
|
|
DEFS( OWNER_RX_SPI_EXPRESSLRS_RESET, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, resetIoTag ),
|
|
DEFS( OWNER_RX_SPI_EXPRESSLRS_BUSY, PG_RX_EXPRESSLRS_SPI_CONFIG, rxExpressLrsSpiConfig_t, busyIoTag ),
|
|
#endif
|
|
#endif
|
|
DEFW( OWNER_GYRO_EXTI, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, extiTag, MAX_GYRODEV_COUNT ),
|
|
DEFW( OWNER_GYRO_CS, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, csnTag, MAX_GYRODEV_COUNT ),
|
|
#if defined(USE_GYRO_CLKIN)
|
|
DEFW( OWNER_GYRO_CLKIN, PG_GYRO_DEVICE_CONFIG, gyroDeviceConfig_t, clkIn, MAX_GYRODEV_COUNT),
|
|
#endif
|
|
#ifdef USE_USB_DETECT
|
|
DEFS( OWNER_USB_DETECT, PG_USB_CONFIG, usbDev_t, detectPin ),
|
|
#endif
|
|
#ifdef USE_VTX_RTC6705
|
|
DEFS( OWNER_VTX_POWER, PG_VTX_IO_CONFIG, vtxIOConfig_t, powerTag ),
|
|
DEFS( OWNER_VTX_CS, PG_VTX_IO_CONFIG, vtxIOConfig_t, csTag ),
|
|
DEFS( OWNER_VTX_DATA, PG_VTX_IO_CONFIG, vtxIOConfig_t, dataTag ),
|
|
DEFS( OWNER_VTX_CLK, PG_VTX_IO_CONFIG, vtxIOConfig_t, clockTag ),
|
|
#endif
|
|
#ifdef USE_PIN_PULL_UP_DOWN
|
|
DEFA( OWNER_PULLUP, PG_PULLUP_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
|
|
DEFA( OWNER_PULLDOWN, PG_PULLDOWN_CONFIG, pinPullUpDownConfig_t, ioTag, PIN_PULL_UP_DOWN_COUNT ),
|
|
#endif
|
|
};
|
|
|
|
#undef DEFS
|
|
#undef DEFA
|
|
#undef DEFW
|
|
|
|
static ioTag_t* getIoTag(const cliResourceValue_t value, uint8_t index)
|
|
{
|
|
const pgRegistry_t* rec = pgFind(value.pgn);
|
|
return (ioTag_t *)(rec->address + value.stride * index + value.offset);
|
|
}
|
|
|
|
static void printResource(dumpFlags_t dumpMask, const char *headingStr)
|
|
{
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
|
|
const char* owner = ownerNames[resourceTable[i].owner];
|
|
const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
|
|
const void *currentConfig;
|
|
const void *defaultConfig;
|
|
if (isReadingConfigFromCopy()) {
|
|
currentConfig = pg->copy;
|
|
defaultConfig = pg->address;
|
|
} else {
|
|
currentConfig = pg->address;
|
|
defaultConfig = NULL;
|
|
}
|
|
|
|
for (int index = 0; index < RESOURCE_VALUE_MAX_INDEX(resourceTable[i].maxIndex); index++) {
|
|
const ioTag_t ioTag = *(ioTag_t *)((const uint8_t *)currentConfig + resourceTable[i].stride * index + resourceTable[i].offset);
|
|
ioTag_t ioTagDefault = 0;
|
|
if (defaultConfig) {
|
|
ioTagDefault = *(ioTag_t *)((const uint8_t *)defaultConfig + resourceTable[i].stride * index + resourceTable[i].offset);
|
|
}
|
|
|
|
const bool equalsDefault = ioTag == ioTagDefault;
|
|
const char *format = "resource %s %d %c%02d";
|
|
const char *formatUnassigned = "resource %s %d NONE";
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
if (ioTagDefault) {
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
|
|
} else if (defaultConfig) {
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
|
|
}
|
|
if (ioTag) {
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
|
|
} else if (!(dumpMask & HIDE_UNUSED)) {
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void printResourceOwner(uint8_t owner, uint8_t index)
|
|
{
|
|
cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
|
|
|
|
if (resourceTable[owner].maxIndex > 0) {
|
|
cliPrintf(" %d", RESOURCE_INDEX(index));
|
|
}
|
|
}
|
|
|
|
static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
|
|
{
|
|
if (!newTag) {
|
|
return;
|
|
}
|
|
|
|
const char * format = "\r\nNOTE: %c%02d already assigned to ";
|
|
for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
|
|
for (int i = 0; i < RESOURCE_VALUE_MAX_INDEX(resourceTable[r].maxIndex); i++) {
|
|
ioTag_t *tag = getIoTag(resourceTable[r], i);
|
|
if (*tag == newTag) {
|
|
bool cleared = false;
|
|
if (r == resourceIndex) {
|
|
if (i == index) {
|
|
continue;
|
|
}
|
|
*tag = IO_TAG_NONE;
|
|
cleared = true;
|
|
}
|
|
|
|
cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
|
|
|
|
printResourceOwner(r, i);
|
|
|
|
if (cleared) {
|
|
cliPrintf(". ");
|
|
printResourceOwner(r, i);
|
|
cliPrintf(" disabled");
|
|
}
|
|
|
|
cliPrintLine(".");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool strToPin(char *ptr, ioTag_t *tag)
|
|
{
|
|
if (strcasecmp(ptr, "NONE") == 0) {
|
|
*tag = IO_TAG_NONE;
|
|
|
|
return true;
|
|
} else {
|
|
const unsigned port = (*ptr >= 'a') ? *ptr - 'a' : *ptr - 'A';
|
|
if (port < 8) {
|
|
ptr++;
|
|
|
|
char *end;
|
|
const long pin = strtol(ptr, &end, 10);
|
|
if (end != ptr && pin >= 0 && pin < DEFIO_PORT_PINS) {
|
|
*tag = DEFIO_TAG_MAKE(port, pin);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef USE_DMA
|
|
static void showDma(void)
|
|
{
|
|
cliPrintLinefeed();
|
|
|
|
#ifdef MINIMAL_CLI
|
|
cliPrintLine("DMA:");
|
|
#else
|
|
cliPrintLine("Currently active DMA:");
|
|
cliRepeat('-', 20);
|
|
#endif
|
|
for (int i = DMA_FIRST_HANDLER; i <= DMA_LAST_HANDLER; i++) {
|
|
const resourceOwner_t *owner = dmaGetOwner(i);
|
|
|
|
cliPrintf(DMA_OUTPUT_STRING, DMA_DEVICE_NO(i), DMA_DEVICE_INDEX(i));
|
|
if (owner->resourceIndex > 0) {
|
|
cliPrintLinef(" %s %d", ownerNames[owner->owner], owner->resourceIndex);
|
|
} else {
|
|
cliPrintLinef(" %s", ownerNames[owner->owner]);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_DMA_SPEC
|
|
|
|
typedef struct dmaoptEntry_s {
|
|
char *device;
|
|
dmaPeripheral_e peripheral;
|
|
pgn_t pgn;
|
|
uint8_t stride;
|
|
uint8_t offset;
|
|
uint8_t maxIndex;
|
|
uint32_t presenceMask;
|
|
} dmaoptEntry_t;
|
|
|
|
#define MASK_IGNORED (0)
|
|
|
|
// Handy macros for keeping the table tidy.
|
|
// DEFS : Single entry
|
|
// DEFA : Array of uint8_t (stride = 1)
|
|
// DEFW : Wider stride case; array of structs.
|
|
// DEFW_OFS: array of structs, starting at offset ofs
|
|
|
|
#define DEFS(device, peripheral, pgn, type, member) \
|
|
{ device, peripheral, pgn, 0, offsetof(type, member), 0, MASK_IGNORED }
|
|
|
|
#define DEFA(device, peripheral, pgn, type, member, max, mask) \
|
|
{ device, peripheral, pgn, sizeof(uint8_t), offsetof(type, member), max, mask }
|
|
|
|
#define DEFW(device, peripheral, pgn, type, member, max, mask) \
|
|
DEFW_OFS(device, peripheral, pgn, type, member, 0, max, mask)
|
|
|
|
#define DEFW_OFS(device, peripheral, pgn, type, member, ofs, max, mask) \
|
|
{ device, peripheral, pgn, sizeof(type), offsetof(type, member) + (ofs) * sizeof(type), max, mask }
|
|
|
|
dmaoptEntry_t dmaoptEntryTable[] = {
|
|
DEFW("SPI_SDO", DMA_PERIPH_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
|
|
DEFW("SPI_SDI", DMA_PERIPH_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
|
|
// SPI_TX/SPI_RX for backwards compatibility with unified configs defined for 4.2.x
|
|
DEFW("SPI_TX", DMA_PERIPH_SPI_SDO, PG_SPI_PIN_CONFIG, spiPinConfig_t, txDmaopt, SPIDEV_COUNT, MASK_IGNORED),
|
|
DEFW("SPI_RX", DMA_PERIPH_SPI_SDI, PG_SPI_PIN_CONFIG, spiPinConfig_t, rxDmaopt, SPIDEV_COUNT, MASK_IGNORED),
|
|
DEFA("ADC", DMA_PERIPH_ADC, PG_ADC_CONFIG, adcConfig_t, dmaopt, ADCDEV_COUNT, MASK_IGNORED),
|
|
DEFS("SDIO", DMA_PERIPH_SDIO, PG_SDIO_CONFIG, sdioConfig_t, dmaopt),
|
|
#ifdef USE_UART
|
|
DEFW_OFS("UART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, RESOURCE_UART_OFFSET, RESOURCE_UART_COUNT, MASK_IGNORED),
|
|
DEFW_OFS("UART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, RESOURCE_UART_OFFSET, RESOURCE_UART_COUNT, MASK_IGNORED),
|
|
#endif
|
|
#ifdef USE_LPUART
|
|
DEFW_OFS("LPUART_TX", DMA_PERIPH_UART_TX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, txDmaopt, RESOURCE_LPUART_OFFSET, RESOURCE_LPUART_COUNT, MASK_IGNORED),
|
|
DEFW_OFS("LPUART_RX", DMA_PERIPH_UART_RX, PG_SERIAL_UART_CONFIG, serialUartConfig_t, rxDmaopt, RESOURCE_LPUART_OFFSET, RESOURCE_LPUART_COUNT, MASK_IGNORED),
|
|
#endif
|
|
#if defined(STM32H7) || defined(STM32G4)
|
|
DEFW("TIMUP", DMA_PERIPH_TIMUP, PG_TIMER_UP_CONFIG, timerUpConfig_t, dmaopt, HARDWARE_TIMER_DEFINITION_COUNT, TIMUP_TIMERS),
|
|
#endif
|
|
};
|
|
|
|
#undef DEFS
|
|
#undef DEFA
|
|
#undef DEFW
|
|
|
|
#define DMA_OPT_UI_INDEX(i) ((i) + 1)
|
|
#define DMA_OPT_STRING_BUFSIZE 5
|
|
|
|
#if !defined(DMA_CHANREQ_STRING)
|
|
#define DMA_CHANREQ_STRING "Channel"
|
|
#endif
|
|
|
|
#if !defined(DMA_STCH_STRING)
|
|
#define DMA_STCH_STRING "Channel"
|
|
#endif
|
|
|
|
#define DMASPEC_FORMAT_STRING "DMA%d " DMA_STCH_STRING " %d " DMA_CHANREQ_STRING " %d"
|
|
|
|
static void optToString(int optval, char *buf)
|
|
{
|
|
if (optval == DMA_OPT_UNUSED) {
|
|
memcpy(buf, "NONE", DMA_OPT_STRING_BUFSIZE);
|
|
} else {
|
|
tfp_sprintf(buf, "%d", optval);
|
|
}
|
|
}
|
|
|
|
static void printPeripheralDmaoptDetails(dmaoptEntry_t *entry, int index, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
|
|
{
|
|
// We compute number to display for different peripherals in advance.
|
|
// This is done to deal with TIMUP which numbered non-contiguously.
|
|
// Note that using timerGetNumberByIndex is not a generic solution,
|
|
// but we are lucky that TIMUP is the only peripheral with non-contiguous numbering.
|
|
|
|
int uiIndex;
|
|
|
|
if (entry->presenceMask) {
|
|
uiIndex = timerGetNumberByIndex(index);
|
|
} else {
|
|
uiIndex = DMA_OPT_UI_INDEX(index);
|
|
}
|
|
|
|
if (dmaopt != DMA_OPT_UNUSED) {
|
|
printValue(dumpMask, equalsDefault,
|
|
"dma %s %d %d",
|
|
entry->device, uiIndex, dmaopt);
|
|
|
|
const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, dmaopt);
|
|
dmaCode_t dmaCode = 0;
|
|
if (dmaChannelSpec) {
|
|
dmaCode = dmaChannelSpec->code;
|
|
}
|
|
printValue(dumpMask, equalsDefault,
|
|
"# %s %d: " DMASPEC_FORMAT_STRING,
|
|
entry->device, uiIndex, DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode));
|
|
} else if (!(dumpMask & HIDE_UNUSED)) {
|
|
printValue(dumpMask, equalsDefault,
|
|
"dma %s %d NONE",
|
|
entry->device, uiIndex);
|
|
}
|
|
}
|
|
|
|
static const char *printPeripheralDmaopt(dmaoptEntry_t *entry, int index, dumpFlags_t dumpMask, const char *headingStr)
|
|
{
|
|
const pgRegistry_t* pg = pgFind(entry->pgn);
|
|
const void *currentConfig;
|
|
const void *defaultConfig;
|
|
|
|
if (isReadingConfigFromCopy()) {
|
|
currentConfig = pg->copy;
|
|
defaultConfig = pg->address;
|
|
} else {
|
|
currentConfig = pg->address;
|
|
defaultConfig = NULL;
|
|
}
|
|
|
|
dmaoptValue_t currentOpt = *(dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
|
|
dmaoptValue_t defaultOpt;
|
|
|
|
if (defaultConfig) {
|
|
defaultOpt = *(dmaoptValue_t *)((uint8_t *)defaultConfig + entry->stride * index + entry->offset);
|
|
} else {
|
|
defaultOpt = DMA_OPT_UNUSED;
|
|
}
|
|
|
|
bool equalsDefault = currentOpt == defaultOpt;
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
|
|
if (defaultConfig) {
|
|
printPeripheralDmaoptDetails(entry, index, defaultOpt, equalsDefault, dumpMask, cliDefaultPrintLinef);
|
|
}
|
|
|
|
printPeripheralDmaoptDetails(entry, index, currentOpt, equalsDefault, dumpMask, cliDumpPrintLinef);
|
|
return headingStr;
|
|
}
|
|
|
|
#if defined(USE_TIMER_MGMT)
|
|
static void printTimerDmaoptDetails(const ioTag_t ioTag, const timerHardware_t *timer, const dmaoptValue_t dmaopt, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
|
|
{
|
|
const char *format = "dma pin %c%02d %d";
|
|
|
|
if (dmaopt != DMA_OPT_UNUSED) {
|
|
const bool printDetails = printValue(dumpMask, equalsDefault, format,
|
|
IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
|
|
dmaopt
|
|
);
|
|
|
|
if (printDetails) {
|
|
const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, dmaopt);
|
|
if (dmaChannelSpec) {
|
|
dmaCode_t dmaCode = dmaChannelSpec->code;
|
|
printValue(dumpMask, false,
|
|
"# pin %c%02d: " DMASPEC_FORMAT_STRING,
|
|
IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
|
|
DMA_CODE_CONTROLLER(dmaCode), DMA_CODE_STREAM(dmaCode), DMA_CODE_CHANNEL(dmaCode)
|
|
);
|
|
}
|
|
}
|
|
} else if (!(dumpMask & HIDE_UNUSED)) {
|
|
printValue(dumpMask, equalsDefault,
|
|
"dma pin %c%02d NONE",
|
|
IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag)
|
|
);
|
|
}
|
|
}
|
|
|
|
static const char *printTimerDmaopt(const timerIOConfig_t *currentConfig, const timerIOConfig_t *defaultConfig, unsigned index, dumpFlags_t dumpMask, bool tagsInUse[], const char *headingStr)
|
|
{
|
|
const ioTag_t ioTag = currentConfig[index].ioTag;
|
|
|
|
if (!ioTag) {
|
|
return headingStr;
|
|
}
|
|
|
|
const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, currentConfig[index].index);
|
|
const dmaoptValue_t dmaopt = currentConfig[index].dmaopt;
|
|
|
|
dmaoptValue_t defaultDmaopt = DMA_OPT_UNUSED;
|
|
bool equalsDefault = defaultDmaopt == dmaopt;
|
|
if (defaultConfig) {
|
|
for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
|
|
if (defaultConfig[i].ioTag == ioTag) {
|
|
defaultDmaopt = defaultConfig[i].dmaopt;
|
|
|
|
// We need to check timer as well here to get 'default' DMA options for non-default timers printed, because setting the timer resets the DMA option.
|
|
equalsDefault = (defaultDmaopt == dmaopt) && (defaultConfig[i].index == currentConfig[index].index || dmaopt == DMA_OPT_UNUSED);
|
|
|
|
tagsInUse[index] = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
|
|
if (defaultConfig) {
|
|
printTimerDmaoptDetails(ioTag, timer, defaultDmaopt, equalsDefault, dumpMask, cliDefaultPrintLinef);
|
|
}
|
|
|
|
printTimerDmaoptDetails(ioTag, timer, dmaopt, equalsDefault, dumpMask, cliDumpPrintLinef);
|
|
return headingStr;
|
|
}
|
|
#endif
|
|
|
|
static void printDmaopt(dumpFlags_t dumpMask, const char *headingStr)
|
|
{
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
for (size_t i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
|
|
dmaoptEntry_t *entry = &dmaoptEntryTable[i];
|
|
for (int index = 0; index < entry->maxIndex; index++) {
|
|
headingStr = printPeripheralDmaopt(entry, index, dumpMask, headingStr);
|
|
}
|
|
}
|
|
|
|
#if defined(USE_TIMER_MGMT)
|
|
const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
|
|
const timerIOConfig_t *currentConfig;
|
|
const timerIOConfig_t *defaultConfig;
|
|
|
|
if (isReadingConfigFromCopy()) {
|
|
currentConfig = (timerIOConfig_t *)pg->copy;
|
|
defaultConfig = (timerIOConfig_t *)pg->address;
|
|
} else {
|
|
currentConfig = (timerIOConfig_t *)pg->address;
|
|
defaultConfig = NULL;
|
|
}
|
|
|
|
bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
|
|
for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
|
|
headingStr = printTimerDmaopt(currentConfig, defaultConfig, i, dumpMask, tagsInUse, headingStr);
|
|
}
|
|
|
|
if (defaultConfig) {
|
|
for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
|
|
if (!tagsInUse[i] && defaultConfig[i].ioTag && defaultConfig[i].dmaopt != DMA_OPT_UNUSED) {
|
|
const timerHardware_t *timer = timerGetByTagAndIndex(defaultConfig[i].ioTag, defaultConfig[i].index);
|
|
headingStr = cliPrintSectionHeading(dumpMask, true, headingStr);
|
|
printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, defaultConfig[i].dmaopt, false, dumpMask, cliDefaultPrintLinef);
|
|
|
|
printTimerDmaoptDetails(defaultConfig[i].ioTag, timer, DMA_OPT_UNUSED, false, dumpMask, cliDumpPrintLinef);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void cliDmaopt(const char *cmdName, char *cmdline)
|
|
{
|
|
char *pch = NULL;
|
|
char *saveptr;
|
|
|
|
// Peripheral name or command option
|
|
pch = strtok_r(cmdline, " ", &saveptr);
|
|
if (!pch) {
|
|
printDmaopt(DUMP_MASTER | HIDE_UNUSED, NULL);
|
|
|
|
return;
|
|
} else if (strcasecmp(pch, "list") == 0) {
|
|
cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
|
|
|
|
return;
|
|
}
|
|
|
|
dmaoptEntry_t *entry = NULL;
|
|
for (unsigned i = 0; i < ARRAYLEN(dmaoptEntryTable); i++) {
|
|
if (strcasecmp(pch, dmaoptEntryTable[i].device) == 0) {
|
|
entry = &dmaoptEntryTable[i];
|
|
}
|
|
}
|
|
|
|
if (!entry && strcasecmp(pch, "pin") != 0) {
|
|
cliPrintErrorLinef(cmdName, "BAD DEVICE: %s", pch);
|
|
return;
|
|
}
|
|
|
|
// Index
|
|
dmaoptValue_t orgval = DMA_OPT_UNUSED;
|
|
|
|
int index = 0;
|
|
dmaoptValue_t *optaddr = NULL;
|
|
|
|
ioTag_t ioTag = IO_TAG_NONE;
|
|
#if defined(USE_TIMER_MGMT)
|
|
timerIOConfig_t *timerIoConfig = NULL;
|
|
#endif
|
|
const timerHardware_t *timer = NULL;
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
if (entry) {
|
|
index = pch ? (atoi(pch) - 1) : -1;
|
|
if (index < 0 || index >= entry->maxIndex || (entry->presenceMask != MASK_IGNORED && !(entry->presenceMask & BIT(index + 1)))) {
|
|
cliPrintErrorLinef(cmdName, "BAD INDEX: '%s'", pch ? pch : "");
|
|
return;
|
|
}
|
|
|
|
const pgRegistry_t* pg = pgFind(entry->pgn);
|
|
const void *currentConfig;
|
|
if (isWritingConfigToCopy()) {
|
|
currentConfig = pg->copy;
|
|
} else {
|
|
currentConfig = pg->address;
|
|
}
|
|
optaddr = (dmaoptValue_t *)((uint8_t *)currentConfig + entry->stride * index + entry->offset);
|
|
orgval = *optaddr;
|
|
} else {
|
|
// It's a pin
|
|
if (!pch || !(strToPin(pch, &ioTag) && IOGetByTag(ioTag))) {
|
|
cliPrintErrorLinef(cmdName, "INVALID PIN: '%s'", pch ? pch : "");
|
|
|
|
return;
|
|
}
|
|
|
|
orgval = dmaoptByTag(ioTag);
|
|
#if defined(USE_TIMER_MGMT)
|
|
timerIoConfig = timerIoConfigByTag(ioTag);
|
|
#endif
|
|
timer = timerGetConfiguredByTag(ioTag);
|
|
}
|
|
|
|
// opt or list
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
if (!pch) {
|
|
if (entry) {
|
|
printPeripheralDmaoptDetails(entry, index, *optaddr, true, DUMP_MASTER, cliDumpPrintLinef);
|
|
}
|
|
#if defined(USE_TIMER_MGMT)
|
|
else {
|
|
printTimerDmaoptDetails(ioTag, timer, orgval, true, DUMP_MASTER, cliDumpPrintLinef);
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
} else if (strcasecmp(pch, "list") == 0) {
|
|
// Show possible opts
|
|
const dmaChannelSpec_t *dmaChannelSpec;
|
|
if (entry) {
|
|
for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByPeripheral(entry->peripheral, index, opt)); opt++) {
|
|
cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
|
|
}
|
|
} else {
|
|
for (int opt = 0; (dmaChannelSpec = dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, opt)); opt++) {
|
|
cliPrintLinef("# %d: " DMASPEC_FORMAT_STRING, opt, DMA_CODE_CONTROLLER(dmaChannelSpec->code), DMA_CODE_STREAM(dmaChannelSpec->code), DMA_CODE_CHANNEL(dmaChannelSpec->code));
|
|
}
|
|
}
|
|
|
|
return;
|
|
} else if (pch) {
|
|
int optval;
|
|
if (strcasecmp(pch, "none") == 0) {
|
|
optval = DMA_OPT_UNUSED;
|
|
} else {
|
|
optval = atoi(pch);
|
|
|
|
if (entry) {
|
|
if (!dmaGetChannelSpecByPeripheral(entry->peripheral, index, optval)) {
|
|
cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR %s %d: '%s'", entry->device, DMA_OPT_UI_INDEX(index), pch);
|
|
|
|
return;
|
|
}
|
|
} else {
|
|
if (!dmaGetChannelSpecByTimerValue(timer->tim, timer->channel, optval)) {
|
|
cliPrintErrorLinef(cmdName, "INVALID DMA OPTION FOR PIN %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
char optvalString[DMA_OPT_STRING_BUFSIZE];
|
|
optToString(optval, optvalString);
|
|
|
|
char orgvalString[DMA_OPT_STRING_BUFSIZE];
|
|
optToString(orgval, orgvalString);
|
|
|
|
if (optval != orgval) {
|
|
if (entry) {
|
|
*optaddr = optval;
|
|
|
|
cliPrintLinef("# dma %s %d: changed from %s to %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString, optvalString);
|
|
} else {
|
|
#if defined(USE_TIMER_MGMT)
|
|
timerIoConfig->dmaopt = optval;
|
|
#endif
|
|
|
|
cliPrintLinef("# dma pin %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
|
|
}
|
|
} else {
|
|
if (entry) {
|
|
cliPrintLinef("# dma %s %d: no change: %s", entry->device, DMA_OPT_UI_INDEX(index), orgvalString);
|
|
} else {
|
|
cliPrintLinef("# dma %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),orgvalString);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // USE_DMA_SPEC
|
|
|
|
#ifdef USE_DMA
|
|
static void cliDma(const char *cmdName, char* cmdline)
|
|
{
|
|
int len = strlen(cmdline);
|
|
if (len && strncasecmp(cmdline, "show", len) == 0) {
|
|
showDma();
|
|
|
|
return;
|
|
}
|
|
|
|
#if defined(USE_DMA_SPEC)
|
|
cliDmaopt(cmdName, cmdline);
|
|
#else
|
|
cliShowParseError(cmdName);
|
|
#endif
|
|
}
|
|
#endif
|
|
#endif // USE_RESOURCE_MGMT
|
|
|
|
#ifdef USE_TIMER_MGMT
|
|
static void printTimerDetails(const ioTag_t ioTag, const unsigned timerIndex, const bool equalsDefault, const dumpFlags_t dumpMask, printFn *printValue)
|
|
{
|
|
const char *format = "timer %c%02d AF%d";
|
|
const char *emptyFormat = "timer %c%02d NONE";
|
|
|
|
if (timerIndex > 0) {
|
|
const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, timerIndex);
|
|
const bool printDetails = printValue(dumpMask, equalsDefault, format,
|
|
IO_GPIOPortIdxByTag(ioTag) + 'A',
|
|
IO_GPIOPinIdxByTag(ioTag),
|
|
timer->alternateFunction
|
|
);
|
|
if (printDetails) {
|
|
printValue(dumpMask, false,
|
|
"# pin %c%02d: TIM%d CH%d%s (AF%d)",
|
|
IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
|
|
timerGetTIMNumber(timer->tim),
|
|
CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
|
|
timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : "",
|
|
timer->alternateFunction
|
|
);
|
|
}
|
|
} else {
|
|
printValue(dumpMask, equalsDefault, emptyFormat,
|
|
IO_GPIOPortIdxByTag(ioTag) + 'A',
|
|
IO_GPIOPinIdxByTag(ioTag)
|
|
);
|
|
}
|
|
}
|
|
|
|
static void printTimer(dumpFlags_t dumpMask, const char *headingStr)
|
|
{
|
|
const pgRegistry_t* pg = pgFind(PG_TIMER_IO_CONFIG);
|
|
const timerIOConfig_t *currentConfig;
|
|
const timerIOConfig_t *defaultConfig;
|
|
|
|
headingStr = cliPrintSectionHeading(dumpMask, false, headingStr);
|
|
if (isReadingConfigFromCopy()) {
|
|
currentConfig = (timerIOConfig_t *)pg->copy;
|
|
defaultConfig = (timerIOConfig_t *)pg->address;
|
|
} else {
|
|
currentConfig = (timerIOConfig_t *)pg->address;
|
|
defaultConfig = NULL;
|
|
}
|
|
|
|
bool tagsInUse[MAX_TIMER_PINMAP_COUNT] = { false };
|
|
for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
|
|
const ioTag_t ioTag = currentConfig[i].ioTag;
|
|
|
|
if (!ioTag) {
|
|
continue;
|
|
}
|
|
|
|
const uint8_t timerIndex = currentConfig[i].index;
|
|
|
|
uint8_t defaultTimerIndex = 0;
|
|
if (defaultConfig) {
|
|
for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
|
|
if (defaultConfig[i].ioTag == ioTag) {
|
|
defaultTimerIndex = defaultConfig[i].index;
|
|
tagsInUse[i] = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool equalsDefault = defaultTimerIndex == timerIndex;
|
|
headingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, headingStr);
|
|
if (defaultConfig && defaultTimerIndex) {
|
|
printTimerDetails(ioTag, defaultTimerIndex, equalsDefault, dumpMask, cliDefaultPrintLinef);
|
|
}
|
|
|
|
printTimerDetails(ioTag, timerIndex, equalsDefault, dumpMask, cliDumpPrintLinef);
|
|
}
|
|
|
|
if (defaultConfig) {
|
|
for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
|
|
if (!tagsInUse[i] && defaultConfig[i].ioTag) {
|
|
headingStr = cliPrintSectionHeading(DO_DIFF, true, headingStr);
|
|
printTimerDetails(defaultConfig[i].ioTag, defaultConfig[i].index, false, dumpMask, cliDefaultPrintLinef);
|
|
|
|
printTimerDetails(defaultConfig[i].ioTag, 0, false, dumpMask, cliDumpPrintLinef);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define TIMER_INDEX_UNDEFINED -1
|
|
#define TIMER_AF_STRING_BUFSIZE 5
|
|
|
|
static void alternateFunctionToString(const ioTag_t ioTag, const int index, char *buf)
|
|
{
|
|
const timerHardware_t *timer = timerGetByTagAndIndex(ioTag, index + 1);
|
|
if (!timer) {
|
|
memcpy(buf, "NONE", TIMER_AF_STRING_BUFSIZE);
|
|
} else {
|
|
tfp_sprintf(buf, "AF%d", timer->alternateFunction);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_TIMER_MAP_PRINT
|
|
static void showTimerMap(void)
|
|
{
|
|
cliPrintLinefeed();
|
|
cliPrintLine("Timer Mapping:");
|
|
for (unsigned int i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
|
|
const ioTag_t ioTag = timerIOConfig(i)->ioTag;
|
|
|
|
if (!ioTag) {
|
|
continue;
|
|
}
|
|
|
|
cliPrintLinef(" TIMER_PIN_MAP(%d, P%c%d, %d, %d)",
|
|
i,
|
|
IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag),
|
|
timerIOConfig(i)->index,
|
|
timerIOConfig(i)->dmaopt
|
|
);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void showTimers(void)
|
|
{
|
|
cliPrintLinefeed();
|
|
|
|
#ifdef MINIMAL_CLI
|
|
cliPrintLine("Timers:");
|
|
#else
|
|
cliPrintLine("Currently active Timers:");
|
|
cliRepeat('-', 23);
|
|
#endif
|
|
|
|
int8_t timerNumber;
|
|
for (int i = 0; (timerNumber = timerGetNumberByIndex(i)); i++) {
|
|
cliPrintf("TIM%d:", timerNumber);
|
|
bool timerUsed = false;
|
|
for (unsigned timerIndex = 0; timerIndex < CC_CHANNELS_PER_TIMER; timerIndex++) {
|
|
const timerHardware_t *timer = timerGetAllocatedByNumberAndChannel(timerNumber, CC_CHANNEL_FROM_INDEX(timerIndex));
|
|
const resourceOwner_t *timerOwner = timerGetOwner(timer);
|
|
if (timerOwner->owner) {
|
|
if (!timerUsed) {
|
|
timerUsed = true;
|
|
|
|
cliPrintLinefeed();
|
|
}
|
|
|
|
if (timerOwner->resourceIndex > 0) {
|
|
cliPrintLinef(" CH%d%s: %s %d", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner], timerOwner->resourceIndex);
|
|
} else {
|
|
cliPrintLinef(" CH%d%s: %s", timerIndex + 1, timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : " ", ownerNames[timerOwner->owner]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!timerUsed) {
|
|
cliPrintLine(" FREE");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cliTimer(const char *cmdName, char *cmdline)
|
|
{
|
|
int len = strlen(cmdline);
|
|
|
|
if (len == 0) {
|
|
printTimer(DUMP_MASTER, NULL);
|
|
|
|
return;
|
|
} else if (strncasecmp(cmdline, "list", len) == 0) {
|
|
cliPrintErrorLinef(cmdName, "NOT IMPLEMENTED YET");
|
|
|
|
return;
|
|
#ifdef USE_TIMER_MAP_PRINT
|
|
} else if (strncasecmp(cmdline, "map", len) == 0) {
|
|
showTimerMap();
|
|
|
|
return;
|
|
#endif
|
|
} else if (strncasecmp(cmdline, "show", len) == 0) {
|
|
showTimers();
|
|
|
|
return;
|
|
}
|
|
|
|
char *pch = NULL;
|
|
char *saveptr;
|
|
|
|
ioTag_t ioTag = IO_TAG_NONE;
|
|
pch = strtok_r(cmdline, " ", &saveptr);
|
|
if (!pch || !strToPin(pch, &ioTag)) {
|
|
cliShowParseError(cmdName);
|
|
|
|
return;
|
|
} else if (!IOGetByTag(ioTag)) {
|
|
cliPrintErrorLinef(cmdName, "PIN NOT USED ON BOARD.");
|
|
|
|
return;
|
|
}
|
|
|
|
int timerIOIndex = TIMER_INDEX_UNDEFINED;
|
|
bool isExistingTimerOpt = false;
|
|
/* find existing entry, or go for next available */
|
|
for (unsigned i = 0; i < MAX_TIMER_PINMAP_COUNT; i++) {
|
|
if (timerIOConfig(i)->ioTag == ioTag) {
|
|
timerIOIndex = i;
|
|
isExistingTimerOpt = true;
|
|
|
|
break;
|
|
}
|
|
|
|
/* first available empty slot */
|
|
if (timerIOIndex < 0 && timerIOConfig(i)->ioTag == IO_TAG_NONE) {
|
|
timerIOIndex = i;
|
|
}
|
|
}
|
|
|
|
if (timerIOIndex < 0) {
|
|
cliPrintErrorLinef(cmdName, "PIN TIMER MAP FULL.");
|
|
|
|
return;
|
|
}
|
|
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
if (pch) {
|
|
int timerIndex = TIMER_INDEX_UNDEFINED;
|
|
if (strcasecmp(pch, "list") == 0) {
|
|
/* output the list of available options */
|
|
const timerHardware_t *timer;
|
|
for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
|
|
cliPrintLinef("# AF%d: TIM%d CH%d%s",
|
|
timer->alternateFunction,
|
|
timerGetTIMNumber(timer->tim),
|
|
CC_INDEX_FROM_CHANNEL(timer->channel) + 1,
|
|
timer->output & TIMER_OUTPUT_N_CHANNEL ? "N" : ""
|
|
);
|
|
}
|
|
|
|
return;
|
|
} else if (strncasecmp(pch, "af", 2) == 0) {
|
|
unsigned alternateFunction = atoi(&pch[2]);
|
|
|
|
const timerHardware_t *timer;
|
|
for (unsigned index = 0; (timer = timerGetByTagAndIndex(ioTag, index + 1)); index++) {
|
|
if (timer->alternateFunction == alternateFunction) {
|
|
timerIndex = index;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!timer) {
|
|
cliPrintErrorLinef(cmdName, "INVALID ALTERNATE FUNCTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
|
|
|
|
return;
|
|
}
|
|
} else if (strcasecmp(pch, "none") != 0) {
|
|
cliPrintErrorLinef(cmdName, "INVALID TIMER OPTION FOR %c%02d: '%s'", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), pch);
|
|
|
|
return;
|
|
}
|
|
|
|
int oldTimerIndex = isExistingTimerOpt ? timerIOConfig(timerIOIndex)->index - 1 : -1;
|
|
timerIOConfigMutable(timerIOIndex)->ioTag = timerIndex == TIMER_INDEX_UNDEFINED ? IO_TAG_NONE : ioTag;
|
|
timerIOConfigMutable(timerIOIndex)->index = timerIndex + 1;
|
|
timerIOConfigMutable(timerIOIndex)->dmaopt = DMA_OPT_UNUSED;
|
|
|
|
char optvalString[DMA_OPT_STRING_BUFSIZE];
|
|
alternateFunctionToString(ioTag, timerIndex, optvalString);
|
|
|
|
char orgvalString[DMA_OPT_STRING_BUFSIZE];
|
|
alternateFunctionToString(ioTag, oldTimerIndex, orgvalString);
|
|
|
|
if (timerIndex == oldTimerIndex) {
|
|
cliPrintLinef("# timer %c%02d: no change: %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString);
|
|
} else {
|
|
cliPrintLinef("# timer %c%02d: changed from %s to %s", IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag), orgvalString, optvalString);
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
printTimerDetails(ioTag, timerIOConfig(timerIOIndex)->index, false, DUMP_MASTER, cliDumpPrintLinef);
|
|
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_RESOURCE_MGMT)
|
|
static void cliResource(const char *cmdName, char *cmdline)
|
|
{
|
|
char *pch = NULL;
|
|
char *saveptr;
|
|
|
|
pch = strtok_r(cmdline, " ", &saveptr);
|
|
if (!pch) {
|
|
printResource(DUMP_MASTER | HIDE_UNUSED, NULL);
|
|
|
|
return;
|
|
} else if (strcasecmp(pch, "show") == 0) {
|
|
#ifdef MINIMAL_CLI
|
|
cliPrintLine("IO");
|
|
#else
|
|
cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
|
|
cliRepeat('-', 20);
|
|
#endif
|
|
for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
|
|
const char* owner;
|
|
owner = ownerNames[ioRecs[i].owner];
|
|
|
|
cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
|
|
if (ioRecs[i].index > 0) {
|
|
cliPrintf(" %d", ioRecs[i].index);
|
|
}
|
|
cliPrintLinefeed();
|
|
}
|
|
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
if (strcasecmp(pch, "all") == 0) {
|
|
#if defined(USE_TIMER_MGMT)
|
|
cliTimer(cmdName, "show");
|
|
#endif
|
|
#if defined(USE_DMA)
|
|
cliDma(cmdName, "show");
|
|
#endif
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
unsigned resourceIndex = 0;
|
|
for (; ; resourceIndex++) {
|
|
if (resourceIndex >= ARRAYLEN(resourceTable)) {
|
|
cliPrintErrorLinef(cmdName, "INVALID RESOURCE NAME: '%s'", pch);
|
|
return;
|
|
}
|
|
|
|
const char *resourceName = ownerNames[resourceTable[resourceIndex].owner];
|
|
if (strcasecmp(pch, resourceName) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
int index = pch ? atoi(pch) : 0;
|
|
|
|
if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
|
|
if (index <= 0 || index > RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex)) {
|
|
cliShowArgumentRangeError(cmdName, "INDEX", 1, RESOURCE_VALUE_MAX_INDEX(resourceTable[resourceIndex].maxIndex));
|
|
return;
|
|
}
|
|
index -= 1;
|
|
|
|
pch = strtok_r(NULL, " ", &saveptr);
|
|
}
|
|
|
|
ioTag_t *resourceTag = getIoTag(resourceTable[resourceIndex], index);
|
|
|
|
if (pch && strlen(pch) > 0) {
|
|
ioTag_t tag;
|
|
if (strToPin(pch, &tag)) {
|
|
if (!tag) {
|
|
*resourceTag = tag;
|
|
#ifdef MINIMAL_CLI
|
|
cliPrintLine("Freed");
|
|
#else
|
|
cliPrintLine("Resource is freed");
|
|
#endif
|
|
return;
|
|
} else {
|
|
ioRec_t *rec = IO_Rec(IOGetByTag(tag));
|
|
if (rec) {
|
|
*resourceTag = tag;
|
|
resourceCheck(resourceIndex, index, tag);
|
|
#ifdef MINIMAL_CLI
|
|
cliPrintLinef(" %c%02d set", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
|
|
#else
|
|
cliPrintLinef("\r\nResource is set to %c%02d", IO_GPIOPortIdx(rec) + 'A', IO_GPIOPinIdx(rec));
|
|
#endif
|
|
} else {
|
|
cliShowParseError(cmdName);
|
|
}
|
|
}
|
|
} else {
|
|
cliPrintErrorLinef(cmdName, "Failed to parse '%s' as pin", pch);
|
|
}
|
|
} else {
|
|
ioTag_t tag = *resourceTag;
|
|
char ioName[5];
|
|
if (tag) {
|
|
tfp_sprintf(ioName, "%c%02d", IO_GPIOPortIdxByTag(tag) + 'A', IO_GPIOPinIdxByTag(tag));
|
|
}
|
|
cliPrintLinef("# resource %s %d %s", ownerNames[resourceTable[resourceIndex].owner], RESOURCE_INDEX(index), tag ? ioName : "NONE");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_DSHOT_TELEMETRY
|
|
|
|
static void cliDshotTelemetryInfo(const char *cmdName, char *cmdline)
|
|
{
|
|
UNUSED(cmdName);
|
|
UNUSED(cmdline);
|
|
|
|
if (useDshotTelemetry) {
|
|
cliPrintLinef("Dshot reads: %u", dshotTelemetryState.readCount);
|
|
cliPrintLinef("Dshot invalid pkts: %u", dshotTelemetryState.invalidPacketCount);
|
|
int32_t directionChangeCycles = cmp32(dshotDMAHandlerCycleCounters.changeDirectionCompletedAt, dshotDMAHandlerCycleCounters.irqAt);
|
|
int32_t directionChangeDurationUs = clockCyclesToMicros(directionChangeCycles);
|
|
cliPrintLinef("Dshot directionChange cycles: %u, micros: %u", directionChangeCycles, directionChangeDurationUs);
|
|
cliPrintLinefeed();
|
|
|
|
#ifdef USE_DSHOT_TELEMETRY_STATS
|
|
cliPrintLine("Motor Type eRPM RPM Hz Invalid TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
|
|
cliPrintLine("===== ====== ====== ====== ====== ======= ====== ====== ====== ====== ====== ====== ======");
|
|
#else
|
|
cliPrintLine("Motor Type eRPM RPM Hz TEMP VCC CURR ST/EV DBG1 DBG2 DBG3");
|
|
cliPrintLine("===== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======");
|
|
#endif
|
|
|
|
for (uint8_t i = 0; i < getMotorCount(); i++) {
|
|
const uint16_t erpm = getDshotErpm(i);
|
|
const uint16_t rpm = lrintf(getDshotRpm(i));
|
|
|
|
cliPrintf("%5d %c%c%c%c%c %6d %6d %6d",
|
|
i + 1,
|
|
((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_eRPM)) ? 'R' : '-'),
|
|
((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_TEMPERATURE)) ? 'T' : '-'),
|
|
((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_VOLTAGE)) ? 'V' : '-'),
|
|
((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_CURRENT)) ? 'C' : '-'),
|
|
((dshotTelemetryState.motorState[i].telemetryTypes & (1 << DSHOT_TELEMETRY_TYPE_STATE_EVENTS)) ? 'S' : '-'),
|
|
erpm * 100, rpm, rpm / 60);
|
|
|
|
#ifdef USE_DSHOT_TELEMETRY_STATS
|
|
if (isDshotMotorTelemetryActive(i)) {
|
|
int32_t calcPercent = getDshotTelemetryMotorInvalidPercent(i);
|
|
cliPrintf(" %3d.%02d%%", calcPercent / 100, calcPercent % 100);
|
|
} else {
|
|
cliPrint(" NO DATA");
|
|
}
|
|
#endif
|
|
|
|
cliPrintLinef(" %6d %3d.%02d %6d %6d %6d %6d %6d",
|
|
dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_TEMPERATURE],
|
|
dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] / 4,
|
|
25 * (dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_VOLTAGE] % 4),
|
|
dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_CURRENT],
|
|
dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_STATE_EVENTS],
|
|
dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG1],
|
|
dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG2],
|
|
dshotTelemetryState.motorState[i].telemetryData[DSHOT_TELEMETRY_TYPE_DEBUG3]
|
|
);
|
|
}
|
|
cliPrintLinefeed();
|
|
|
|
const int len = MAX_GCR_EDGES;
|
|
#ifdef DEBUG_BBDECODE
|
|
extern uint16_t bbBuffer[134];
|
|
for (int i = 0; i < 134; i++) {
|
|
cliPrintf("%u ", (int)bbBuffer[i]);
|
|
}
|
|
cliPrintLinefeed();
|
|
#endif
|
|
for (int i = 0; i < len; i++) {
|
|
cliPrintf("%u ", (int)dshotTelemetryState.inputBuffer[i]);
|
|
}
|
|
cliPrintLinefeed();
|
|
for (int i = 1; i < len; i++) {
|
|
cliPrintf("%u ", (int)(dshotTelemetryState.inputBuffer[i] - dshotTelemetryState.inputBuffer[i-1]));
|
|
}
|
|
cliPrintLinefeed();
|
|
} else {
|
|
cliPrintLine("Dshot telemetry not enabled");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static void printConfig(const char *cmdName, char *cmdline, bool doDiff)
|
|
{
|
|
dumpFlags_t dumpMask = DUMP_MASTER;
|
|
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, "rates"))) {
|
|
dumpMask = DUMP_RATES; // only
|
|
} else if ((options = checkCommand(cmdline, "hardware"))) {
|
|
dumpMask = DUMP_MASTER | HARDWARE_ONLY; // Show only hardware related settings (useful to generate unified target configs).
|
|
} else if ((options = checkCommand(cmdline, "all"))) {
|
|
dumpMask = DUMP_ALL; // all profiles and rates
|
|
} else {
|
|
options = cmdline;
|
|
}
|
|
|
|
if (doDiff) {
|
|
dumpMask = dumpMask | DO_DIFF;
|
|
}
|
|
|
|
if (checkCommand(options, "defaults")) {
|
|
dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
|
|
} else if (checkCommand(options, "bare")) {
|
|
dumpMask = dumpMask | BARE; // show the diff / dump without extra commands and board specific data
|
|
}
|
|
|
|
backupAndResetConfigs();
|
|
|
|
#ifdef USE_CLI_BATCH
|
|
bool batchModeEnabled = false;
|
|
#endif
|
|
if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
|
|
cliPrintHashLine("version");
|
|
printVersion(false);
|
|
|
|
if (!(dumpMask & BARE)) {
|
|
#ifdef USE_CLI_BATCH
|
|
cliPrintHashLine("start the command batch");
|
|
cliPrintLine("batch start");
|
|
batchModeEnabled = true;
|
|
#endif
|
|
|
|
if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
|
|
cliPrintHashLine("reset configuration to default settings");
|
|
cliPrintLine("defaults nosave");
|
|
}
|
|
}
|
|
|
|
#if defined(USE_BOARD_INFO)
|
|
cliPrintLinefeed();
|
|
printBoardName(dumpMask);
|
|
printManufacturerId(dumpMask);
|
|
#endif
|
|
|
|
if ((dumpMask & DUMP_ALL) && !(dumpMask & BARE)) {
|
|
cliMcuId(cmdName, "");
|
|
#if defined(USE_SIGNATURE)
|
|
cliSignature(cmdName, "");
|
|
#endif
|
|
}
|
|
|
|
if (!(dumpMask & HARDWARE_ONLY)) {
|
|
printCraftName(dumpMask, &pilotConfig_Copy);
|
|
}
|
|
|
|
#ifdef USE_RESOURCE_MGMT
|
|
printResource(dumpMask, "resources");
|
|
#if defined(USE_TIMER_MGMT)
|
|
printTimer(dumpMask, "timer");
|
|
#endif
|
|
#ifdef USE_DMA_SPEC
|
|
printDmaopt(dumpMask, "dma");
|
|
#endif
|
|
#endif
|
|
|
|
printFeature(dumpMask, featureConfig_Copy.enabledFeatures, featureConfig()->enabledFeatures, "feature");
|
|
|
|
printSerial(dumpMask, &serialConfig_Copy, serialConfig(), "serial");
|
|
|
|
if (!(dumpMask & HARDWARE_ONLY)) {
|
|
#ifndef USE_QUAD_MIXER_ONLY
|
|
const char *mixerHeadingStr = "mixer";
|
|
const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
|
|
mixerHeadingStr = cliPrintSectionHeading(dumpMask, !equalsDefault, mixerHeadingStr);
|
|
const char *formatMixer = "mixer %s";
|
|
cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
|
|
cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
|
|
|
|
cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
|
|
|
|
printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0), mixerHeadingStr);
|
|
|
|
#ifdef USE_SERVOS
|
|
printServo(dumpMask, servoParams_CopyArray, servoParams(0), "servo");
|
|
|
|
const char *servoMixHeadingStr = "servo mixer";
|
|
if (!(dumpMask & DO_DIFF) || customServoMixers(0)->rate != 0) {
|
|
cliPrintHashLine(servoMixHeadingStr);
|
|
cliPrintLine("smix reset\r\n");
|
|
servoMixHeadingStr = NULL;
|
|
}
|
|
printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0), servoMixHeadingStr);
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(USE_BEEPER)
|
|
printBeeper(dumpMask, beeperConfig_Copy.beeper_off_flags, beeperConfig()->beeper_off_flags, "beeper", BEEPER_ALLOWED_MODES, "beeper");
|
|
|
|
#if defined(USE_DSHOT)
|
|
printBeeper(dumpMask, beeperConfig_Copy.dshotBeaconOffFlags, beeperConfig()->dshotBeaconOffFlags, "beacon", DSHOT_BEACON_ALLOWED_MODES, "beacon");
|
|
#endif
|
|
#endif // USE_BEEPER
|
|
|
|
printMap(dumpMask, &rxConfig_Copy, rxConfig(), "map");
|
|
|
|
#ifdef USE_LED_STRIP_STATUS_MODE
|
|
printLed(dumpMask, ledStripStatusModeConfig_Copy.ledConfigs, ledStripStatusModeConfig()->ledConfigs, "led");
|
|
|
|
printColor(dumpMask, ledStripStatusModeConfig_Copy.colors, ledStripStatusModeConfig()->colors, "color");
|
|
|
|
printModeColor(dumpMask, &ledStripStatusModeConfig_Copy, ledStripStatusModeConfig(), "mode_color");
|
|
#endif
|
|
|
|
printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0), "aux");
|
|
|
|
printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0), "adjrange");
|
|
|
|
printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0), "rxrange");
|
|
|
|
#ifdef USE_VTX_TABLE
|
|
printVtxTable(dumpMask, &vtxTableConfig_Copy, vtxTableConfig(), "vtxtable");
|
|
#endif
|
|
|
|
#ifdef USE_VTX_CONTROL
|
|
printVtx(dumpMask, &vtxConfig_Copy, vtxConfig(), "vtx");
|
|
#endif
|
|
|
|
printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0), "rxfail");
|
|
}
|
|
|
|
if (dumpMask & HARDWARE_ONLY) {
|
|
dumpAllValues(cmdName, HARDWARE_VALUE, dumpMask, "master");
|
|
} else {
|
|
dumpAllValues(cmdName, MASTER_VALUE, dumpMask, "master");
|
|
|
|
if (dumpMask & DUMP_ALL) {
|
|
for (uint32_t pidProfileIndex = 0; pidProfileIndex < PID_PROFILE_COUNT; pidProfileIndex++) {
|
|
cliDumpPidProfile(cmdName, pidProfileIndex, dumpMask);
|
|
}
|
|
|
|
pidProfileIndexToUse = systemConfig_Copy.pidProfileIndex;
|
|
|
|
if (!(dumpMask & BARE)) {
|
|
cliPrintHashLine("restore original profile selection");
|
|
|
|
cliProfile(cmdName, "");
|
|
}
|
|
|
|
pidProfileIndexToUse = CURRENT_PROFILE_INDEX;
|
|
|
|
for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
|
|
cliDumpRateProfile(cmdName, rateIndex, dumpMask);
|
|
}
|
|
|
|
rateProfileIndexToUse = systemConfig_Copy.activeRateProfile;
|
|
|
|
if (!(dumpMask & BARE)) {
|
|
cliPrintHashLine("restore original rateprofile selection");
|
|
|
|
cliRateProfile(cmdName, "");
|
|
|
|
cliPrintHashLine("save configuration");
|
|
cliPrint("save");
|
|
#ifdef USE_CLI_BATCH
|
|
batchModeEnabled = false;
|
|
#endif
|
|
}
|
|
|
|
rateProfileIndexToUse = CURRENT_PROFILE_INDEX;
|
|
} else {
|
|
cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
|
|
|
|
cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
|
|
}
|
|
}
|
|
} else if (dumpMask & DUMP_PROFILE) {
|
|
cliDumpPidProfile(cmdName, systemConfig_Copy.pidProfileIndex, dumpMask);
|
|
} else if (dumpMask & DUMP_RATES) {
|
|
cliDumpRateProfile(cmdName, systemConfig_Copy.activeRateProfile, dumpMask);
|
|
}
|
|
|
|
#ifdef USE_CLI_BATCH
|
|
if (batchModeEnabled) {
|
|
cliPrintHashLine("end the command batch");
|
|
cliPrintLine("batch end");
|
|
}
|
|
#endif
|
|
|
|
// restore configs from copies
|
|
restoreConfigs(0);
|
|
}
|
|
|
|
static void cliDump(const char *cmdName, char *cmdline)
|
|
{
|
|
printConfig(cmdName, cmdline, false);
|
|
}
|
|
|
|
static void cliDiff(const char *cmdName, char *cmdline)
|
|
{
|
|
printConfig(cmdName, cmdline, true);
|
|
}
|
|
|
|
#if defined(USE_USB_MSC)
|
|
static void cliMsc(const char *cmdName, char *cmdline)
|
|
{
|
|
if (mscCheckFilesystemReady()) {
|
|
#ifdef USE_RTC_TIME
|
|
int timezoneOffsetMinutes = timeConfig()->tz_offsetMinutes;
|
|
if (!isEmpty(cmdline)) {
|
|
timezoneOffsetMinutes = atoi(cmdline);
|
|
if ((timezoneOffsetMinutes < TIMEZONE_OFFSET_MINUTES_MIN) || (timezoneOffsetMinutes > TIMEZONE_OFFSET_MINUTES_MAX)) {
|
|
cliPrintErrorLinef(cmdName, "INVALID TIMEZONE OFFSET");
|
|
return;
|
|
}
|
|
}
|
|
#else
|
|
int timezoneOffsetMinutes = 0;
|
|
UNUSED(cmdline);
|
|
#endif
|
|
cliPrintHashLine("Restarting in mass storage mode");
|
|
cliPrint("\r\nRebooting");
|
|
cliWriterFlush();
|
|
waitForSerialPortToFinishTransmitting(cliPort);
|
|
motorShutdown();
|
|
|
|
systemResetToMsc(timezoneOffsetMinutes);
|
|
} else {
|
|
cliPrintHashLine("Storage not present or failed to initialize!");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
typedef void cliCommandFn(const char* name, char *cmdline);
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
#ifndef MINIMAL_CLI
|
|
const char *description;
|
|
const char *args;
|
|
#endif
|
|
cliCommandFn *cliCommand;
|
|
} clicmd_t;
|
|
|
|
#ifndef MINIMAL_CLI
|
|
#define CLI_COMMAND_DEF(name, description, args, cliCommand) \
|
|
{ \
|
|
name , \
|
|
description , \
|
|
args , \
|
|
cliCommand \
|
|
}
|
|
#else
|
|
#define CLI_COMMAND_DEF(name, description, args, cliCommand) \
|
|
{ \
|
|
name, \
|
|
cliCommand \
|
|
}
|
|
#endif
|
|
|
|
static void cliHelp(const char *cmdName, char *cmdline);
|
|
|
|
// should be sorted a..z for bsearch()
|
|
const clicmd_t cmdTable[] = {
|
|
CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", "<index> <unused> <range channel> <start> <end> <function> <select channel> [<center> <scale>]", cliAdjustmentRange),
|
|
CLI_COMMAND_DEF("aux", "configure modes", "<index> <mode> <aux> <start> <end> <logic>", cliAux),
|
|
#ifdef USE_CLI_BATCH
|
|
CLI_COMMAND_DEF("batch", "start or end a batch of commands", "start | end", cliBatch),
|
|
#endif
|
|
#if defined(USE_BEEPER)
|
|
#if defined(USE_DSHOT)
|
|
CLI_COMMAND_DEF("beacon", "enable/disable Dshot beacon for a condition", "list\r\n"
|
|
"\t<->[name]", cliBeacon),
|
|
#endif
|
|
CLI_COMMAND_DEF("beeper", "enable/disable beeper for a condition", "list\r\n"
|
|
"\t<->[name]", cliBeeper),
|
|
#endif // USE_BEEPER
|
|
#if defined(USE_RX_BIND)
|
|
CLI_COMMAND_DEF("bind_rx", "initiate binding for RX SPI, SRXL2 or CRSF", NULL, cliRxBind),
|
|
#endif
|
|
#if defined(USE_FLASH_BOOT_LOADER)
|
|
CLI_COMMAND_DEF("bl", "reboot into bootloader", "[flash|rom]", cliBootloader),
|
|
#else
|
|
CLI_COMMAND_DEF("bl", "reboot into bootloader", "[rom]", cliBootloader),
|
|
#endif
|
|
#if defined(USE_BOARD_INFO)
|
|
CLI_COMMAND_DEF("board_name", "get / set the name of the board model", "[board name]", cliBoardName),
|
|
#endif
|
|
#ifdef USE_LED_STRIP_STATUS_MODE
|
|
CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
|
|
#endif
|
|
CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", "{nosave}", cliDefaults),
|
|
CLI_COMMAND_DEF("diff", "list configuration changes from default", "[master|profile|rates|hardware|all] {defaults|bare}", cliDiff),
|
|
#ifdef USE_RESOURCE_MGMT
|
|
|
|
#ifdef USE_DMA
|
|
#ifdef USE_DMA_SPEC
|
|
CLI_COMMAND_DEF("dma", "show/set DMA assignments", "<> | <device> <index> list | <device> <index> [<option>|none] | list | show", cliDma),
|
|
#else
|
|
CLI_COMMAND_DEF("dma", "show DMA assignments", "show", cliDma),
|
|
#endif
|
|
#endif
|
|
|
|
#endif
|
|
#ifdef USE_DSHOT_TELEMETRY
|
|
CLI_COMMAND_DEF("dshot_telemetry_info", "display dshot telemetry info and stats", NULL, cliDshotTelemetryInfo),
|
|
#endif
|
|
#ifdef USE_DSHOT
|
|
CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", "<index> <command>+", cliDshotProg),
|
|
#endif
|
|
CLI_COMMAND_DEF("dump", "dump configuration",
|
|
"[master|profile|rates|hardware|all] {defaults|bare}", cliDump),
|
|
#ifdef USE_ESCSERIAL
|
|
CLI_COMMAND_DEF("escprog", "passthrough esc to serial", "<mode [sk/bl/ki/cc]> <index>", cliEscPassthrough),
|
|
#endif
|
|
CLI_COMMAND_DEF("exit", "exit command line interface and reboot (default)", "[noreboot]", cliExitCmd),
|
|
CLI_COMMAND_DEF("feature", "configure features",
|
|
"list\r\n"
|
|
"\t<->[name]", cliFeature),
|
|
#ifdef USE_FLASH_CHIP
|
|
#ifdef USE_FLASHFS
|
|
CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
|
|
#endif
|
|
CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
|
|
#if defined(USE_FLASH_TOOLS) && defined(USE_FLASHFS)
|
|
CLI_COMMAND_DEF("flash_read", NULL, "<address> <length>", cliFlashRead),
|
|
CLI_COMMAND_DEF("flash_scan", "scan flash device for errors", NULL, cliFlashVerify),
|
|
CLI_COMMAND_DEF("flash_write", NULL, "<address> <message>", cliFlashWrite),
|
|
#endif
|
|
#endif
|
|
CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
|
|
#ifdef USE_GPS
|
|
CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
|
|
#endif
|
|
#if defined(USE_GYRO_REGISTER_DUMP) && !defined(SIMULATOR_BUILD)
|
|
CLI_COMMAND_DEF("gyroregisters", "dump gyro config registers contents", NULL, cliDumpGyroRegisters),
|
|
#endif
|
|
CLI_COMMAND_DEF("help", "display command help", "[search string]", cliHelp),
|
|
#ifdef USE_LED_STRIP_STATUS_MODE
|
|
CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
|
|
#endif
|
|
#if defined(USE_BOARD_INFO)
|
|
CLI_COMMAND_DEF("manufacturer_id", "get / set the id of the board manufacturer", "[manufacturer id]", cliManufacturerId),
|
|
#endif
|
|
CLI_COMMAND_DEF("map", "configure rc channel order", "[<map>]", cliMap),
|
|
CLI_COMMAND_DEF("mcu_id", "id of the microcontroller", NULL, cliMcuId),
|
|
#ifndef USE_QUAD_MIXER_ONLY
|
|
CLI_COMMAND_DEF("mixer", "configure mixer", "list\r\n\t<name>", cliMixer),
|
|
#endif
|
|
CLI_COMMAND_DEF("mmix", "custom motor mixer", NULL, cliMotorMix),
|
|
#ifdef USE_LED_STRIP_STATUS_MODE
|
|
CLI_COMMAND_DEF("mode_color", "configure mode and special colors", NULL, cliModeColor),
|
|
#endif
|
|
CLI_COMMAND_DEF("motor", "get/set motor", "<index> [<value>]", cliMotor),
|
|
#ifdef USE_USB_MSC
|
|
#ifdef USE_RTC_TIME
|
|
CLI_COMMAND_DEF("msc", "switch into msc mode", "[<timezone offset minutes>]", cliMsc),
|
|
#else
|
|
CLI_COMMAND_DEF("msc", "switch into msc mode", NULL, cliMsc),
|
|
#endif
|
|
#endif
|
|
#ifndef MINIMAL_CLI
|
|
CLI_COMMAND_DEF("play_sound", NULL, "[<index>]", cliPlaySound),
|
|
#endif
|
|
CLI_COMMAND_DEF("profile", "change profile", "[<index>]", cliProfile),
|
|
CLI_COMMAND_DEF("rateprofile", "change rate profile", "[<index>]", cliRateProfile),
|
|
#ifdef USE_RC_SMOOTHING_FILTER
|
|
CLI_COMMAND_DEF("rc_smoothing_info", "show rc_smoothing operational settings", NULL, cliRcSmoothing),
|
|
#endif // USE_RC_SMOOTHING_FILTER
|
|
#ifdef USE_RESOURCE_MGMT
|
|
CLI_COMMAND_DEF("resource", "show/set resources", "<> | <resource name> <index> [<pin>|none] | show [all]", cliResource),
|
|
#endif
|
|
CLI_COMMAND_DEF("rxfail", "show/set rx failsafe settings", NULL, cliRxFailsafe),
|
|
CLI_COMMAND_DEF("rxrange", "configure rx channel ranges", NULL, cliRxRange),
|
|
CLI_COMMAND_DEF("save", "save and reboot (default)", "[noreboot]", cliSave),
|
|
#ifdef USE_SDCARD
|
|
CLI_COMMAND_DEF("sd_info", "sdcard info", NULL, cliSdInfo),
|
|
#endif
|
|
CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial),
|
|
#if defined(USE_SERIAL_PASSTHROUGH)
|
|
#if defined(USE_PINIO)
|
|
CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|<dtr pinio>|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
|
|
#else
|
|
CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data from port 1 to VCP / port 2", "<id1> [<baud1>] [<mode1>] [none|reset] [<id2>] [<baud2>] [<mode2>]", cliSerialPassthrough),
|
|
#endif
|
|
#endif
|
|
#ifdef USE_SERVOS
|
|
CLI_COMMAND_DEF("servo", "configure servos", NULL, cliServo),
|
|
#endif
|
|
CLI_COMMAND_DEF("set", "change setting", "[<name>=<value>]", cliSet),
|
|
#if defined(USE_SIGNATURE)
|
|
CLI_COMMAND_DEF("signature", "get / set the board type signature", "[signature]", cliSignature),
|
|
#endif
|
|
#if defined(USE_SIMPLIFIED_TUNING)
|
|
CLI_COMMAND_DEF("simplified_tuning", "applies or disables simplified tuning", "apply | disable", cliSimplifiedTuning),
|
|
#endif
|
|
#ifdef USE_SERVOS
|
|
CLI_COMMAND_DEF("smix", "servo mixer", "<rule> <servo> <source> <rate> <speed> <min> <max> <box>\r\n"
|
|
"\treset\r\n"
|
|
"\tload <mixer>\r\n"
|
|
"\treverse <servo> <source> r|n", cliServoMix),
|
|
#endif
|
|
CLI_COMMAND_DEF("status", "show status", NULL, cliStatus),
|
|
CLI_COMMAND_DEF("tasks", "show task stats", NULL, cliTasks),
|
|
#ifdef USE_TIMER_MGMT
|
|
CLI_COMMAND_DEF("timer", "show/set timers", "<> | <pin> list | <pin> [af<alternate function>|none|<option(deprecated)>] | list | show", cliTimer),
|
|
#endif
|
|
CLI_COMMAND_DEF("version", "show version", NULL, cliVersion),
|
|
#ifdef USE_VTX_CONTROL
|
|
#ifdef MINIMAL_CLI
|
|
CLI_COMMAND_DEF("vtx", "vtx channels on switch", NULL, cliVtx),
|
|
#else
|
|
CLI_COMMAND_DEF("vtx", "vtx channels on switch", "<index> <aux_channel> <vtx_band> <vtx_channel> <vtx_power> <start_range> <end_range>", cliVtx),
|
|
#endif
|
|
#endif
|
|
#ifdef USE_VTX_TABLE
|
|
CLI_COMMAND_DEF("vtx_info", "vtx power config dump", NULL, cliVtxInfo),
|
|
CLI_COMMAND_DEF("vtxtable", "vtx frequency table", "<band> <bandname> <bandletter> [FACTORY|CUSTOM] <freq> ... <freq>\r\n", cliVtxTable),
|
|
#endif
|
|
};
|
|
|
|
static void cliHelp(const char *cmdName, char *cmdline)
|
|
{
|
|
bool anyMatches = false;
|
|
|
|
for (uint32_t i = 0; i < ARRAYLEN(cmdTable); i++) {
|
|
bool printEntry = false;
|
|
if (isEmpty(cmdline)) {
|
|
printEntry = true;
|
|
} else {
|
|
if (strcasestr(cmdTable[i].name, cmdline)
|
|
#ifndef MINIMAL_CLI
|
|
|| strcasestr(cmdTable[i].description, cmdline)
|
|
#endif
|
|
) {
|
|
printEntry = true;
|
|
}
|
|
}
|
|
|
|
if (printEntry) {
|
|
anyMatches = true;
|
|
cliPrint(cmdTable[i].name);
|
|
#ifndef MINIMAL_CLI
|
|
if (cmdTable[i].description) {
|
|
cliPrintf(" - %s", cmdTable[i].description);
|
|
}
|
|
if (cmdTable[i].args) {
|
|
cliPrintf("\r\n\t%s", cmdTable[i].args);
|
|
}
|
|
#endif
|
|
cliPrintLinefeed();
|
|
}
|
|
}
|
|
if (!isEmpty(cmdline) && !anyMatches) {
|
|
cliPrintErrorLinef(cmdName, "NO MATCHES FOR '%s'", cmdline);
|
|
}
|
|
}
|
|
|
|
static void processCharacter(const char c)
|
|
{
|
|
if (bufferIndex && (c == '\n' || c == '\r')) {
|
|
if (cliInteractive) {
|
|
// echo new line back to terminal
|
|
cliPrintLinefeed();
|
|
}
|
|
|
|
// Strip comment starting with # from line
|
|
char *p = cliBuffer;
|
|
p = strchr(p, '#');
|
|
if (NULL != p) {
|
|
bufferIndex = (uint32_t)(p - cliBuffer);
|
|
}
|
|
|
|
// Strip trailing whitespace
|
|
while (bufferIndex > 0 && cliBuffer[bufferIndex - 1] == ' ') {
|
|
bufferIndex--;
|
|
}
|
|
|
|
// Process non-empty lines
|
|
if (bufferIndex > 0) {
|
|
cliBuffer[bufferIndex] = 0; // null terminate
|
|
|
|
const clicmd_t *cmd;
|
|
char *options;
|
|
for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); cmd++) {
|
|
if ((options = checkCommand(cliBuffer, cmd->name))) {
|
|
break;
|
|
}
|
|
}
|
|
if (cmd < cmdTable + ARRAYLEN(cmdTable)) {
|
|
cmd->cliCommand(cmd->name, options);
|
|
if (!cliMode) {
|
|
// cli session ended
|
|
return;
|
|
}
|
|
} else {
|
|
if (cliInteractive) {
|
|
cliPrintError("input", "UNKNOWN COMMAND, TRY 'HELP'");
|
|
} else {
|
|
cliPrint("ERR_CMD_NA: ");
|
|
cliPrintLine(cliBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
cliClearInputBuffer();
|
|
|
|
// prompt if in interactive mode
|
|
if (cliInteractive) {
|
|
cliPrompt();
|
|
}
|
|
|
|
} else if (bufferIndex < sizeof(cliBuffer) && c >= 32 && c <= 126) {
|
|
if (!bufferIndex && c == ' ') {
|
|
return; // Ignore leading spaces
|
|
}
|
|
cliBuffer[bufferIndex++] = c;
|
|
|
|
// echo the character if interactive
|
|
if (cliInteractive) {
|
|
cliWrite(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void processCharacterInteractive(const char c)
|
|
{
|
|
if (c == '\t' || c == '?') {
|
|
// do tab completion
|
|
const clicmd_t *cmd, *pstart = NULL, *pend = NULL;
|
|
uint32_t i = bufferIndex;
|
|
for (cmd = cmdTable; cmd < cmdTable + ARRAYLEN(cmdTable); 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\n\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) { // CTRL-D
|
|
cliExit(true);
|
|
return;
|
|
} else if (c == 12) { // NewPage / CTRL-L
|
|
// clear screen
|
|
cliPrint("\033[2J\033[1;1H");
|
|
cliPrompt();
|
|
} else if (c == 127) {
|
|
// backspace
|
|
if (bufferIndex) {
|
|
cliBuffer[--bufferIndex] = 0;
|
|
cliPrint("\010 \010");
|
|
}
|
|
} else {
|
|
processCharacter(c);
|
|
}
|
|
}
|
|
|
|
bool cliProcess(void)
|
|
{
|
|
if (!cliWriter || !cliMode) {
|
|
return false;
|
|
}
|
|
|
|
while (serialRxBytesWaiting(cliPort)) {
|
|
uint8_t c = serialRead(cliPort);
|
|
if (cliInteractive) {
|
|
processCharacterInteractive(c);
|
|
} else {
|
|
// handle terminating flow control character
|
|
if (c == 0x3 || (cmp32(millis(), cliEntryTime) > 2000)) { // CTRL-C (ETX) or 2 seconds timeout
|
|
cliWrite(0x3); // send end of text, terminating flow control
|
|
cliExit(false);
|
|
return cliMode;
|
|
}
|
|
processCharacter(c);
|
|
}
|
|
}
|
|
cliWriterFlush();
|
|
return cliMode;
|
|
}
|
|
|
|
static void cliExit(const bool reboot)
|
|
{
|
|
cliWriterFlush();
|
|
waitForSerialPortToFinishTransmitting(cliPort);
|
|
cliClearInputBuffer();
|
|
cliMode = false;
|
|
cliInteractive = false;
|
|
// incase a motor was left running during motortest, clear it here
|
|
mixerResetDisarmedMotors();
|
|
|
|
if (reboot) {
|
|
cliReboot();
|
|
}
|
|
}
|
|
|
|
void cliEnter(serialPort_t *serialPort, bool interactive)
|
|
{
|
|
cliMode = true;
|
|
cliInteractive = interactive;
|
|
cliPort = serialPort;
|
|
cliEntryTime = millis();
|
|
cliClearInputBuffer();
|
|
|
|
if (interactive) {
|
|
setPrintfSerialPort(cliPort);
|
|
}
|
|
|
|
bufWriterInit(&cliWriterDesc, cliWriteBuffer, sizeof(cliWriteBuffer), (bufWrite_t)serialWriteBufBlockingShim, serialPort);
|
|
cliErrorWriter = cliWriter = &cliWriterDesc;
|
|
|
|
if (interactive) {
|
|
#ifndef MINIMAL_CLI
|
|
cliPrintLine("\r\nEntering CLI Mode, type 'exit' to reboot, or 'help'");
|
|
#else
|
|
cliPrintLine("\r\nCLI");
|
|
#endif
|
|
// arming flag not released if exiting cli with no reboot for safety
|
|
setArmingDisabled(ARMING_DISABLED_CLI);
|
|
cliPrompt();
|
|
|
|
#ifdef USE_CLI_BATCH
|
|
resetCommandBatch();
|
|
#endif
|
|
} else {
|
|
cliWrite(0x2); // send start of text, initiating flow control
|
|
}
|
|
}
|
|
|
|
#endif // USE_CLI
|