1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-25 17:25:20 +03:00
betaflight/src/main/msp/msp.c
ctzsnooze 8050ecd1e7 Mixer update: dynamic idle and throttle logging improvements
- all CLI parameters related to dynamic idle alone re-named with the `dyn_idle_` prefix
- when linear throttle scaling is active, the user's set idle value is now correct whether dynamic idle is on or off. Previously, the idle value fell when dynamic idle was activated at the same time as linear throttle scaling.
- enabling dynamic idle no longer causes a deadband at full throttle
- the setpoint throttle value sent to Blackbox does not include the dynamic idle offset
- the throttle value sent to the antigravity and dynamic lowpass code includes throttle scaling, but no other modifiers, to avoid false elevation of the apparent throttle position from dynamic idle and unnecessary transient changes in their filter cutoffs
- Dynamic Idle now uses a modified PI controller during active rpm control phase
- the D factor provides early detection of rapid falls in rpm, e.g. in hard chops. It is filtered heavily. Inadequate `dyn_idle_d_gain` may lead to a transient drop in rpm immediately after cutting throttle. Default is 50.
- the P factor provides fast control over rpm during the active control phase. Too much `dyn_idle_p_gain` may cause oscillation in that phase. Note enough and a slow drop in rpm will be inadequately corrected. Default is 50. Needs to be higher with heavier larger props.
- An integral element does most of the work.  It prevents enduring offsets from the set rpm. The I gain is high when increasing responding to low rpm, and slow to release.  The slow release makes a huge difference and avoids I oscillation. Not enough `dyn_idle_i_gain` and there may be wobble in rpm during the control phase, or the idle value may rise too slowly; too much may cause wobble. Default is 50. Needs to be higher with heavier larger props.
- The DYN_IDLE debug shows idle P, I and D in debugs 0, 1 and 2. minRps stays in debug 3.
- Interactions between throttle and thrust linear, dynamic idle, throttle scaling and throttle boost have been checked and work as they should.
2020-11-09 09:09:17 +11:00

3704 lines
129 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 <string.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include "platform.h"
#include "blackbox/blackbox.h"
#include "build/build_config.h"
#include "build/debug.h"
#include "build/version.h"
#include "cli/cli.h"
#include "common/axis.h"
#include "common/bitarray.h"
#include "common/color.h"
#include "common/huffman.h"
#include "common/maths.h"
#include "common/streambuf.h"
#include "common/utils.h"
#include "config/config.h"
#include "config/config_eeprom.h"
#include "config/feature.h"
#include "drivers/accgyro/accgyro.h"
#include "drivers/bus_i2c.h"
#include "drivers/bus_spi.h"
#include "drivers/camera_control.h"
#include "drivers/compass/compass.h"
#include "drivers/display.h"
#include "drivers/dshot.h"
#include "drivers/flash.h"
#include "drivers/io.h"
#include "drivers/motor.h"
#include "drivers/osd.h"
#include "drivers/pwm_output.h"
#include "drivers/sdcard.h"
#include "drivers/serial.h"
#include "drivers/serial_escserial.h"
#include "drivers/system.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/rc_modes.h"
#include "fc/runtime_config.h"
#include "flight/failsafe.h"
#include "flight/gps_rescue.h"
#include "flight/imu.h"
#include "flight/mixer.h"
#include "flight/pid.h"
#include "flight/pid_init.h"
#include "flight/position.h"
#include "flight/rpm_filter.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/serial_4way.h"
#include "io/servos.h"
#include "io/transponder_ir.h"
#include "io/usb_msc.h"
#include "io/vtx_control.h"
#include "io/vtx.h"
#include "msp/msp_box.h"
#include "msp/msp_protocol.h"
#include "msp/msp_protocol_v2_betaflight.h"
#include "msp/msp_protocol_v2_common.h"
#include "msp/msp_serial.h"
#include "osd/osd.h"
#include "osd/osd_elements.h"
#include "pg/beeper.h"
#include "pg/board.h"
#include "pg/gyrodev.h"
#include "pg/motor.h"
#include "pg/rx.h"
#include "pg/rx_spi.h"
#include "pg/usb.h"
#include "pg/vcd.h"
#include "pg/vtx_table.h"
#include "rx/rx.h"
#include "rx/rx_bind.h"
#include "rx/msp.h"
#include "scheduler/scheduler.h"
#include "sensors/acceleration.h"
#include "sensors/barometer.h"
#include "sensors/battery.h"
#include "sensors/boardalignment.h"
#include "sensors/compass.h"
#include "sensors/esc_sensor.h"
#include "sensors/gyro.h"
#include "sensors/gyro_init.h"
#include "sensors/rangefinder.h"
#include "telemetry/telemetry.h"
#ifdef USE_HARDWARE_REVISION_DETECTION
#include "hardware_revision.h"
#endif
#include "msp.h"
static const char * const flightControllerIdentifier = FC_FIRMWARE_IDENTIFIER; // 4 UPPER CASE alpha numeric characters that identify the flight controller.
enum {
MSP_REBOOT_FIRMWARE = 0,
MSP_REBOOT_BOOTLOADER_ROM,
MSP_REBOOT_MSC,
MSP_REBOOT_MSC_UTC,
MSP_REBOOT_BOOTLOADER_FLASH,
MSP_REBOOT_COUNT,
};
static uint8_t rebootMode;
typedef enum {
MSP_SDCARD_STATE_NOT_PRESENT = 0,
MSP_SDCARD_STATE_FATAL = 1,
MSP_SDCARD_STATE_CARD_INIT = 2,
MSP_SDCARD_STATE_FS_INIT = 3,
MSP_SDCARD_STATE_READY = 4
} mspSDCardState_e;
typedef enum {
MSP_SDCARD_FLAG_SUPPORTED = 1
} mspSDCardFlags_e;
typedef enum {
MSP_FLASHFS_FLAG_READY = 1,
MSP_FLASHFS_FLAG_SUPPORTED = 2
} mspFlashFsFlags_e;
typedef enum {
MSP_PASSTHROUGH_ESC_SIMONK = PROTOCOL_SIMONK,
MSP_PASSTHROUGH_ESC_BLHELI = PROTOCOL_BLHELI,
MSP_PASSTHROUGH_ESC_KISS = PROTOCOL_KISS,
MSP_PASSTHROUGH_ESC_KISSALL = PROTOCOL_KISSALL,
MSP_PASSTHROUGH_ESC_CASTLE = PROTOCOL_CASTLE,
MSP_PASSTHROUGH_SERIAL_ID = 0xFD,
MSP_PASSTHROUGH_SERIAL_FUNCTION_ID = 0xFE,
MSP_PASSTHROUGH_ESC_4WAY = 0xFF,
} mspPassthroughType_e;
#define RATEPROFILE_MASK (1 << 7)
#define RTC_NOT_SUPPORTED 0xff
typedef enum {
DEFAULTS_TYPE_BASE = 0,
DEFAULTS_TYPE_CUSTOM,
} defaultsType_e;
#ifdef USE_VTX_TABLE
static bool vtxTableNeedsInit = false;
#endif
static int mspDescriptor = 0;
mspDescriptor_t mspDescriptorAlloc(void)
{
return (mspDescriptor_t)mspDescriptor++;
}
static uint32_t mspArmingDisableFlags = 0;
static void mspArmingDisableByDescriptor(mspDescriptor_t desc)
{
mspArmingDisableFlags |= (1 << desc);
}
static void mspArmingEnableByDescriptor(mspDescriptor_t desc)
{
mspArmingDisableFlags &= ~(1 << desc);
}
static bool mspIsMspArmingEnabled(void)
{
return mspArmingDisableFlags == 0;
}
#define MSP_PASSTHROUGH_ESC_4WAY 0xff
static uint8_t mspPassthroughMode;
static uint8_t mspPassthroughArgument;
#ifdef USE_ESCSERIAL
static void mspEscPassthroughFn(serialPort_t *serialPort)
{
escEnablePassthrough(serialPort, &motorConfig()->dev, mspPassthroughArgument, mspPassthroughMode);
}
#endif
static serialPort_t *mspFindPassthroughSerialPort(void)
{
serialPortUsage_t *portUsage = NULL;
switch (mspPassthroughMode) {
case MSP_PASSTHROUGH_SERIAL_ID:
{
portUsage = findSerialPortUsageByIdentifier(mspPassthroughArgument);
break;
}
case MSP_PASSTHROUGH_SERIAL_FUNCTION_ID:
{
const serialPortConfig_t *portConfig = findSerialPortConfig(1 << mspPassthroughArgument);
if (portConfig) {
portUsage = findSerialPortUsageByIdentifier(portConfig->identifier);
}
break;
}
}
return portUsage ? portUsage->serialPort : NULL;
}
static void mspSerialPassthroughFn(serialPort_t *serialPort)
{
serialPort_t *passthroughPort = mspFindPassthroughSerialPort();
if (passthroughPort && serialPort) {
serialPassthrough(passthroughPort, serialPort, NULL, NULL);
}
}
static void mspFcSetPassthroughCommand(sbuf_t *dst, sbuf_t *src, mspPostProcessFnPtr *mspPostProcessFn)
{
const unsigned int dataSize = sbufBytesRemaining(src);
if (dataSize == 0) {
// Legacy format
mspPassthroughMode = MSP_PASSTHROUGH_ESC_4WAY;
} else {
mspPassthroughMode = sbufReadU8(src);
mspPassthroughArgument = sbufReadU8(src);
}
switch (mspPassthroughMode) {
case MSP_PASSTHROUGH_SERIAL_ID:
case MSP_PASSTHROUGH_SERIAL_FUNCTION_ID:
if (mspFindPassthroughSerialPort()) {
if (mspPostProcessFn) {
*mspPostProcessFn = mspSerialPassthroughFn;
}
sbufWriteU8(dst, 1);
} else {
sbufWriteU8(dst, 0);
}
break;
#ifdef USE_SERIAL_4WAY_BLHELI_INTERFACE
case MSP_PASSTHROUGH_ESC_4WAY:
// get channel number
// switch all motor lines HI
// reply with the count of ESC found
sbufWriteU8(dst, esc4wayInit());
if (mspPostProcessFn) {
*mspPostProcessFn = esc4wayProcess;
}
break;
#ifdef USE_ESCSERIAL
case MSP_PASSTHROUGH_ESC_SIMONK:
case MSP_PASSTHROUGH_ESC_BLHELI:
case MSP_PASSTHROUGH_ESC_KISS:
case MSP_PASSTHROUGH_ESC_KISSALL:
case MSP_PASSTHROUGH_ESC_CASTLE:
if (mspPassthroughArgument < getMotorCount() || (mspPassthroughMode == MSP_PASSTHROUGH_ESC_KISS && mspPassthroughArgument == ALL_MOTORS)) {
sbufWriteU8(dst, 1);
if (mspPostProcessFn) {
*mspPostProcessFn = mspEscPassthroughFn;
}
break;
}
FALLTHROUGH;
#endif // USE_ESCSERIAL
#endif //USE_SERIAL_4WAY_BLHELI_INTERFACE
default:
sbufWriteU8(dst, 0);
}
}
// TODO: Remove the pragma once this is called from unconditional code
#pragma GCC diagnostic ignored "-Wunused-function"
static void configRebootUpdateCheckU8(uint8_t *parm, uint8_t value)
{
if (*parm != value) {
setRebootRequired();
}
*parm = value;
}
#pragma GCC diagnostic pop
static void mspRebootFn(serialPort_t *serialPort)
{
UNUSED(serialPort);
motorShutdown();
switch (rebootMode) {
case MSP_REBOOT_FIRMWARE:
systemReset();
break;
case MSP_REBOOT_BOOTLOADER_ROM:
systemResetToBootloader(BOOTLOADER_REQUEST_ROM);
break;
#if defined(USE_USB_MSC)
case MSP_REBOOT_MSC:
case MSP_REBOOT_MSC_UTC: {
#ifdef USE_RTC_TIME
const int16_t timezoneOffsetMinutes = (rebootMode == MSP_REBOOT_MSC) ? timeConfig()->tz_offsetMinutes : 0;
systemResetToMsc(timezoneOffsetMinutes);
#else
systemResetToMsc(0);
#endif
}
break;
#endif
#if defined(USE_FLASH_BOOT_LOADER)
case MSP_REBOOT_BOOTLOADER_FLASH:
systemResetToBootloader(BOOTLOADER_REQUEST_FLASH);
break;
#endif
default:
return;
}
// control should never return here.
while (true) ;
}
static void serializeSDCardSummaryReply(sbuf_t *dst)
{
uint8_t flags = 0;
uint8_t state = 0;
uint8_t lastError = 0;
uint32_t freeSpace = 0;
uint32_t totalSpace = 0;
#if defined(USE_SDCARD)
if (sdcardConfig()->mode != SDCARD_MODE_NONE) {
flags = MSP_SDCARD_FLAG_SUPPORTED;
// Merge the card and filesystem states together
if (!sdcard_isInserted()) {
state = MSP_SDCARD_STATE_NOT_PRESENT;
} else if (!sdcard_isFunctional()) {
state = MSP_SDCARD_STATE_FATAL;
} else {
switch (afatfs_getFilesystemState()) {
case AFATFS_FILESYSTEM_STATE_READY:
state = MSP_SDCARD_STATE_READY;
break;
case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
if (sdcard_isInitialized()) {
state = MSP_SDCARD_STATE_FS_INIT;
} else {
state = MSP_SDCARD_STATE_CARD_INIT;
}
break;
case AFATFS_FILESYSTEM_STATE_FATAL:
case AFATFS_FILESYSTEM_STATE_UNKNOWN:
default:
state = MSP_SDCARD_STATE_FATAL;
break;
}
}
lastError = afatfs_getLastError();
// Write free space and total space in kilobytes
if (state == MSP_SDCARD_STATE_READY) {
freeSpace = afatfs_getContiguousFreeSpace() / 1024;
totalSpace = sdcard_getMetadata()->numBlocks / 2;
}
}
#endif
sbufWriteU8(dst, flags);
sbufWriteU8(dst, state);
sbufWriteU8(dst, lastError);
sbufWriteU32(dst, freeSpace);
sbufWriteU32(dst, totalSpace);
}
static void serializeDataflashSummaryReply(sbuf_t *dst)
{
#ifdef USE_FLASHFS
if (flashfsIsSupported()) {
uint8_t flags = MSP_FLASHFS_FLAG_SUPPORTED;
flags |= (flashfsIsReady() ? MSP_FLASHFS_FLAG_READY : 0);
const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS);
sbufWriteU8(dst, flags);
sbufWriteU32(dst, FLASH_PARTITION_SECTOR_COUNT(flashPartition));
sbufWriteU32(dst, flashfsGetSize());
sbufWriteU32(dst, flashfsGetOffset()); // Effectively the current number of bytes stored on the volume
} else
#endif
// FlashFS is not configured or valid device is not detected
{
sbufWriteU8(dst, 0);
sbufWriteU32(dst, 0);
sbufWriteU32(dst, 0);
sbufWriteU32(dst, 0);
}
}
#ifdef USE_FLASHFS
enum compressionType_e {
NO_COMPRESSION,
HUFFMAN
};
static void serializeDataflashReadReply(sbuf_t *dst, uint32_t address, const uint16_t size, bool useLegacyFormat, bool allowCompression)
{
STATIC_ASSERT(MSP_PORT_DATAFLASH_INFO_SIZE >= 16, MSP_PORT_DATAFLASH_INFO_SIZE_invalid);
uint16_t readLen = size;
const int bytesRemainingInBuf = sbufBytesRemaining(dst) - MSP_PORT_DATAFLASH_INFO_SIZE;
if (readLen > bytesRemainingInBuf) {
readLen = bytesRemainingInBuf;
}
// size will be lower than that requested if we reach end of volume
const uint32_t flashfsSize = flashfsGetSize();
if (readLen > flashfsSize - address) {
// truncate the request
readLen = flashfsSize - address;
}
sbufWriteU32(dst, address);
// legacy format does not support compression
#ifdef USE_HUFFMAN
const uint8_t compressionMethod = (!allowCompression || useLegacyFormat) ? NO_COMPRESSION : HUFFMAN;
#else
const uint8_t compressionMethod = NO_COMPRESSION;
UNUSED(allowCompression);
#endif
if (compressionMethod == NO_COMPRESSION) {
uint16_t *readLenPtr = (uint16_t *)sbufPtr(dst);
if (!useLegacyFormat) {
// new format supports variable read lengths
sbufWriteU16(dst, readLen);
sbufWriteU8(dst, 0); // placeholder for compression format
}
const int bytesRead = flashfsReadAbs(address, sbufPtr(dst), readLen);
if (!useLegacyFormat) {
// update the 'read length' with the actual amount read from flash.
*readLenPtr = bytesRead;
}
sbufAdvance(dst, bytesRead);
if (useLegacyFormat) {
// pad the buffer with zeros
for (int i = bytesRead; i < size; i++) {
sbufWriteU8(dst, 0);
}
}
} else {
#ifdef USE_HUFFMAN
// compress in 256-byte chunks
const uint16_t READ_BUFFER_SIZE = 256;
uint8_t readBuffer[READ_BUFFER_SIZE];
huffmanState_t state = {
.bytesWritten = 0,
.outByte = sbufPtr(dst) + sizeof(uint16_t) + sizeof(uint8_t) + HUFFMAN_INFO_SIZE,
.outBufLen = readLen,
.outBit = 0x80,
};
*state.outByte = 0;
uint16_t bytesReadTotal = 0;
// read until output buffer overflows or flash is exhausted
while (state.bytesWritten < state.outBufLen && address + bytesReadTotal < flashfsSize) {
const int bytesRead = flashfsReadAbs(address + bytesReadTotal, readBuffer,
MIN(sizeof(readBuffer), flashfsSize - address - bytesReadTotal));
const int status = huffmanEncodeBufStreaming(&state, readBuffer, bytesRead, huffmanTable);
if (status == -1) {
// overflow
break;
}
bytesReadTotal += bytesRead;
}
if (state.outBit != 0x80) {
++state.bytesWritten;
}
// header
sbufWriteU16(dst, HUFFMAN_INFO_SIZE + state.bytesWritten);
sbufWriteU8(dst, compressionMethod);
// payload
sbufWriteU16(dst, bytesReadTotal);
sbufAdvance(dst, state.bytesWritten);
#endif
}
}
#endif // USE_FLASHFS
/*
* Returns true if the command was processd, false otherwise.
* May set mspPostProcessFunc to a function to be called once the command has been processed
*/
static bool mspCommonProcessOutCommand(int16_t cmdMSP, sbuf_t *dst, mspPostProcessFnPtr *mspPostProcessFn)
{
UNUSED(mspPostProcessFn);
switch (cmdMSP) {
case MSP_API_VERSION:
sbufWriteU8(dst, MSP_PROTOCOL_VERSION);
sbufWriteU8(dst, API_VERSION_MAJOR);
sbufWriteU8(dst, API_VERSION_MINOR);
break;
case MSP_FC_VARIANT:
sbufWriteData(dst, flightControllerIdentifier, FLIGHT_CONTROLLER_IDENTIFIER_LENGTH);
break;
case MSP_FC_VERSION:
sbufWriteU8(dst, FC_VERSION_MAJOR);
sbufWriteU8(dst, FC_VERSION_MINOR);
sbufWriteU8(dst, FC_VERSION_PATCH_LEVEL);
break;
case MSP_BOARD_INFO:
{
sbufWriteData(dst, systemConfig()->boardIdentifier, BOARD_IDENTIFIER_LENGTH);
#ifdef USE_HARDWARE_REVISION_DETECTION
sbufWriteU16(dst, hardwareRevision);
#else
sbufWriteU16(dst, 0); // No other build targets currently have hardware revision detection.
#endif
#if defined(USE_MAX7456)
sbufWriteU8(dst, 2); // 2 == FC with MAX7456
#else
sbufWriteU8(dst, 0); // 0 == FC
#endif
// Target capabilities (uint8)
#define TARGET_HAS_VCP 0
#define TARGET_HAS_SOFTSERIAL 1
#define TARGET_IS_UNIFIED 2
#define TARGET_HAS_FLASH_BOOTLOADER 3
#define TARGET_SUPPORTS_CUSTOM_DEFAULTS 4
#define TARGET_HAS_CUSTOM_DEFAULTS 5
#define TARGET_SUPPORTS_RX_BIND 6
uint8_t targetCapabilities = 0;
#ifdef USE_VCP
targetCapabilities |= BIT(TARGET_HAS_VCP);
#endif
#if defined(USE_SOFTSERIAL1) || defined(USE_SOFTSERIAL2)
targetCapabilities |= BIT(TARGET_HAS_SOFTSERIAL);
#endif
#if defined(USE_UNIFIED_TARGET)
targetCapabilities |= BIT(TARGET_IS_UNIFIED);
#endif
#if defined(USE_FLASH_BOOT_LOADER)
targetCapabilities |= BIT(TARGET_HAS_FLASH_BOOTLOADER);
#endif
#if defined(USE_CUSTOM_DEFAULTS)
targetCapabilities |= BIT(TARGET_SUPPORTS_CUSTOM_DEFAULTS);
if (hasCustomDefaults()) {
targetCapabilities |= BIT(TARGET_HAS_CUSTOM_DEFAULTS);
}
#endif
#if defined(USE_RX_BIND)
if (getRxBindSupported()) {
targetCapabilities |= BIT(TARGET_SUPPORTS_RX_BIND);
}
#endif
sbufWriteU8(dst, targetCapabilities);
// Target name with explicit length
sbufWriteU8(dst, strlen(targetName));
sbufWriteData(dst, targetName, strlen(targetName));
#if defined(USE_BOARD_INFO)
// Board name with explicit length
char *value = getBoardName();
sbufWriteU8(dst, strlen(value));
sbufWriteString(dst, value);
// Manufacturer id with explicit length
value = getManufacturerId();
sbufWriteU8(dst, strlen(value));
sbufWriteString(dst, value);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
#endif
#if defined(USE_SIGNATURE)
// Signature
sbufWriteData(dst, getSignature(), SIGNATURE_LENGTH);
#else
uint8_t emptySignature[SIGNATURE_LENGTH];
memset(emptySignature, 0, sizeof(emptySignature));
sbufWriteData(dst, &emptySignature, sizeof(emptySignature));
#endif
sbufWriteU8(dst, getMcuTypeId());
// Added in API version 1.42
sbufWriteU8(dst, systemConfig()->configurationState);
// Added in API version 1.43
sbufWriteU16(dst, gyro.sampleRateHz); // informational so the configurator can display the correct gyro/pid frequencies in the drop-down
// Configuration warnings / problems (uint32_t)
#define PROBLEM_ACC_NEEDS_CALIBRATION 0
#define PROBLEM_MOTOR_PROTOCOL_DISABLED 1
uint32_t configurationProblems = 0;
#if defined(USE_ACC)
if (!accHasBeenCalibrated()) {
configurationProblems |= BIT(PROBLEM_ACC_NEEDS_CALIBRATION);
}
#endif
if (!checkMotorProtocolEnabled(&motorConfig()->dev, NULL)) {
configurationProblems |= BIT(PROBLEM_MOTOR_PROTOCOL_DISABLED);
}
sbufWriteU32(dst, configurationProblems);
// Added in MSP API 1.44
#if defined(USE_SPI)
sbufWriteU8(dst, spiGetRegisteredDeviceCount());
#else
sbufWriteU8(dst, 0);
#endif
#if defined(USE_I2C)
sbufWriteU8(dst, i2cGetRegisteredDeviceCount());
#else
sbufWriteU8(dst, 0);
#endif
break;
}
case MSP_BUILD_INFO:
sbufWriteData(dst, buildDate, BUILD_DATE_LENGTH);
sbufWriteData(dst, buildTime, BUILD_TIME_LENGTH);
sbufWriteData(dst, shortGitRevision, GIT_SHORT_REVISION_LENGTH);
break;
case MSP_ANALOG:
sbufWriteU8(dst, (uint8_t)constrain(getLegacyBatteryVoltage(), 0, 255));
sbufWriteU16(dst, (uint16_t)constrain(getMAhDrawn(), 0, 0xFFFF)); // milliamp hours drawn from battery
sbufWriteU16(dst, getRssi());
sbufWriteU16(dst, (int16_t)constrain(getAmperage(), -0x8000, 0x7FFF)); // send current in 0.01 A steps, range is -320A to 320A
sbufWriteU16(dst, getBatteryVoltage());
break;
case MSP_DEBUG:
for (int i = 0; i < DEBUG16_VALUE_COUNT; i++) {
sbufWriteU16(dst, debug[i]); // 4 variables are here for general monitoring purpose
}
break;
case MSP_UID:
sbufWriteU32(dst, U_ID_0);
sbufWriteU32(dst, U_ID_1);
sbufWriteU32(dst, U_ID_2);
break;
case MSP_FEATURE_CONFIG:
sbufWriteU32(dst, featureConfig()->enabledFeatures);
break;
#ifdef USE_BEEPER
case MSP_BEEPER_CONFIG:
sbufWriteU32(dst, beeperConfig()->beeper_off_flags);
sbufWriteU8(dst, beeperConfig()->dshotBeaconTone);
sbufWriteU32(dst, beeperConfig()->dshotBeaconOffFlags);
break;
#endif
case MSP_BATTERY_STATE: {
// battery characteristics
sbufWriteU8(dst, (uint8_t)constrain(getBatteryCellCount(), 0, 255)); // 0 indicates battery not detected.
sbufWriteU16(dst, batteryConfig()->batteryCapacity); // in mAh
// battery state
sbufWriteU8(dst, (uint8_t)constrain(getLegacyBatteryVoltage(), 0, 255)); // in 0.1V steps
sbufWriteU16(dst, (uint16_t)constrain(getMAhDrawn(), 0, 0xFFFF)); // milliamp hours drawn from battery
sbufWriteU16(dst, (int16_t)constrain(getAmperage(), -0x8000, 0x7FFF)); // send current in 0.01 A steps, range is -320A to 320A
// battery alerts
sbufWriteU8(dst, (uint8_t)getBatteryState());
sbufWriteU16(dst, getBatteryVoltage()); // in 0.01V steps
break;
}
case MSP_VOLTAGE_METERS: {
// write out id and voltage meter values, once for each meter we support
uint8_t count = supportedVoltageMeterCount;
#ifdef USE_ESC_SENSOR
count -= VOLTAGE_METER_ID_ESC_COUNT - getMotorCount();
#endif
for (int i = 0; i < count; i++) {
voltageMeter_t meter;
uint8_t id = (uint8_t)voltageMeterIds[i];
voltageMeterRead(id, &meter);
sbufWriteU8(dst, id);
sbufWriteU8(dst, (uint8_t)constrain((meter.displayFiltered + 5) / 10, 0, 255));
}
break;
}
case MSP_CURRENT_METERS: {
// write out id and current meter values, once for each meter we support
uint8_t count = supportedCurrentMeterCount;
#ifdef USE_ESC_SENSOR
count -= VOLTAGE_METER_ID_ESC_COUNT - getMotorCount();
#endif
for (int i = 0; i < count; i++) {
currentMeter_t meter;
uint8_t id = (uint8_t)currentMeterIds[i];
currentMeterRead(id, &meter);
sbufWriteU8(dst, id);
sbufWriteU16(dst, (uint16_t)constrain(meter.mAhDrawn, 0, 0xFFFF)); // milliamp hours drawn from battery
sbufWriteU16(dst, (uint16_t)constrain(meter.amperage * 10, 0, 0xFFFF)); // send amperage in 0.001 A steps (mA). Negative range is truncated to zero
}
break;
}
case MSP_VOLTAGE_METER_CONFIG:
{
// by using a sensor type and a sub-frame length it's possible to configure any type of voltage meter,
// e.g. an i2c/spi/can sensor or any sensor not built directly into the FC such as ESC/RX/SPort/SBus that has
// different configuration requirements.
STATIC_ASSERT(VOLTAGE_SENSOR_ADC_VBAT == 0, VOLTAGE_SENSOR_ADC_VBAT_incorrect); // VOLTAGE_SENSOR_ADC_VBAT should be the first index
sbufWriteU8(dst, MAX_VOLTAGE_SENSOR_ADC); // voltage meters in payload
for (int i = VOLTAGE_SENSOR_ADC_VBAT; i < MAX_VOLTAGE_SENSOR_ADC; i++) {
const uint8_t adcSensorSubframeLength = 1 + 1 + 1 + 1 + 1; // length of id, type, vbatscale, vbatresdivval, vbatresdivmultipler, in bytes
sbufWriteU8(dst, adcSensorSubframeLength); // ADC sensor sub-frame length
sbufWriteU8(dst, voltageMeterADCtoIDMap[i]); // id of the sensor
sbufWriteU8(dst, VOLTAGE_SENSOR_TYPE_ADC_RESISTOR_DIVIDER); // indicate the type of sensor that the next part of the payload is for
sbufWriteU8(dst, voltageSensorADCConfig(i)->vbatscale);
sbufWriteU8(dst, voltageSensorADCConfig(i)->vbatresdivval);
sbufWriteU8(dst, voltageSensorADCConfig(i)->vbatresdivmultiplier);
}
// if we had any other voltage sensors, this is where we would output any needed configuration
}
break;
case MSP_CURRENT_METER_CONFIG: {
// the ADC and VIRTUAL sensors have the same configuration requirements, however this API reflects
// that this situation may change and allows us to support configuration of any current sensor with
// specialist configuration requirements.
int currentMeterCount = 1;
#ifdef USE_VIRTUAL_CURRENT_METER
currentMeterCount++;
#endif
sbufWriteU8(dst, currentMeterCount);
const uint8_t adcSensorSubframeLength = 1 + 1 + 2 + 2; // length of id, type, scale, offset, in bytes
sbufWriteU8(dst, adcSensorSubframeLength);
sbufWriteU8(dst, CURRENT_METER_ID_BATTERY_1); // the id of the meter
sbufWriteU8(dst, CURRENT_SENSOR_ADC); // indicate the type of sensor that the next part of the payload is for
sbufWriteU16(dst, currentSensorADCConfig()->scale);
sbufWriteU16(dst, currentSensorADCConfig()->offset);
#ifdef USE_VIRTUAL_CURRENT_METER
const int8_t virtualSensorSubframeLength = 1 + 1 + 2 + 2; // length of id, type, scale, offset, in bytes
sbufWriteU8(dst, virtualSensorSubframeLength);
sbufWriteU8(dst, CURRENT_METER_ID_VIRTUAL_1); // the id of the meter
sbufWriteU8(dst, CURRENT_SENSOR_VIRTUAL); // indicate the type of sensor that the next part of the payload is for
sbufWriteU16(dst, currentSensorVirtualConfig()->scale);
sbufWriteU16(dst, currentSensorVirtualConfig()->offset);
#endif
// if we had any other current sensors, this is where we would output any needed configuration
break;
}
case MSP_BATTERY_CONFIG:
sbufWriteU8(dst, (batteryConfig()->vbatmincellvoltage + 5) / 10);
sbufWriteU8(dst, (batteryConfig()->vbatmaxcellvoltage + 5) / 10);
sbufWriteU8(dst, (batteryConfig()->vbatwarningcellvoltage + 5) / 10);
sbufWriteU16(dst, batteryConfig()->batteryCapacity);
sbufWriteU8(dst, batteryConfig()->voltageMeterSource);
sbufWriteU8(dst, batteryConfig()->currentMeterSource);
sbufWriteU16(dst, batteryConfig()->vbatmincellvoltage);
sbufWriteU16(dst, batteryConfig()->vbatmaxcellvoltage);
sbufWriteU16(dst, batteryConfig()->vbatwarningcellvoltage);
break;
case MSP_TRANSPONDER_CONFIG: {
#ifdef USE_TRANSPONDER
// Backward compatibility to BFC 3.1.1 is lost for this message type
sbufWriteU8(dst, TRANSPONDER_PROVIDER_COUNT);
for (unsigned int i = 0; i < TRANSPONDER_PROVIDER_COUNT; i++) {
sbufWriteU8(dst, transponderRequirements[i].provider);
sbufWriteU8(dst, transponderRequirements[i].dataLength);
}
uint8_t provider = transponderConfig()->provider;
sbufWriteU8(dst, provider);
if (provider) {
uint8_t requirementIndex = provider - 1;
uint8_t providerDataLength = transponderRequirements[requirementIndex].dataLength;
for (unsigned int i = 0; i < providerDataLength; i++) {
sbufWriteU8(dst, transponderConfig()->data[i]);
}
}
#else
sbufWriteU8(dst, 0); // no providers
#endif
break;
}
case MSP_OSD_CONFIG: {
#define OSD_FLAGS_OSD_FEATURE (1 << 0)
//#define OSD_FLAGS_OSD_SLAVE (1 << 1)
#define OSD_FLAGS_RESERVED_1 (1 << 2)
#define OSD_FLAGS_OSD_HARDWARE_FRSKYOSD (1 << 3)
#define OSD_FLAGS_OSD_HARDWARE_MAX_7456 (1 << 4)
#define OSD_FLAGS_OSD_DEVICE_DETECTED (1 << 5)
uint8_t osdFlags = 0;
#if defined(USE_OSD)
osdFlags |= OSD_FLAGS_OSD_FEATURE;
osdDisplayPortDevice_e deviceType;
displayPort_t *osdDisplayPort = osdGetDisplayPort(&deviceType);
bool displayIsReady = osdDisplayPort && displayCheckReady(osdDisplayPort, true);
switch (deviceType) {
case OSD_DISPLAYPORT_DEVICE_MAX7456:
osdFlags |= OSD_FLAGS_OSD_HARDWARE_MAX_7456;
if (displayIsReady) {
osdFlags |= OSD_FLAGS_OSD_DEVICE_DETECTED;
}
break;
case OSD_DISPLAYPORT_DEVICE_FRSKYOSD:
osdFlags |= OSD_FLAGS_OSD_HARDWARE_FRSKYOSD;
if (displayIsReady) {
osdFlags |= OSD_FLAGS_OSD_DEVICE_DETECTED;
}
break;
default:
break;
}
#endif
sbufWriteU8(dst, osdFlags);
#ifdef USE_MAX7456
// send video system (AUTO/PAL/NTSC)
sbufWriteU8(dst, vcdProfile()->video_system);
#else
sbufWriteU8(dst, 0);
#endif
#ifdef USE_OSD
// OSD specific, not applicable to OSD slaves.
// Configuration
sbufWriteU8(dst, osdConfig()->units);
// Alarms
sbufWriteU8(dst, osdConfig()->rssi_alarm);
sbufWriteU16(dst, osdConfig()->cap_alarm);
// Reuse old timer alarm (U16) as OSD_ITEM_COUNT
sbufWriteU8(dst, 0);
sbufWriteU8(dst, OSD_ITEM_COUNT);
sbufWriteU16(dst, osdConfig()->alt_alarm);
// Element position and visibility
for (int i = 0; i < OSD_ITEM_COUNT; i++) {
sbufWriteU16(dst, osdElementConfig()->item_pos[i]);
}
// Post flight statistics
sbufWriteU8(dst, OSD_STAT_COUNT);
for (int i = 0; i < OSD_STAT_COUNT; i++ ) {
sbufWriteU8(dst, osdStatGetState(i));
}
// Timers
sbufWriteU8(dst, OSD_TIMER_COUNT);
for (int i = 0; i < OSD_TIMER_COUNT; i++) {
sbufWriteU16(dst, osdConfig()->timers[i]);
}
// Enabled warnings
// Send low word first for backwards compatibility (API < 1.41)
sbufWriteU16(dst, (uint16_t)(osdConfig()->enabledWarnings & 0xFFFF));
// API >= 1.41
// Send the warnings count and 32bit enabled warnings flags.
// Add currently active OSD profile (0 indicates OSD profiles not available).
// Add OSD stick overlay mode (0 indicates OSD stick overlay not available).
sbufWriteU8(dst, OSD_WARNING_COUNT);
sbufWriteU32(dst, osdConfig()->enabledWarnings);
#ifdef USE_OSD_PROFILES
sbufWriteU8(dst, OSD_PROFILE_COUNT); // available profiles
sbufWriteU8(dst, osdConfig()->osdProfileIndex); // selected profile
#else
// If the feature is not available there is only 1 profile and it's always selected
sbufWriteU8(dst, 1);
sbufWriteU8(dst, 1);
#endif // USE_OSD_PROFILES
#ifdef USE_OSD_STICK_OVERLAY
sbufWriteU8(dst, osdConfig()->overlay_radio_mode);
#else
sbufWriteU8(dst, 0);
#endif // USE_OSD_STICK_OVERLAY
// API >= 1.43
// Add the camera frame element width/height
sbufWriteU8(dst, osdConfig()->camera_frame_width);
sbufWriteU8(dst, osdConfig()->camera_frame_height);
#endif // USE_OSD
break;
}
default:
return false;
}
return true;
}
static bool mspProcessOutCommand(int16_t cmdMSP, sbuf_t *dst)
{
bool unsupportedCommand = false;
switch (cmdMSP) {
case MSP_STATUS_EX:
case MSP_STATUS:
{
boxBitmask_t flightModeFlags;
const int flagBits = packFlightModeFlags(&flightModeFlags);
sbufWriteU16(dst, getTaskDeltaTimeUs(TASK_PID));
#ifdef USE_I2C
sbufWriteU16(dst, i2cGetErrorCounter());
#else
sbufWriteU16(dst, 0);
#endif
sbufWriteU16(dst, sensors(SENSOR_ACC) | sensors(SENSOR_BARO) << 1 | sensors(SENSOR_MAG) << 2 | sensors(SENSOR_GPS) << 3 | sensors(SENSOR_RANGEFINDER) << 4 | sensors(SENSOR_GYRO) << 5);
sbufWriteData(dst, &flightModeFlags, 4); // unconditional part of flags, first 32 bits
sbufWriteU8(dst, getCurrentPidProfileIndex());
sbufWriteU16(dst, constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE));
if (cmdMSP == MSP_STATUS_EX) {
sbufWriteU8(dst, PID_PROFILE_COUNT);
sbufWriteU8(dst, getCurrentControlRateProfileIndex());
} else { // MSP_STATUS
sbufWriteU16(dst, 0); // gyro cycle time
}
// write flightModeFlags header. Lowest 4 bits contain number of bytes that follow
// header is emited even when all bits fit into 32 bits to allow future extension
int byteCount = (flagBits - 32 + 7) / 8; // 32 already stored, round up
byteCount = constrain(byteCount, 0, 15); // limit to 16 bytes (128 bits)
sbufWriteU8(dst, byteCount);
sbufWriteData(dst, ((uint8_t*)&flightModeFlags) + 4, byteCount);
// Write arming disable flags
// 1 byte, flag count
sbufWriteU8(dst, ARMING_DISABLE_FLAGS_COUNT);
// 4 bytes, flags
const uint32_t armingDisableFlags = getArmingDisableFlags();
sbufWriteU32(dst, armingDisableFlags);
// config state flags - bits to indicate the state of the configuration, reboot required, etc.
// other flags can be added as needed
sbufWriteU8(dst, (getRebootRequired() << 0));
}
break;
case MSP_RAW_IMU:
{
#if defined(USE_ACC)
// Hack scale due to choice of units for sensor data in multiwii
uint8_t scale;
if (acc.dev.acc_1G > 512 * 4) {
scale = 8;
} else if (acc.dev.acc_1G > 512 * 2) {
scale = 4;
} else if (acc.dev.acc_1G >= 512) {
scale = 2;
} else {
scale = 1;
}
#endif
for (int i = 0; i < 3; i++) {
#if defined(USE_ACC)
sbufWriteU16(dst, lrintf(acc.accADC[i] / scale));
#else
sbufWriteU16(dst, 0);
#endif
}
for (int i = 0; i < 3; i++) {
sbufWriteU16(dst, gyroRateDps(i));
}
for (int i = 0; i < 3; i++) {
#if defined(USE_MAG)
sbufWriteU16(dst, lrintf(mag.magADC[i]));
#else
sbufWriteU16(dst, 0);
#endif
}
}
break;
case MSP_NAME:
{
const int nameLen = strlen(pilotConfig()->name);
for (int i = 0; i < nameLen; i++) {
sbufWriteU8(dst, pilotConfig()->name[i]);
}
}
break;
#ifdef USE_SERVOS
case MSP_SERVO:
sbufWriteData(dst, &servo, MAX_SUPPORTED_SERVOS * 2);
break;
case MSP_SERVO_CONFIGURATIONS:
for (int i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
sbufWriteU16(dst, servoParams(i)->min);
sbufWriteU16(dst, servoParams(i)->max);
sbufWriteU16(dst, servoParams(i)->middle);
sbufWriteU8(dst, servoParams(i)->rate);
sbufWriteU8(dst, servoParams(i)->forwardFromChannel);
sbufWriteU32(dst, servoParams(i)->reversedSources);
}
break;
case MSP_SERVO_MIX_RULES:
for (int i = 0; i < MAX_SERVO_RULES; i++) {
sbufWriteU8(dst, customServoMixers(i)->targetChannel);
sbufWriteU8(dst, customServoMixers(i)->inputSource);
sbufWriteU8(dst, customServoMixers(i)->rate);
sbufWriteU8(dst, customServoMixers(i)->speed);
sbufWriteU8(dst, customServoMixers(i)->min);
sbufWriteU8(dst, customServoMixers(i)->max);
sbufWriteU8(dst, customServoMixers(i)->box);
}
break;
#endif
case MSP_MOTOR:
for (unsigned i = 0; i < 8; i++) {
#ifdef USE_MOTOR
if (!motorIsEnabled() || i >= MAX_SUPPORTED_MOTORS || !motorIsMotorEnabled(i)) {
sbufWriteU16(dst, 0);
continue;
}
sbufWriteU16(dst, motorConvertToExternal(motor[i]));
#else
sbufWriteU16(dst, 0);
#endif
}
break;
// Added in API version 1.42
case MSP_MOTOR_TELEMETRY:
sbufWriteU8(dst, getMotorCount());
for (unsigned i = 0; i < getMotorCount(); i++) {
int rpm = 0;
uint16_t invalidPct = 0;
uint8_t escTemperature = 0; // degrees celcius
uint16_t escVoltage = 0; // 0.01V per unit
uint16_t escCurrent = 0; // 0.01A per unit
uint16_t escConsumption = 0; // mAh
bool rpmDataAvailable = false;
#ifdef USE_DSHOT_TELEMETRY
if (motorConfig()->dev.useDshotTelemetry) {
rpm = (int)getDshotTelemetry(i) * 100 * 2 / motorConfig()->motorPoleCount;
rpmDataAvailable = true;
invalidPct = 10000; // 100.00%
#ifdef USE_DSHOT_TELEMETRY_STATS
if (isDshotMotorTelemetryActive(i)) {
invalidPct = getDshotTelemetryMotorInvalidPercent(i);
}
#endif
}
#endif
#ifdef USE_ESC_SENSOR
if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
escSensorData_t *escData = getEscSensorData(i);
if (!rpmDataAvailable) { // We want DSHOT telemetry RPM data (if available) to have precedence
rpm = calcEscRpm(escData->rpm);
rpmDataAvailable = true;
}
escTemperature = escData->temperature;
escVoltage = escData->voltage;
escCurrent = escData->current;
escConsumption = escData->consumption;
}
#endif
sbufWriteU32(dst, (rpmDataAvailable ? rpm : 0));
sbufWriteU16(dst, invalidPct);
sbufWriteU8(dst, escTemperature);
sbufWriteU16(dst, escVoltage);
sbufWriteU16(dst, escCurrent);
sbufWriteU16(dst, escConsumption);
}
break;
case MSP2_MOTOR_OUTPUT_REORDERING:
{
sbufWriteU8(dst, MAX_SUPPORTED_MOTORS);
for (unsigned i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
sbufWriteU8(dst, motorConfig()->dev.motorOutputReordering[i]);
}
}
break;
case MSP_RC:
for (int i = 0; i < rxRuntimeState.channelCount; i++) {
sbufWriteU16(dst, rcData[i]);
}
break;
case MSP_ATTITUDE:
sbufWriteU16(dst, attitude.values.roll);
sbufWriteU16(dst, attitude.values.pitch);
sbufWriteU16(dst, DECIDEGREES_TO_DEGREES(attitude.values.yaw));
break;
case MSP_ALTITUDE:
sbufWriteU32(dst, getEstimatedAltitudeCm());
#ifdef USE_VARIO
sbufWriteU16(dst, getEstimatedVario());
#else
sbufWriteU16(dst, 0);
#endif
break;
case MSP_SONAR_ALTITUDE:
#if defined(USE_RANGEFINDER)
sbufWriteU32(dst, rangefinderGetLatestAltitude());
#else
sbufWriteU32(dst, 0);
#endif
break;
case MSP_BOARD_ALIGNMENT_CONFIG:
sbufWriteU16(dst, boardAlignment()->rollDegrees);
sbufWriteU16(dst, boardAlignment()->pitchDegrees);
sbufWriteU16(dst, boardAlignment()->yawDegrees);
break;
case MSP_ARMING_CONFIG:
sbufWriteU8(dst, armingConfig()->auto_disarm_delay);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, imuConfig()->small_angle);
break;
case MSP_RC_TUNING:
sbufWriteU8(dst, currentControlRateProfile->rcRates[FD_ROLL]);
sbufWriteU8(dst, currentControlRateProfile->rcExpo[FD_ROLL]);
for (int i = 0 ; i < 3; i++) {
sbufWriteU8(dst, currentControlRateProfile->rates[i]); // R,P,Y see flight_dynamics_index_t
}
sbufWriteU8(dst, currentControlRateProfile->dynThrPID);
sbufWriteU8(dst, currentControlRateProfile->thrMid8);
sbufWriteU8(dst, currentControlRateProfile->thrExpo8);
sbufWriteU16(dst, currentControlRateProfile->tpa_breakpoint);
sbufWriteU8(dst, currentControlRateProfile->rcExpo[FD_YAW]);
sbufWriteU8(dst, currentControlRateProfile->rcRates[FD_YAW]);
sbufWriteU8(dst, currentControlRateProfile->rcRates[FD_PITCH]);
sbufWriteU8(dst, currentControlRateProfile->rcExpo[FD_PITCH]);
// added in 1.41
sbufWriteU8(dst, currentControlRateProfile->throttle_limit_type);
sbufWriteU8(dst, currentControlRateProfile->throttle_limit_percent);
// added in 1.42
sbufWriteU16(dst, currentControlRateProfile->rate_limit[FD_ROLL]);
sbufWriteU16(dst, currentControlRateProfile->rate_limit[FD_PITCH]);
sbufWriteU16(dst, currentControlRateProfile->rate_limit[FD_YAW]);
// added in 1.43
sbufWriteU8(dst, currentControlRateProfile->rates_type);
break;
case MSP_PID:
for (int i = 0; i < PID_ITEM_COUNT; i++) {
sbufWriteU8(dst, currentPidProfile->pid[i].P);
sbufWriteU8(dst, currentPidProfile->pid[i].I);
sbufWriteU8(dst, currentPidProfile->pid[i].D);
}
break;
case MSP_PIDNAMES:
for (const char *c = pidNames; *c; c++) {
sbufWriteU8(dst, *c);
}
break;
case MSP_PID_CONTROLLER:
sbufWriteU8(dst, PID_CONTROLLER_BETAFLIGHT);
break;
case MSP_MODE_RANGES:
for (int i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
const modeActivationCondition_t *mac = modeActivationConditions(i);
const box_t *box = findBoxByBoxId(mac->modeId);
sbufWriteU8(dst, box->permanentId);
sbufWriteU8(dst, mac->auxChannelIndex);
sbufWriteU8(dst, mac->range.startStep);
sbufWriteU8(dst, mac->range.endStep);
}
break;
case MSP_MODE_RANGES_EXTRA:
sbufWriteU8(dst, MAX_MODE_ACTIVATION_CONDITION_COUNT); // prepend number of EXTRAs array elements
for (int i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
const modeActivationCondition_t *mac = modeActivationConditions(i);
const box_t *box = findBoxByBoxId(mac->modeId);
const box_t *linkedBox = findBoxByBoxId(mac->linkedTo);
sbufWriteU8(dst, box->permanentId); // each element is aligned with MODE_RANGES by the permanentId
sbufWriteU8(dst, mac->modeLogic);
sbufWriteU8(dst, linkedBox->permanentId);
}
break;
case MSP_ADJUSTMENT_RANGES:
for (int i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
const adjustmentRange_t *adjRange = adjustmentRanges(i);
sbufWriteU8(dst, 0); // was adjRange->adjustmentIndex
sbufWriteU8(dst, adjRange->auxChannelIndex);
sbufWriteU8(dst, adjRange->range.startStep);
sbufWriteU8(dst, adjRange->range.endStep);
sbufWriteU8(dst, adjRange->adjustmentConfig);
sbufWriteU8(dst, adjRange->auxSwitchChannelIndex);
}
break;
case MSP_MOTOR_CONFIG:
sbufWriteU16(dst, motorConfig()->minthrottle);
sbufWriteU16(dst, motorConfig()->maxthrottle);
sbufWriteU16(dst, motorConfig()->mincommand);
// API 1.42
sbufWriteU8(dst, getMotorCount());
sbufWriteU8(dst, motorConfig()->motorPoleCount);
#ifdef USE_DSHOT_TELEMETRY
sbufWriteU8(dst, motorConfig()->dev.useDshotTelemetry);
#else
sbufWriteU8(dst, 0);
#endif
#ifdef USE_ESC_SENSOR
sbufWriteU8(dst, featureIsEnabled(FEATURE_ESC_SENSOR)); // ESC sensor available
#else
sbufWriteU8(dst, 0);
#endif
break;
#if defined(USE_ESC_SENSOR)
// Deprecated in favor of MSP_MOTOR_TELEMETY as of API version 1.42
case MSP_ESC_SENSOR_DATA:
if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
sbufWriteU8(dst, getMotorCount());
for (int i = 0; i < getMotorCount(); i++) {
const escSensorData_t *escData = getEscSensorData(i);
sbufWriteU8(dst, escData->temperature);
sbufWriteU16(dst, escData->rpm);
}
} else {
unsupportedCommand = true;
}
break;
#endif
#ifdef USE_GPS
case MSP_GPS_CONFIG:
sbufWriteU8(dst, gpsConfig()->provider);
sbufWriteU8(dst, gpsConfig()->sbasMode);
sbufWriteU8(dst, gpsConfig()->autoConfig);
sbufWriteU8(dst, gpsConfig()->autoBaud);
// Added in API version 1.43
sbufWriteU8(dst, gpsConfig()->gps_set_home_point_once);
sbufWriteU8(dst, gpsConfig()->gps_ublox_use_galileo);
break;
case MSP_RAW_GPS:
sbufWriteU8(dst, STATE(GPS_FIX));
sbufWriteU8(dst, gpsSol.numSat);
sbufWriteU32(dst, gpsSol.llh.lat);
sbufWriteU32(dst, gpsSol.llh.lon);
sbufWriteU16(dst, (uint16_t)constrain(gpsSol.llh.altCm / 100, 0, UINT16_MAX)); // alt changed from 1m to 0.01m per lsb since MSP API 1.39 by RTH. To maintain backwards compatibility compensate to 1m per lsb in MSP again.
sbufWriteU16(dst, gpsSol.groundSpeed);
sbufWriteU16(dst, gpsSol.groundCourse);
// Added in API version 1.44
sbufWriteU16(dst, gpsSol.hdop);
break;
case MSP_COMP_GPS:
sbufWriteU16(dst, GPS_distanceToHome);
sbufWriteU16(dst, GPS_directionToHome);
sbufWriteU8(dst, GPS_update & 1);
break;
case MSP_GPSSVINFO:
sbufWriteU8(dst, GPS_numCh);
for (int i = 0; i < GPS_numCh; i++) {
sbufWriteU8(dst, GPS_svinfo_chn[i]);
sbufWriteU8(dst, GPS_svinfo_svid[i]);
sbufWriteU8(dst, GPS_svinfo_quality[i]);
sbufWriteU8(dst, GPS_svinfo_cno[i]);
}
break;
#ifdef USE_GPS_RESCUE
case MSP_GPS_RESCUE:
sbufWriteU16(dst, gpsRescueConfig()->angle);
sbufWriteU16(dst, gpsRescueConfig()->initialAltitudeM);
sbufWriteU16(dst, gpsRescueConfig()->descentDistanceM);
sbufWriteU16(dst, gpsRescueConfig()->rescueGroundspeed);
sbufWriteU16(dst, gpsRescueConfig()->throttleMin);
sbufWriteU16(dst, gpsRescueConfig()->throttleMax);
sbufWriteU16(dst, gpsRescueConfig()->throttleHover);
sbufWriteU8(dst, gpsRescueConfig()->sanityChecks);
sbufWriteU8(dst, gpsRescueConfig()->minSats);
// Added in API version 1.43
sbufWriteU16(dst, gpsRescueConfig()->ascendRate);
sbufWriteU16(dst, gpsRescueConfig()->descendRate);
sbufWriteU8(dst, gpsRescueConfig()->allowArmingWithoutFix);
sbufWriteU8(dst, gpsRescueConfig()->altitudeMode);
break;
case MSP_GPS_RESCUE_PIDS:
sbufWriteU16(dst, gpsRescueConfig()->throttleP);
sbufWriteU16(dst, gpsRescueConfig()->throttleI);
sbufWriteU16(dst, gpsRescueConfig()->throttleD);
sbufWriteU16(dst, gpsRescueConfig()->velP);
sbufWriteU16(dst, gpsRescueConfig()->velI);
sbufWriteU16(dst, gpsRescueConfig()->velD);
sbufWriteU16(dst, gpsRescueConfig()->yawP);
break;
#endif
#endif
#if defined(USE_ACC)
case MSP_ACC_TRIM:
sbufWriteU16(dst, accelerometerConfig()->accelerometerTrims.values.pitch);
sbufWriteU16(dst, accelerometerConfig()->accelerometerTrims.values.roll);
break;
#endif
case MSP_MIXER_CONFIG:
sbufWriteU8(dst, mixerConfig()->mixerMode);
sbufWriteU8(dst, mixerConfig()->yaw_motors_reversed);
break;
case MSP_RX_CONFIG:
sbufWriteU8(dst, rxConfig()->serialrx_provider);
sbufWriteU16(dst, rxConfig()->maxcheck);
sbufWriteU16(dst, rxConfig()->midrc);
sbufWriteU16(dst, rxConfig()->mincheck);
sbufWriteU8(dst, rxConfig()->spektrum_sat_bind);
sbufWriteU16(dst, rxConfig()->rx_min_usec);
sbufWriteU16(dst, rxConfig()->rx_max_usec);
sbufWriteU8(dst, rxConfig()->rcInterpolation);
sbufWriteU8(dst, rxConfig()->rcInterpolationInterval);
sbufWriteU16(dst, rxConfig()->airModeActivateThreshold * 10 + 1000);
#ifdef USE_RX_SPI
sbufWriteU8(dst, rxSpiConfig()->rx_spi_protocol);
sbufWriteU32(dst, rxSpiConfig()->rx_spi_id);
sbufWriteU8(dst, rxSpiConfig()->rx_spi_rf_channel_count);
#else
sbufWriteU8(dst, 0);
sbufWriteU32(dst, 0);
sbufWriteU8(dst, 0);
#endif
sbufWriteU8(dst, rxConfig()->fpvCamAngleDegrees);
sbufWriteU8(dst, rxConfig()->rcInterpolationChannels);
#if defined(USE_RC_SMOOTHING_FILTER)
sbufWriteU8(dst, rxConfig()->rc_smoothing_type);
sbufWriteU8(dst, rxConfig()->rc_smoothing_input_cutoff);
sbufWriteU8(dst, rxConfig()->rc_smoothing_derivative_cutoff);
sbufWriteU8(dst, rxConfig()->rc_smoothing_input_type);
sbufWriteU8(dst, rxConfig()->rc_smoothing_derivative_type);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
#endif
#if defined(USE_USB_CDC_HID)
sbufWriteU8(dst, usbDevConfig()->type);
#else
sbufWriteU8(dst, 0);
#endif
// Added in MSP API 1.42
#if defined(USE_RC_SMOOTHING_FILTER)
sbufWriteU8(dst, rxConfig()->rc_smoothing_auto_factor);
#else
sbufWriteU8(dst, 0);
#endif
break;
case MSP_FAILSAFE_CONFIG:
sbufWriteU8(dst, failsafeConfig()->failsafe_delay);
sbufWriteU8(dst, failsafeConfig()->failsafe_off_delay);
sbufWriteU16(dst, failsafeConfig()->failsafe_throttle);
sbufWriteU8(dst, failsafeConfig()->failsafe_switch_mode);
sbufWriteU16(dst, failsafeConfig()->failsafe_throttle_low_delay);
sbufWriteU8(dst, failsafeConfig()->failsafe_procedure);
break;
case MSP_RXFAIL_CONFIG:
for (int i = 0; i < rxRuntimeState.channelCount; i++) {
sbufWriteU8(dst, rxFailsafeChannelConfigs(i)->mode);
sbufWriteU16(dst, RXFAIL_STEP_TO_CHANNEL_VALUE(rxFailsafeChannelConfigs(i)->step));
}
break;
case MSP_RSSI_CONFIG:
sbufWriteU8(dst, rxConfig()->rssi_channel);
break;
case MSP_RX_MAP:
sbufWriteData(dst, rxConfig()->rcmap, RX_MAPPABLE_CHANNEL_COUNT);
break;
case MSP_CF_SERIAL_CONFIG:
for (int i = 0; i < SERIAL_PORT_COUNT; i++) {
if (!serialIsPortAvailable(serialConfig()->portConfigs[i].identifier)) {
continue;
};
sbufWriteU8(dst, serialConfig()->portConfigs[i].identifier);
sbufWriteU16(dst, serialConfig()->portConfigs[i].functionMask);
sbufWriteU8(dst, serialConfig()->portConfigs[i].msp_baudrateIndex);
sbufWriteU8(dst, serialConfig()->portConfigs[i].gps_baudrateIndex);
sbufWriteU8(dst, serialConfig()->portConfigs[i].telemetry_baudrateIndex);
sbufWriteU8(dst, serialConfig()->portConfigs[i].blackbox_baudrateIndex);
}
break;
case MSP2_COMMON_SERIAL_CONFIG: {
uint8_t count = 0;
for (int i = 0; i < SERIAL_PORT_COUNT; i++) {
if (serialIsPortAvailable(serialConfig()->portConfigs[i].identifier)) {
count++;
}
}
sbufWriteU8(dst, count);
for (int i = 0; i < SERIAL_PORT_COUNT; i++) {
if (!serialIsPortAvailable(serialConfig()->portConfigs[i].identifier)) {
continue;
};
sbufWriteU8(dst, serialConfig()->portConfigs[i].identifier);
sbufWriteU32(dst, serialConfig()->portConfigs[i].functionMask);
sbufWriteU8(dst, serialConfig()->portConfigs[i].msp_baudrateIndex);
sbufWriteU8(dst, serialConfig()->portConfigs[i].gps_baudrateIndex);
sbufWriteU8(dst, serialConfig()->portConfigs[i].telemetry_baudrateIndex);
sbufWriteU8(dst, serialConfig()->portConfigs[i].blackbox_baudrateIndex);
}
break;
}
#ifdef USE_LED_STRIP_STATUS_MODE
case MSP_LED_COLORS:
for (int i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
const hsvColor_t *color = &ledStripStatusModeConfig()->colors[i];
sbufWriteU16(dst, color->h);
sbufWriteU8(dst, color->s);
sbufWriteU8(dst, color->v);
}
break;
#endif
#ifdef USE_LED_STRIP
case MSP_LED_STRIP_CONFIG:
for (int i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
#ifdef USE_LED_STRIP_STATUS_MODE
const ledConfig_t *ledConfig = &ledStripStatusModeConfig()->ledConfigs[i];
sbufWriteU32(dst, *ledConfig);
#else
sbufWriteU32(dst, 0);
#endif
}
// API 1.41 - add indicator for advanced profile support and the current profile selection
// 0 = basic ledstrip available
// 1 = advanced ledstrip available
#ifdef USE_LED_STRIP_STATUS_MODE
sbufWriteU8(dst, 1); // advanced ledstrip available
#else
sbufWriteU8(dst, 0); // only simple ledstrip available
#endif
sbufWriteU8(dst, ledStripConfig()->ledstrip_profile);
break;
#endif
#ifdef USE_LED_STRIP_STATUS_MODE
case MSP_LED_STRIP_MODECOLOR:
for (int i = 0; i < LED_MODE_COUNT; i++) {
for (int j = 0; j < LED_DIRECTION_COUNT; j++) {
sbufWriteU8(dst, i);
sbufWriteU8(dst, j);
sbufWriteU8(dst, ledStripStatusModeConfig()->modeColors[i].color[j]);
}
}
for (int j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
sbufWriteU8(dst, LED_MODE_COUNT);
sbufWriteU8(dst, j);
sbufWriteU8(dst, ledStripStatusModeConfig()->specialColors.color[j]);
}
sbufWriteU8(dst, LED_AUX_CHANNEL);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, ledStripStatusModeConfig()->ledstrip_aux_channel);
break;
#endif
case MSP_DATAFLASH_SUMMARY:
serializeDataflashSummaryReply(dst);
break;
case MSP_BLACKBOX_CONFIG:
#ifdef USE_BLACKBOX
sbufWriteU8(dst, 1); //Blackbox supported
sbufWriteU8(dst, blackboxConfig()->device);
sbufWriteU8(dst, 1); // Rate numerator, not used anymore
sbufWriteU8(dst, blackboxGetRateDenom());
sbufWriteU16(dst, blackboxGetPRatio());
sbufWriteU8(dst, blackboxConfig()->sample_rate);
#else
sbufWriteU8(dst, 0); // Blackbox not supported
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU16(dst, 0);
sbufWriteU8(dst, 0);
#endif
break;
case MSP_SDCARD_SUMMARY:
serializeSDCardSummaryReply(dst);
break;
case MSP_MOTOR_3D_CONFIG:
sbufWriteU16(dst, flight3DConfig()->deadband3d_low);
sbufWriteU16(dst, flight3DConfig()->deadband3d_high);
sbufWriteU16(dst, flight3DConfig()->neutral3d);
break;
case MSP_RC_DEADBAND:
sbufWriteU8(dst, rcControlsConfig()->deadband);
sbufWriteU8(dst, rcControlsConfig()->yaw_deadband);
sbufWriteU8(dst, rcControlsConfig()->alt_hold_deadband);
sbufWriteU16(dst, flight3DConfig()->deadband3d_throttle);
break;
case MSP_SENSOR_ALIGNMENT: {
uint8_t gyroAlignment;
#ifdef USE_MULTI_GYRO
switch (gyroConfig()->gyro_to_use) {
case GYRO_CONFIG_USE_GYRO_2:
gyroAlignment = gyroDeviceConfig(1)->alignment;
break;
case GYRO_CONFIG_USE_GYRO_BOTH:
// for dual-gyro in "BOTH" mode we only read/write gyro 0
default:
gyroAlignment = gyroDeviceConfig(0)->alignment;
break;
}
#else
gyroAlignment = gyroDeviceConfig(0)->alignment;
#endif
sbufWriteU8(dst, gyroAlignment);
sbufWriteU8(dst, gyroAlignment); // Starting with 4.0 gyro and acc alignment are the same
#if defined(USE_MAG)
sbufWriteU8(dst, compassConfig()->mag_alignment);
#else
sbufWriteU8(dst, 0);
#endif
// API 1.41 - Add multi-gyro indicator, selected gyro, and support for separate gyro 1 & 2 alignment
sbufWriteU8(dst, getGyroDetectionFlags());
#ifdef USE_MULTI_GYRO
sbufWriteU8(dst, gyroConfig()->gyro_to_use);
sbufWriteU8(dst, gyroDeviceConfig(0)->alignment);
sbufWriteU8(dst, gyroDeviceConfig(1)->alignment);
#else
sbufWriteU8(dst, GYRO_CONFIG_USE_GYRO_1);
sbufWriteU8(dst, gyroDeviceConfig(0)->alignment);
sbufWriteU8(dst, ALIGN_DEFAULT);
#endif
break;
}
case MSP_ADVANCED_CONFIG:
sbufWriteU8(dst, 1); // was gyroConfig()->gyro_sync_denom - removed in API 1.43
sbufWriteU8(dst, pidConfig()->pid_process_denom);
sbufWriteU8(dst, motorConfig()->dev.useUnsyncedPwm);
sbufWriteU8(dst, motorConfig()->dev.motorPwmProtocol);
sbufWriteU16(dst, motorConfig()->dev.motorPwmRate);
sbufWriteU16(dst, motorConfig()->digitalIdleOffsetValue);
sbufWriteU8(dst, 0); // DEPRECATED: gyro_use_32kHz
sbufWriteU8(dst, motorConfig()->dev.motorPwmInversion);
sbufWriteU8(dst, gyroConfig()->gyro_to_use);
sbufWriteU8(dst, gyroConfig()->gyro_high_fsr);
sbufWriteU8(dst, gyroConfig()->gyroMovementCalibrationThreshold);
sbufWriteU16(dst, gyroConfig()->gyroCalibrationDuration);
sbufWriteU16(dst, gyroConfig()->gyro_offset_yaw);
sbufWriteU8(dst, gyroConfig()->checkOverflow);
//Added in MSP API 1.42
sbufWriteU8(dst, systemConfig()->debug_mode);
sbufWriteU8(dst, DEBUG_COUNT);
break;
case MSP_FILTER_CONFIG :
sbufWriteU8(dst, gyroConfig()->gyro_lowpass_hz);
sbufWriteU16(dst, currentPidProfile->dterm_lowpass_hz);
sbufWriteU16(dst, currentPidProfile->yaw_lowpass_hz);
sbufWriteU16(dst, gyroConfig()->gyro_soft_notch_hz_1);
sbufWriteU16(dst, gyroConfig()->gyro_soft_notch_cutoff_1);
sbufWriteU16(dst, currentPidProfile->dterm_notch_hz);
sbufWriteU16(dst, currentPidProfile->dterm_notch_cutoff);
sbufWriteU16(dst, gyroConfig()->gyro_soft_notch_hz_2);
sbufWriteU16(dst, gyroConfig()->gyro_soft_notch_cutoff_2);
sbufWriteU8(dst, currentPidProfile->dterm_filter_type);
sbufWriteU8(dst, gyroConfig()->gyro_hardware_lpf);
sbufWriteU8(dst, 0); // DEPRECATED: gyro_32khz_hardware_lpf
sbufWriteU16(dst, gyroConfig()->gyro_lowpass_hz);
sbufWriteU16(dst, gyroConfig()->gyro_lowpass2_hz);
sbufWriteU8(dst, gyroConfig()->gyro_lowpass_type);
sbufWriteU8(dst, gyroConfig()->gyro_lowpass2_type);
sbufWriteU16(dst, currentPidProfile->dterm_lowpass2_hz);
// Added in MSP API 1.41
sbufWriteU8(dst, currentPidProfile->dterm_filter2_type);
#if defined(USE_DYN_LPF)
sbufWriteU16(dst, gyroConfig()->dyn_lpf_gyro_min_hz);
sbufWriteU16(dst, gyroConfig()->dyn_lpf_gyro_max_hz);
sbufWriteU16(dst, currentPidProfile->dyn_lpf_dterm_min_hz);
sbufWriteU16(dst, currentPidProfile->dyn_lpf_dterm_max_hz);
#else
sbufWriteU16(dst, 0);
sbufWriteU16(dst, 0);
sbufWriteU16(dst, 0);
sbufWriteU16(dst, 0);
#endif
// Added in MSP API 1.42
#if defined(USE_GYRO_DATA_ANALYSE)
sbufWriteU8(dst, 0); // DEPRECATED 1.43: dyn_notch_range
sbufWriteU8(dst, gyroConfig()->dyn_notch_width_percent);
sbufWriteU16(dst, gyroConfig()->dyn_notch_q);
sbufWriteU16(dst, gyroConfig()->dyn_notch_min_hz);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU16(dst, 0);
sbufWriteU16(dst, 0);
#endif
#if defined(USE_RPM_FILTER)
sbufWriteU8(dst, rpmFilterConfig()->gyro_rpm_notch_harmonics);
sbufWriteU8(dst, rpmFilterConfig()->gyro_rpm_notch_min);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
#endif
#if defined(USE_GYRO_DATA_ANALYSE)
// Added in MSP API 1.43
sbufWriteU16(dst, gyroConfig()->dyn_notch_max_hz);
#else
sbufWriteU16(dst, 0);
#endif
#if defined(USE_DYN_LPF)
// Added in MSP API 1.44
sbufWriteU8(dst, currentPidProfile->dyn_lpf_curve_expo);
#else
sbufWriteU8(dst, 0);
#endif
break;
case MSP_PID_ADVANCED:
sbufWriteU16(dst, 0);
sbufWriteU16(dst, 0);
sbufWriteU16(dst, 0); // was pidProfile.yaw_p_limit
sbufWriteU8(dst, 0); // reserved
sbufWriteU8(dst, 0); // was vbatPidCompensation
sbufWriteU8(dst, currentPidProfile->feedForwardTransition);
sbufWriteU8(dst, 0); // was low byte of currentPidProfile->dtermSetpointWeight
sbufWriteU8(dst, 0); // reserved
sbufWriteU8(dst, 0); // reserved
sbufWriteU8(dst, 0); // reserved
sbufWriteU16(dst, currentPidProfile->rateAccelLimit);
sbufWriteU16(dst, currentPidProfile->yawRateAccelLimit);
sbufWriteU8(dst, currentPidProfile->levelAngleLimit);
sbufWriteU8(dst, 0); // was pidProfile.levelSensitivity
sbufWriteU16(dst, currentPidProfile->itermThrottleThreshold);
sbufWriteU16(dst, currentPidProfile->itermAcceleratorGain);
sbufWriteU16(dst, 0); // was currentPidProfile->dtermSetpointWeight
sbufWriteU8(dst, currentPidProfile->iterm_rotation);
sbufWriteU8(dst, 0); // was currentPidProfile->smart_feedforward
#if defined(USE_ITERM_RELAX)
sbufWriteU8(dst, currentPidProfile->iterm_relax);
sbufWriteU8(dst, currentPidProfile->iterm_relax_type);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
#endif
#if defined(USE_ABSOLUTE_CONTROL)
sbufWriteU8(dst, currentPidProfile->abs_control_gain);
#else
sbufWriteU8(dst, 0);
#endif
#if defined(USE_THROTTLE_BOOST)
sbufWriteU8(dst, currentPidProfile->throttle_boost);
#else
sbufWriteU8(dst, 0);
#endif
#if defined(USE_ACRO_TRAINER)
sbufWriteU8(dst, currentPidProfile->acro_trainer_angle_limit);
#else
sbufWriteU8(dst, 0);
#endif
sbufWriteU16(dst, currentPidProfile->pid[PID_ROLL].F);
sbufWriteU16(dst, currentPidProfile->pid[PID_PITCH].F);
sbufWriteU16(dst, currentPidProfile->pid[PID_YAW].F);
sbufWriteU8(dst, currentPidProfile->antiGravityMode);
#if defined(USE_D_MIN)
sbufWriteU8(dst, currentPidProfile->d_min[PID_ROLL]);
sbufWriteU8(dst, currentPidProfile->d_min[PID_PITCH]);
sbufWriteU8(dst, currentPidProfile->d_min[PID_YAW]);
sbufWriteU8(dst, currentPidProfile->d_min_gain);
sbufWriteU8(dst, currentPidProfile->d_min_advance);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
#endif
#if defined(USE_INTEGRATED_YAW_CONTROL)
sbufWriteU8(dst, currentPidProfile->use_integrated_yaw);
sbufWriteU8(dst, currentPidProfile->integrated_yaw_relax);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
#endif
#if defined(USE_ITERM_RELAX)
// Added in MSP API 1.42
sbufWriteU8(dst, currentPidProfile->iterm_relax_cutoff);
#else
sbufWriteU8(dst, 0);
#endif
// Added in MSP API 1.43
sbufWriteU8(dst, currentPidProfile->motor_output_limit);
sbufWriteU8(dst, currentPidProfile->auto_profile_cell_count);
#if defined(USE_DYN_IDLE)
sbufWriteU8(dst, currentPidProfile->dyn_idle_min_rpm);
#else
sbufWriteU8(dst, 0);
#endif
// Added in MSP API 1.44
#if defined(USE_INTERPOLATED_SP)
sbufWriteU8(dst, currentPidProfile->ff_interpolate_sp);
sbufWriteU8(dst, currentPidProfile->ff_smooth_factor);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
#endif
sbufWriteU8(dst, currentPidProfile->ff_boost);
#if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
sbufWriteU8(dst, currentPidProfile->vbat_sag_compensation);
#else
sbufWriteU8(dst, 0);
#endif
#if defined(USE_THRUST_LINEARIZATION)
sbufWriteU8(dst, currentPidProfile->thrustLinearization);
#else
sbufWriteU8(dst, 0);
#endif
break;
case MSP_SENSOR_CONFIG:
#if defined(USE_ACC)
sbufWriteU8(dst, accelerometerConfig()->acc_hardware);
#else
sbufWriteU8(dst, 0);
#endif
#ifdef USE_BARO
sbufWriteU8(dst, barometerConfig()->baro_hardware);
#else
sbufWriteU8(dst, BARO_NONE);
#endif
#ifdef USE_MAG
sbufWriteU8(dst, compassConfig()->mag_hardware);
#else
sbufWriteU8(dst, MAG_NONE);
#endif
break;
#if defined(USE_VTX_COMMON)
case MSP_VTX_CONFIG:
{
const vtxDevice_t *vtxDevice = vtxCommonDevice();
unsigned vtxStatus = 0;
vtxDevType_e vtxType = VTXDEV_UNKNOWN;
uint8_t deviceIsReady = 0;
if (vtxDevice) {
vtxCommonGetStatus(vtxDevice, &vtxStatus);
vtxType = vtxCommonGetDeviceType(vtxDevice);
deviceIsReady = vtxCommonDeviceIsReady(vtxDevice) ? 1 : 0;
}
sbufWriteU8(dst, vtxType);
sbufWriteU8(dst, vtxSettingsConfig()->band);
sbufWriteU8(dst, vtxSettingsConfig()->channel);
sbufWriteU8(dst, vtxSettingsConfig()->power);
sbufWriteU8(dst, (vtxStatus & VTX_STATUS_PIT_MODE) ? 1 : 0);
sbufWriteU16(dst, vtxSettingsConfig()->freq);
sbufWriteU8(dst, deviceIsReady);
sbufWriteU8(dst, vtxSettingsConfig()->lowPowerDisarm);
// API version 1.42
sbufWriteU16(dst, vtxSettingsConfig()->pitModeFreq);
#ifdef USE_VTX_TABLE
sbufWriteU8(dst, 1); // vtxtable is available
sbufWriteU8(dst, vtxTableConfig()->bands);
sbufWriteU8(dst, vtxTableConfig()->channels);
sbufWriteU8(dst, vtxTableConfig()->powerLevels);
#else
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
sbufWriteU8(dst, 0);
#endif
}
break;
#endif
case MSP_TX_INFO:
sbufWriteU8(dst, rssiSource);
uint8_t rtcDateTimeIsSet = 0;
#ifdef USE_RTC_TIME
dateTime_t dt;
if (rtcGetDateTime(&dt)) {
rtcDateTimeIsSet = 1;
}
#else
rtcDateTimeIsSet = RTC_NOT_SUPPORTED;
#endif
sbufWriteU8(dst, rtcDateTimeIsSet);
break;
#ifdef USE_RTC_TIME
case MSP_RTC:
{
dateTime_t dt;
if (rtcGetDateTime(&dt)) {
sbufWriteU16(dst, dt.year);
sbufWriteU8(dst, dt.month);
sbufWriteU8(dst, dt.day);
sbufWriteU8(dst, dt.hours);
sbufWriteU8(dst, dt.minutes);
sbufWriteU8(dst, dt.seconds);
sbufWriteU16(dst, dt.millis);
}
}
break;
#endif
default:
unsupportedCommand = true;
}
return !unsupportedCommand;
}
static mspResult_e mspFcProcessOutCommandWithArg(mspDescriptor_t srcDesc, int16_t cmdMSP, sbuf_t *src, sbuf_t *dst, mspPostProcessFnPtr *mspPostProcessFn)
{
switch (cmdMSP) {
case MSP_BOXNAMES:
{
const int page = sbufBytesRemaining(src) ? sbufReadU8(src) : 0;
serializeBoxReply(dst, page, &serializeBoxNameFn);
}
break;
case MSP_BOXIDS:
{
const int page = sbufBytesRemaining(src) ? sbufReadU8(src) : 0;
serializeBoxReply(dst, page, &serializeBoxPermanentIdFn);
}
break;
case MSP_REBOOT:
if (sbufBytesRemaining(src)) {
rebootMode = sbufReadU8(src);
if (rebootMode >= MSP_REBOOT_COUNT
#if !defined(USE_USB_MSC)
|| rebootMode == MSP_REBOOT_MSC || rebootMode == MSP_REBOOT_MSC_UTC
#endif
) {
return MSP_RESULT_ERROR;
}
} else {
rebootMode = MSP_REBOOT_FIRMWARE;
}
sbufWriteU8(dst, rebootMode);
#if defined(USE_USB_MSC)
if (rebootMode == MSP_REBOOT_MSC) {
if (mscCheckFilesystemReady()) {
sbufWriteU8(dst, 1);
} else {
sbufWriteU8(dst, 0);
return MSP_RESULT_ACK;
}
}
#endif
if (mspPostProcessFn) {
*mspPostProcessFn = mspRebootFn;
}
break;
case MSP_MULTIPLE_MSP:
{
uint8_t maxMSPs = 0;
if (sbufBytesRemaining(src) == 0) {
return MSP_RESULT_ERROR;
}
int bytesRemaining = sbufBytesRemaining(dst) - 1; // need to keep one byte for checksum
mspPacket_t packetIn, packetOut;
sbufInit(&packetIn.buf, src->end, src->end);
uint8_t* resetInputPtr = src->ptr;
while (sbufBytesRemaining(src) && bytesRemaining > 0) {
uint8_t newMSP = sbufReadU8(src);
sbufInit(&packetOut.buf, dst->ptr, dst->end);
packetIn.cmd = newMSP;
mspFcProcessCommand(srcDesc, &packetIn, &packetOut, NULL);
uint8_t mspSize = sbufPtr(&packetOut.buf) - dst->ptr;
mspSize++; // need to add length information for each MSP
bytesRemaining -= mspSize;
if (bytesRemaining >= 0) {
maxMSPs++;
}
}
src->ptr = resetInputPtr;
sbufInit(&packetOut.buf, dst->ptr, dst->end);
for (int i = 0; i < maxMSPs; i++) {
uint8_t* sizePtr = sbufPtr(&packetOut.buf);
sbufWriteU8(&packetOut.buf, 0); // dummy
packetIn.cmd = sbufReadU8(src);
mspFcProcessCommand(srcDesc, &packetIn, &packetOut, NULL);
(*sizePtr) = sbufPtr(&packetOut.buf) - (sizePtr + 1);
}
dst->ptr = packetOut.buf.ptr;
}
break;
#ifdef USE_VTX_TABLE
case MSP_VTXTABLE_BAND:
{
const uint8_t band = sbufBytesRemaining(src) ? sbufReadU8(src) : 0;
if (band > 0 && band <= VTX_TABLE_MAX_BANDS) {
sbufWriteU8(dst, band); // band number (same as request)
sbufWriteU8(dst, VTX_TABLE_BAND_NAME_LENGTH); // band name length
for (int i = 0; i < VTX_TABLE_BAND_NAME_LENGTH; i++) { // band name bytes
sbufWriteU8(dst, vtxTableConfig()->bandNames[band - 1][i]);
}
sbufWriteU8(dst, vtxTableConfig()->bandLetters[band - 1]); // band letter
sbufWriteU8(dst, vtxTableConfig()->isFactoryBand[band - 1]); // CUSTOM = 0; FACTORY = 1
sbufWriteU8(dst, vtxTableConfig()->channels); // number of channel frequencies to follow
for (int i = 0; i < vtxTableConfig()->channels; i++) { // the frequency for each channel
sbufWriteU16(dst, vtxTableConfig()->frequency[band - 1][i]);
}
} else {
return MSP_RESULT_ERROR;
}
}
break;
case MSP_VTXTABLE_POWERLEVEL:
{
const uint8_t powerLevel = sbufBytesRemaining(src) ? sbufReadU8(src) : 0;
if (powerLevel > 0 && powerLevel <= VTX_TABLE_MAX_POWER_LEVELS) {
sbufWriteU8(dst, powerLevel); // powerLevel number (same as request)
sbufWriteU16(dst, vtxTableConfig()->powerValues[powerLevel - 1]);
sbufWriteU8(dst, VTX_TABLE_POWER_LABEL_LENGTH); // powerLevel label length
for (int i = 0; i < VTX_TABLE_POWER_LABEL_LENGTH; i++) { // powerlevel label bytes
sbufWriteU8(dst, vtxTableConfig()->powerLabels[powerLevel - 1][i]);
}
} else {
return MSP_RESULT_ERROR;
}
}
break;
#endif // USE_VTX_TABLE
case MSP_RESET_CONF:
{
#if defined(USE_CUSTOM_DEFAULTS)
defaultsType_e defaultsType = DEFAULTS_TYPE_CUSTOM;
#endif
if (sbufBytesRemaining(src) >= 1) {
// Added in MSP API 1.42
#if defined(USE_CUSTOM_DEFAULTS)
defaultsType = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
}
bool success = false;
if (!ARMING_FLAG(ARMED)) {
#if defined(USE_CUSTOM_DEFAULTS)
success = resetEEPROM(defaultsType == DEFAULTS_TYPE_CUSTOM);
#else
success = resetEEPROM(false);
#endif
if (success && mspPostProcessFn) {
rebootMode = MSP_REBOOT_FIRMWARE;
*mspPostProcessFn = mspRebootFn;
}
}
// Added in API version 1.42
sbufWriteU8(dst, success);
}
break;
default:
return MSP_RESULT_CMD_UNKNOWN;
}
return MSP_RESULT_ACK;
}
#ifdef USE_FLASHFS
static void mspFcDataFlashReadCommand(sbuf_t *dst, sbuf_t *src)
{
const unsigned int dataSize = sbufBytesRemaining(src);
const uint32_t readAddress = sbufReadU32(src);
uint16_t readLength;
bool allowCompression = false;
bool useLegacyFormat;
if (dataSize >= sizeof(uint32_t) + sizeof(uint16_t)) {
readLength = sbufReadU16(src);
if (sbufBytesRemaining(src)) {
allowCompression = sbufReadU8(src);
}
useLegacyFormat = false;
} else {
readLength = 128;
useLegacyFormat = true;
}
serializeDataflashReadReply(dst, readAddress, readLength, useLegacyFormat, allowCompression);
}
#endif
static mspResult_e mspProcessInCommand(mspDescriptor_t srcDesc, int16_t cmdMSP, sbuf_t *src)
{
uint32_t i;
uint8_t value;
const unsigned int dataSize = sbufBytesRemaining(src);
switch (cmdMSP) {
case MSP_SELECT_SETTING:
value = sbufReadU8(src);
if ((value & RATEPROFILE_MASK) == 0) {
if (!ARMING_FLAG(ARMED)) {
if (value >= PID_PROFILE_COUNT) {
value = 0;
}
changePidProfile(value);
}
} else {
value = value & ~RATEPROFILE_MASK;
if (value >= CONTROL_RATE_PROFILE_COUNT) {
value = 0;
}
changeControlRateProfile(value);
}
break;
case MSP_COPY_PROFILE:
value = sbufReadU8(src); // 0 = pid profile, 1 = control rate profile
uint8_t dstProfileIndex = sbufReadU8(src);
uint8_t srcProfileIndex = sbufReadU8(src);
if (value == 0) {
pidCopyProfile(dstProfileIndex, srcProfileIndex);
}
else if (value == 1) {
copyControlRateProfile(dstProfileIndex, srcProfileIndex);
}
break;
#if defined(USE_GPS) || defined(USE_MAG)
case MSP_SET_HEADING:
magHold = sbufReadU16(src);
break;
#endif
case MSP_SET_RAW_RC:
#ifdef USE_RX_MSP
{
uint8_t channelCount = dataSize / sizeof(uint16_t);
if (channelCount > MAX_SUPPORTED_RC_CHANNEL_COUNT) {
return MSP_RESULT_ERROR;
} else {
uint16_t frame[MAX_SUPPORTED_RC_CHANNEL_COUNT];
for (int i = 0; i < channelCount; i++) {
frame[i] = sbufReadU16(src);
}
rxMspFrameReceive(frame, channelCount);
}
}
#endif
break;
#if defined(USE_ACC)
case MSP_SET_ACC_TRIM:
accelerometerConfigMutable()->accelerometerTrims.values.pitch = sbufReadU16(src);
accelerometerConfigMutable()->accelerometerTrims.values.roll = sbufReadU16(src);
break;
#endif
case MSP_SET_ARMING_CONFIG:
armingConfigMutable()->auto_disarm_delay = sbufReadU8(src);
sbufReadU8(src); // reserved
if (sbufBytesRemaining(src)) {
imuConfigMutable()->small_angle = sbufReadU8(src);
}
break;
case MSP_SET_PID_CONTROLLER:
break;
case MSP_SET_PID:
for (int i = 0; i < PID_ITEM_COUNT; i++) {
currentPidProfile->pid[i].P = sbufReadU8(src);
currentPidProfile->pid[i].I = sbufReadU8(src);
currentPidProfile->pid[i].D = sbufReadU8(src);
}
pidInitConfig(currentPidProfile);
break;
case MSP_SET_MODE_RANGE:
i = sbufReadU8(src);
if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
i = sbufReadU8(src);
const box_t *box = findBoxByPermanentId(i);
if (box) {
mac->modeId = box->boxId;
mac->auxChannelIndex = sbufReadU8(src);
mac->range.startStep = sbufReadU8(src);
mac->range.endStep = sbufReadU8(src);
if (sbufBytesRemaining(src) != 0) {
mac->modeLogic = sbufReadU8(src);
i = sbufReadU8(src);
mac->linkedTo = findBoxByPermanentId(i)->boxId;
}
rcControlsInit();
} else {
return MSP_RESULT_ERROR;
}
} else {
return MSP_RESULT_ERROR;
}
break;
case MSP_SET_ADJUSTMENT_RANGE:
i = sbufReadU8(src);
if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
adjustmentRange_t *adjRange = adjustmentRangesMutable(i);
sbufReadU8(src); // was adjRange->adjustmentIndex
adjRange->auxChannelIndex = sbufReadU8(src);
adjRange->range.startStep = sbufReadU8(src);
adjRange->range.endStep = sbufReadU8(src);
adjRange->adjustmentConfig = sbufReadU8(src);
adjRange->auxSwitchChannelIndex = sbufReadU8(src);
activeAdjustmentRangeReset();
} else {
return MSP_RESULT_ERROR;
}
break;
case MSP_SET_RC_TUNING:
if (sbufBytesRemaining(src) >= 10) {
value = sbufReadU8(src);
if (currentControlRateProfile->rcRates[FD_PITCH] == currentControlRateProfile->rcRates[FD_ROLL]) {
currentControlRateProfile->rcRates[FD_PITCH] = value;
}
currentControlRateProfile->rcRates[FD_ROLL] = value;
value = sbufReadU8(src);
if (currentControlRateProfile->rcExpo[FD_PITCH] == currentControlRateProfile->rcExpo[FD_ROLL]) {
currentControlRateProfile->rcExpo[FD_PITCH] = value;
}
currentControlRateProfile->rcExpo[FD_ROLL] = value;
for (int i = 0; i < 3; i++) {
currentControlRateProfile->rates[i] = sbufReadU8(src);
}
value = sbufReadU8(src);
currentControlRateProfile->dynThrPID = MIN(value, CONTROL_RATE_CONFIG_TPA_MAX);
currentControlRateProfile->thrMid8 = sbufReadU8(src);
currentControlRateProfile->thrExpo8 = sbufReadU8(src);
currentControlRateProfile->tpa_breakpoint = sbufReadU16(src);
if (sbufBytesRemaining(src) >= 1) {
currentControlRateProfile->rcExpo[FD_YAW] = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 1) {
currentControlRateProfile->rcRates[FD_YAW] = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 1) {
currentControlRateProfile->rcRates[FD_PITCH] = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 1) {
currentControlRateProfile->rcExpo[FD_PITCH] = sbufReadU8(src);
}
// version 1.41
if (sbufBytesRemaining(src) >= 2) {
currentControlRateProfile->throttle_limit_type = sbufReadU8(src);
currentControlRateProfile->throttle_limit_percent = sbufReadU8(src);
}
// version 1.42
if (sbufBytesRemaining(src) >= 6) {
currentControlRateProfile->rate_limit[FD_ROLL] = sbufReadU16(src);
currentControlRateProfile->rate_limit[FD_PITCH] = sbufReadU16(src);
currentControlRateProfile->rate_limit[FD_YAW] = sbufReadU16(src);
}
// version 1.43
if (sbufBytesRemaining(src) >= 1) {
currentControlRateProfile->rates_type = sbufReadU8(src);
}
initRcProcessing();
} else {
return MSP_RESULT_ERROR;
}
break;
case MSP_SET_MOTOR_CONFIG:
motorConfigMutable()->minthrottle = sbufReadU16(src);
motorConfigMutable()->maxthrottle = sbufReadU16(src);
motorConfigMutable()->mincommand = sbufReadU16(src);
// version 1.42
if (sbufBytesRemaining(src) >= 2) {
motorConfigMutable()->motorPoleCount = sbufReadU8(src);
#if defined(USE_DSHOT_TELEMETRY)
motorConfigMutable()->dev.useDshotTelemetry = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
}
break;
#ifdef USE_GPS
case MSP_SET_GPS_CONFIG:
gpsConfigMutable()->provider = sbufReadU8(src);
gpsConfigMutable()->sbasMode = sbufReadU8(src);
gpsConfigMutable()->autoConfig = sbufReadU8(src);
gpsConfigMutable()->autoBaud = sbufReadU8(src);
if (sbufBytesRemaining(src) >= 2) {
// Added in API version 1.43
gpsConfigMutable()->gps_set_home_point_once = sbufReadU8(src);
gpsConfigMutable()->gps_ublox_use_galileo = sbufReadU8(src);
}
break;
#ifdef USE_GPS_RESCUE
case MSP_SET_GPS_RESCUE:
gpsRescueConfigMutable()->angle = sbufReadU16(src);
gpsRescueConfigMutable()->initialAltitudeM = sbufReadU16(src);
gpsRescueConfigMutable()->descentDistanceM = sbufReadU16(src);
gpsRescueConfigMutable()->rescueGroundspeed = sbufReadU16(src);
gpsRescueConfigMutable()->throttleMin = sbufReadU16(src);
gpsRescueConfigMutable()->throttleMax = sbufReadU16(src);
gpsRescueConfigMutable()->throttleHover = sbufReadU16(src);
gpsRescueConfigMutable()->sanityChecks = sbufReadU8(src);
gpsRescueConfigMutable()->minSats = sbufReadU8(src);
if (sbufBytesRemaining(src) >= 6) {
// Added in API version 1.43
gpsRescueConfigMutable()->ascendRate = sbufReadU16(src);
gpsRescueConfigMutable()->descendRate = sbufReadU16(src);
gpsRescueConfigMutable()->allowArmingWithoutFix = sbufReadU8(src);
gpsRescueConfigMutable()->altitudeMode = sbufReadU8(src);
}
break;
case MSP_SET_GPS_RESCUE_PIDS:
gpsRescueConfigMutable()->throttleP = sbufReadU16(src);
gpsRescueConfigMutable()->throttleI = sbufReadU16(src);
gpsRescueConfigMutable()->throttleD = sbufReadU16(src);
gpsRescueConfigMutable()->velP = sbufReadU16(src);
gpsRescueConfigMutable()->velI = sbufReadU16(src);
gpsRescueConfigMutable()->velD = sbufReadU16(src);
gpsRescueConfigMutable()->yawP = sbufReadU16(src);
break;
#endif
#endif
case MSP_SET_MOTOR:
for (int i = 0; i < getMotorCount(); i++) {
motor_disarmed[i] = motorConvertFromExternal(sbufReadU16(src));
}
break;
case MSP_SET_SERVO_CONFIGURATION:
#ifdef USE_SERVOS
if (dataSize != 1 + 12) {
return MSP_RESULT_ERROR;
}
i = sbufReadU8(src);
if (i >= MAX_SUPPORTED_SERVOS) {
return MSP_RESULT_ERROR;
} else {
servoParamsMutable(i)->min = sbufReadU16(src);
servoParamsMutable(i)->max = sbufReadU16(src);
servoParamsMutable(i)->middle = sbufReadU16(src);
servoParamsMutable(i)->rate = sbufReadU8(src);
servoParamsMutable(i)->forwardFromChannel = sbufReadU8(src);
servoParamsMutable(i)->reversedSources = sbufReadU32(src);
}
#endif
break;
case MSP_SET_SERVO_MIX_RULE:
#ifdef USE_SERVOS
i = sbufReadU8(src);
if (i >= MAX_SERVO_RULES) {
return MSP_RESULT_ERROR;
} else {
customServoMixersMutable(i)->targetChannel = sbufReadU8(src);
customServoMixersMutable(i)->inputSource = sbufReadU8(src);
customServoMixersMutable(i)->rate = sbufReadU8(src);
customServoMixersMutable(i)->speed = sbufReadU8(src);
customServoMixersMutable(i)->min = sbufReadU8(src);
customServoMixersMutable(i)->max = sbufReadU8(src);
customServoMixersMutable(i)->box = sbufReadU8(src);
loadCustomServoMixer();
}
#endif
break;
case MSP_SET_MOTOR_3D_CONFIG:
flight3DConfigMutable()->deadband3d_low = sbufReadU16(src);
flight3DConfigMutable()->deadband3d_high = sbufReadU16(src);
flight3DConfigMutable()->neutral3d = sbufReadU16(src);
break;
case MSP_SET_RC_DEADBAND:
rcControlsConfigMutable()->deadband = sbufReadU8(src);
rcControlsConfigMutable()->yaw_deadband = sbufReadU8(src);
rcControlsConfigMutable()->alt_hold_deadband = sbufReadU8(src);
flight3DConfigMutable()->deadband3d_throttle = sbufReadU16(src);
break;
case MSP_SET_RESET_CURR_PID:
resetPidProfile(currentPidProfile);
break;
case MSP_SET_SENSOR_ALIGNMENT: {
// maintain backwards compatibility for API < 1.41
const uint8_t gyroAlignment = sbufReadU8(src);
sbufReadU8(src); // discard deprecated acc_align
#if defined(USE_MAG)
compassConfigMutable()->mag_alignment = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
if (sbufBytesRemaining(src) >= 3) {
// API >= 1.41 - support the gyro_to_use and alignment for gyros 1 & 2
#ifdef USE_MULTI_GYRO
gyroConfigMutable()->gyro_to_use = sbufReadU8(src);
gyroDeviceConfigMutable(0)->alignment = sbufReadU8(src);
gyroDeviceConfigMutable(1)->alignment = sbufReadU8(src);
#else
sbufReadU8(src); // unused gyro_to_use
gyroDeviceConfigMutable(0)->alignment = sbufReadU8(src);
sbufReadU8(src); // unused gyro_2_sensor_align
#endif
} else {
// maintain backwards compatibility for API < 1.41
#ifdef USE_MULTI_GYRO
switch (gyroConfig()->gyro_to_use) {
case GYRO_CONFIG_USE_GYRO_2:
gyroDeviceConfigMutable(1)->alignment = gyroAlignment;
break;
case GYRO_CONFIG_USE_GYRO_BOTH:
// For dual-gyro in "BOTH" mode we'll only update gyro 0
default:
gyroDeviceConfigMutable(0)->alignment = gyroAlignment;
break;
}
#else
gyroDeviceConfigMutable(0)->alignment = gyroAlignment;
#endif
}
break;
}
case MSP_SET_ADVANCED_CONFIG:
sbufReadU8(src); // was gyroConfigMutable()->gyro_sync_denom - removed in API 1.43
pidConfigMutable()->pid_process_denom = sbufReadU8(src);
motorConfigMutable()->dev.useUnsyncedPwm = sbufReadU8(src);
motorConfigMutable()->dev.motorPwmProtocol = sbufReadU8(src);
motorConfigMutable()->dev.motorPwmRate = sbufReadU16(src);
if (sbufBytesRemaining(src) >= 2) {
motorConfigMutable()->digitalIdleOffsetValue = sbufReadU16(src);
}
if (sbufBytesRemaining(src)) {
sbufReadU8(src); // DEPRECATED: gyro_use_32khz
}
if (sbufBytesRemaining(src)) {
motorConfigMutable()->dev.motorPwmInversion = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 8) {
gyroConfigMutable()->gyro_to_use = sbufReadU8(src);
gyroConfigMutable()->gyro_high_fsr = sbufReadU8(src);
gyroConfigMutable()->gyroMovementCalibrationThreshold = sbufReadU8(src);
gyroConfigMutable()->gyroCalibrationDuration = sbufReadU16(src);
gyroConfigMutable()->gyro_offset_yaw = sbufReadU16(src);
gyroConfigMutable()->checkOverflow = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 1) {
//Added in MSP API 1.42
systemConfigMutable()->debug_mode = sbufReadU8(src);
}
validateAndFixGyroConfig();
break;
case MSP_SET_FILTER_CONFIG:
gyroConfigMutable()->gyro_lowpass_hz = sbufReadU8(src);
currentPidProfile->dterm_lowpass_hz = sbufReadU16(src);
currentPidProfile->yaw_lowpass_hz = sbufReadU16(src);
if (sbufBytesRemaining(src) >= 8) {
gyroConfigMutable()->gyro_soft_notch_hz_1 = sbufReadU16(src);
gyroConfigMutable()->gyro_soft_notch_cutoff_1 = sbufReadU16(src);
currentPidProfile->dterm_notch_hz = sbufReadU16(src);
currentPidProfile->dterm_notch_cutoff = sbufReadU16(src);
}
if (sbufBytesRemaining(src) >= 4) {
gyroConfigMutable()->gyro_soft_notch_hz_2 = sbufReadU16(src);
gyroConfigMutable()->gyro_soft_notch_cutoff_2 = sbufReadU16(src);
}
if (sbufBytesRemaining(src) >= 1) {
currentPidProfile->dterm_filter_type = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 10) {
gyroConfigMutable()->gyro_hardware_lpf = sbufReadU8(src);
sbufReadU8(src); // DEPRECATED: gyro_32khz_hardware_lpf
gyroConfigMutable()->gyro_lowpass_hz = sbufReadU16(src);
gyroConfigMutable()->gyro_lowpass2_hz = sbufReadU16(src);
gyroConfigMutable()->gyro_lowpass_type = sbufReadU8(src);
gyroConfigMutable()->gyro_lowpass2_type = sbufReadU8(src);
currentPidProfile->dterm_lowpass2_hz = sbufReadU16(src);
}
if (sbufBytesRemaining(src) >= 9) {
// Added in MSP API 1.41
currentPidProfile->dterm_filter2_type = sbufReadU8(src);
#if defined(USE_DYN_LPF)
gyroConfigMutable()->dyn_lpf_gyro_min_hz = sbufReadU16(src);
gyroConfigMutable()->dyn_lpf_gyro_max_hz = sbufReadU16(src);
currentPidProfile->dyn_lpf_dterm_min_hz = sbufReadU16(src);
currentPidProfile->dyn_lpf_dterm_max_hz = sbufReadU16(src);
#else
sbufReadU16(src);
sbufReadU16(src);
sbufReadU16(src);
sbufReadU16(src);
#endif
}
if (sbufBytesRemaining(src) >= 8) {
// Added in MSP API 1.42
#if defined(USE_GYRO_DATA_ANALYSE)
sbufReadU8(src); // DEPRECATED: dyn_notch_range
gyroConfigMutable()->dyn_notch_width_percent = sbufReadU8(src);
gyroConfigMutable()->dyn_notch_q = sbufReadU16(src);
gyroConfigMutable()->dyn_notch_min_hz = sbufReadU16(src);
#else
sbufReadU8(src);
sbufReadU8(src);
sbufReadU16(src);
sbufReadU16(src);
#endif
#if defined(USE_RPM_FILTER)
rpmFilterConfigMutable()->gyro_rpm_notch_harmonics = sbufReadU8(src);
rpmFilterConfigMutable()->gyro_rpm_notch_min = sbufReadU8(src);
#else
sbufReadU8(src);
sbufReadU8(src);
#endif
}
if (sbufBytesRemaining(src) >= 1) {
#if defined(USE_GYRO_DATA_ANALYSE)
// Added in MSP API 1.43
gyroConfigMutable()->dyn_notch_max_hz = sbufReadU16(src);
#else
sbufReadU16(src);
#endif
}
if (sbufBytesRemaining(src) >= 1) {
// Added in MSP API 1.44
#if defined(USE_DYN_LPF)
currentPidProfile->dyn_lpf_curve_expo = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
}
// reinitialize the gyro filters with the new values
validateAndFixGyroConfig();
gyroInitFilters();
// reinitialize the PID filters with the new values
pidInitFilters(currentPidProfile);
break;
case MSP_SET_PID_ADVANCED:
sbufReadU16(src);
sbufReadU16(src);
sbufReadU16(src); // was pidProfile.yaw_p_limit
sbufReadU8(src); // reserved
sbufReadU8(src); // was vbatPidCompensation
currentPidProfile->feedForwardTransition = sbufReadU8(src);
sbufReadU8(src); // was low byte of currentPidProfile->dtermSetpointWeight
sbufReadU8(src); // reserved
sbufReadU8(src); // reserved
sbufReadU8(src); // reserved
currentPidProfile->rateAccelLimit = sbufReadU16(src);
currentPidProfile->yawRateAccelLimit = sbufReadU16(src);
if (sbufBytesRemaining(src) >= 2) {
currentPidProfile->levelAngleLimit = sbufReadU8(src);
sbufReadU8(src); // was pidProfile.levelSensitivity
}
if (sbufBytesRemaining(src) >= 4) {
currentPidProfile->itermThrottleThreshold = sbufReadU16(src);
currentPidProfile->itermAcceleratorGain = sbufReadU16(src);
}
if (sbufBytesRemaining(src) >= 2) {
sbufReadU16(src); // was currentPidProfile->dtermSetpointWeight
}
if (sbufBytesRemaining(src) >= 14) {
// Added in MSP API 1.40
currentPidProfile->iterm_rotation = sbufReadU8(src);
sbufReadU8(src); // was currentPidProfile->smart_feedforward
#if defined(USE_ITERM_RELAX)
currentPidProfile->iterm_relax = sbufReadU8(src);
currentPidProfile->iterm_relax_type = sbufReadU8(src);
#else
sbufReadU8(src);
sbufReadU8(src);
#endif
#if defined(USE_ABSOLUTE_CONTROL)
currentPidProfile->abs_control_gain = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
#if defined(USE_THROTTLE_BOOST)
currentPidProfile->throttle_boost = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
#if defined(USE_ACRO_TRAINER)
currentPidProfile->acro_trainer_angle_limit = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
// PID controller feedforward terms
currentPidProfile->pid[PID_ROLL].F = sbufReadU16(src);
currentPidProfile->pid[PID_PITCH].F = sbufReadU16(src);
currentPidProfile->pid[PID_YAW].F = sbufReadU16(src);
currentPidProfile->antiGravityMode = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 7) {
// Added in MSP API 1.41
#if defined(USE_D_MIN)
currentPidProfile->d_min[PID_ROLL] = sbufReadU8(src);
currentPidProfile->d_min[PID_PITCH] = sbufReadU8(src);
currentPidProfile->d_min[PID_YAW] = sbufReadU8(src);
currentPidProfile->d_min_gain = sbufReadU8(src);
currentPidProfile->d_min_advance = sbufReadU8(src);
#else
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
#endif
#if defined(USE_INTEGRATED_YAW_CONTROL)
currentPidProfile->use_integrated_yaw = sbufReadU8(src);
currentPidProfile->integrated_yaw_relax = sbufReadU8(src);
#else
sbufReadU8(src);
sbufReadU8(src);
#endif
}
if(sbufBytesRemaining(src) >= 1) {
// Added in MSP API 1.42
#if defined(USE_ITERM_RELAX)
currentPidProfile->iterm_relax_cutoff = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
}
if (sbufBytesRemaining(src) >= 3) {
// Added in MSP API 1.43
currentPidProfile->motor_output_limit = sbufReadU8(src);
currentPidProfile->auto_profile_cell_count = sbufReadU8(src);
#if defined(USE_DYN_IDLE)
currentPidProfile->dyn_idle_min_rpm = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
}
if (sbufBytesRemaining(src) >= 5) {
// Added in MSP API 1.44
#if defined(USE_INTERPOLATED_SP)
currentPidProfile->ff_interpolate_sp = sbufReadU8(src);
currentPidProfile->ff_smooth_factor = sbufReadU8(src);
#else
sbufReadU8(src);
sbufReadU8(src);
#endif
currentPidProfile->ff_boost = sbufReadU8(src);
#if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
currentPidProfile->vbat_sag_compensation = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
#if defined(USE_THRUST_LINEARIZATION)
currentPidProfile->thrustLinearization = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
}
pidInitConfig(currentPidProfile);
break;
case MSP_SET_SENSOR_CONFIG:
#if defined(USE_ACC)
accelerometerConfigMutable()->acc_hardware = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
#if defined(USE_BARO)
barometerConfigMutable()->baro_hardware = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
#if defined(USE_MAG)
compassConfigMutable()->mag_hardware = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
break;
#ifdef USE_ACC
case MSP_ACC_CALIBRATION:
if (!ARMING_FLAG(ARMED))
accStartCalibration();
break;
#endif
#if defined(USE_MAG)
case MSP_MAG_CALIBRATION:
if (!ARMING_FLAG(ARMED)) {
compassStartCalibration();
}
#endif
break;
case MSP_EEPROM_WRITE:
if (ARMING_FLAG(ARMED)) {
return MSP_RESULT_ERROR;
}
writeEEPROM();
readEEPROM();
#ifdef USE_VTX_TABLE
if (vtxTableNeedsInit) {
vtxTableNeedsInit = false;
vtxTableInit(); // Reinitialize and refresh the in-memory copies
}
#endif
break;
#ifdef USE_BLACKBOX
case MSP_SET_BLACKBOX_CONFIG:
// Don't allow config to be updated while Blackbox is logging
if (blackboxMayEditConfig()) {
blackboxConfigMutable()->device = sbufReadU8(src);
const int rateNum = sbufReadU8(src); // was rate_num
const int rateDenom = sbufReadU8(src); // was rate_denom
uint16_t pRatio = 0;
if (sbufBytesRemaining(src) >= 2) {
// p_ratio specified, so use it directly
pRatio = sbufReadU16(src);
} else {
// p_ratio not specified in MSP, so calculate it from old rateNum and rateDenom
pRatio = blackboxCalculatePDenom(rateNum, rateDenom);
}
if (sbufBytesRemaining(src) >= 1) {
// sample_rate specified, so use it directly
blackboxConfigMutable()->sample_rate = sbufReadU8(src);
} else {
// sample_rate not specified in MSP, so calculate it from old p_ratio
blackboxConfigMutable()->sample_rate = blackboxCalculateSampleRate(pRatio);
}
}
break;
#endif
#ifdef USE_VTX_COMMON
case MSP_SET_VTX_CONFIG:
{
vtxDevice_t *vtxDevice = vtxCommonDevice();
vtxDevType_e vtxType = VTXDEV_UNKNOWN;
if (vtxDevice) {
vtxType = vtxCommonGetDeviceType(vtxDevice);
}
uint16_t newFrequency = sbufReadU16(src);
if (newFrequency <= VTXCOMMON_MSP_BANDCHAN_CHKVAL) { // Value is band and channel
const uint8_t newBand = (newFrequency / 8) + 1;
const uint8_t newChannel = (newFrequency % 8) + 1;
vtxSettingsConfigMutable()->band = newBand;
vtxSettingsConfigMutable()->channel = newChannel;
vtxSettingsConfigMutable()->freq = vtxCommonLookupFrequency(vtxDevice, newBand, newChannel);
} else if (newFrequency <= VTX_SETTINGS_MAX_FREQUENCY_MHZ) { // Value is frequency in MHz
vtxSettingsConfigMutable()->band = 0;
vtxSettingsConfigMutable()->freq = newFrequency;
}
if (sbufBytesRemaining(src) >= 2) {
vtxSettingsConfigMutable()->power = sbufReadU8(src);
const uint8_t newPitmode = sbufReadU8(src);
if (vtxType != VTXDEV_UNKNOWN) {
// Delegate pitmode to vtx directly
unsigned vtxCurrentStatus;
vtxCommonGetStatus(vtxDevice, &vtxCurrentStatus);
if ((bool)(vtxCurrentStatus & VTX_STATUS_PIT_MODE) != (bool)newPitmode) {
vtxCommonSetPitMode(vtxDevice, newPitmode);
}
}
}
if (sbufBytesRemaining(src)) {
vtxSettingsConfigMutable()->lowPowerDisarm = sbufReadU8(src);
}
// API version 1.42 - this parameter kept separate since clients may already be supplying
if (sbufBytesRemaining(src) >= 2) {
vtxSettingsConfigMutable()->pitModeFreq = sbufReadU16(src);
}
// API version 1.42 - extensions for non-encoded versions of the band, channel or frequency
if (sbufBytesRemaining(src) >= 4) {
// Added standalone values for band, channel and frequency to move
// away from the flawed encoded combined method originally implemented.
uint8_t newBand = sbufReadU8(src);
const uint8_t newChannel = sbufReadU8(src);
uint16_t newFreq = sbufReadU16(src);
if (newBand) {
newFreq = vtxCommonLookupFrequency(vtxDevice, newBand, newChannel);
}
vtxSettingsConfigMutable()->band = newBand;
vtxSettingsConfigMutable()->channel = newChannel;
vtxSettingsConfigMutable()->freq = newFreq;
}
// API version 1.42 - extensions for vtxtable support
if (sbufBytesRemaining(src) >= 4) {
#ifdef USE_VTX_TABLE
const uint8_t newBandCount = sbufReadU8(src);
const uint8_t newChannelCount = sbufReadU8(src);
const uint8_t newPowerCount = sbufReadU8(src);
if ((newBandCount > VTX_TABLE_MAX_BANDS) ||
(newChannelCount > VTX_TABLE_MAX_CHANNELS) ||
(newPowerCount > VTX_TABLE_MAX_POWER_LEVELS)) {
return MSP_RESULT_ERROR;
}
vtxTableConfigMutable()->bands = newBandCount;
vtxTableConfigMutable()->channels = newChannelCount;
vtxTableConfigMutable()->powerLevels = newPowerCount;
// boolean to determine whether the vtxtable should be cleared in
// expectation that the detailed band/channel and power level messages
// will follow to repopulate the tables
if (sbufReadU8(src)) {
for (int i = 0; i < VTX_TABLE_MAX_BANDS; i++) {
vtxTableConfigClearBand(vtxTableConfigMutable(), i);
vtxTableConfigClearChannels(vtxTableConfigMutable(), i, 0);
}
vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), 0);
vtxTableConfigClearPowerValues(vtxTableConfigMutable(), 0);
}
#else
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
#endif
}
}
break;
#endif
#ifdef USE_VTX_TABLE
case MSP_SET_VTXTABLE_BAND:
{
char bandName[VTX_TABLE_BAND_NAME_LENGTH + 1];
memset(bandName, 0, VTX_TABLE_BAND_NAME_LENGTH + 1);
uint16_t frequencies[VTX_TABLE_MAX_CHANNELS];
const uint8_t band = sbufReadU8(src);
const uint8_t bandNameLength = sbufReadU8(src);
for (int i = 0; i < bandNameLength; i++) {
const char nameChar = sbufReadU8(src);
if (i < VTX_TABLE_BAND_NAME_LENGTH) {
bandName[i] = toupper(nameChar);
}
}
const char bandLetter = toupper(sbufReadU8(src));
const bool isFactoryBand = (bool)sbufReadU8(src);
const uint8_t channelCount = sbufReadU8(src);
for (int i = 0; i < channelCount; i++) {
const uint16_t frequency = sbufReadU16(src);
if (i < vtxTableConfig()->channels) {
frequencies[i] = frequency;
}
}
if (band > 0 && band <= vtxTableConfig()->bands) {
vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames[band - 1], bandName, VTX_TABLE_BAND_NAME_LENGTH);
vtxTableConfigMutable()->bandLetters[band - 1] = bandLetter;
vtxTableConfigMutable()->isFactoryBand[band - 1] = isFactoryBand;
for (int i = 0; i < vtxTableConfig()->channels; i++) {
vtxTableConfigMutable()->frequency[band - 1][i] = frequencies[i];
}
// If this is the currently selected band then reset the frequency
if (band == vtxSettingsConfig()->band) {
uint16_t newFreq = 0;
if (vtxSettingsConfig()->channel > 0 && vtxSettingsConfig()->channel <= vtxTableConfig()->channels) {
newFreq = frequencies[vtxSettingsConfig()->channel - 1];
}
vtxSettingsConfigMutable()->freq = newFreq;
}
vtxTableNeedsInit = true; // reinintialize vtxtable after eeprom write
} else {
return MSP_RESULT_ERROR;
}
}
break;
case MSP_SET_VTXTABLE_POWERLEVEL:
{
char powerLevelLabel[VTX_TABLE_POWER_LABEL_LENGTH + 1];
memset(powerLevelLabel, 0, VTX_TABLE_POWER_LABEL_LENGTH + 1);
const uint8_t powerLevel = sbufReadU8(src);
const uint16_t powerValue = sbufReadU16(src);
const uint8_t powerLevelLabelLength = sbufReadU8(src);
for (int i = 0; i < powerLevelLabelLength; i++) {
const char labelChar = sbufReadU8(src);
if (i < VTX_TABLE_POWER_LABEL_LENGTH) {
powerLevelLabel[i] = toupper(labelChar);
}
}
if (powerLevel > 0 && powerLevel <= vtxTableConfig()->powerLevels) {
vtxTableConfigMutable()->powerValues[powerLevel - 1] = powerValue;
vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels[powerLevel - 1], powerLevelLabel, VTX_TABLE_POWER_LABEL_LENGTH);
vtxTableNeedsInit = true; // reinintialize vtxtable after eeprom write
} else {
return MSP_RESULT_ERROR;
}
}
break;
#endif
case MSP2_SET_MOTOR_OUTPUT_REORDERING:
{
const uint8_t arraySize = sbufReadU8(src);
for (unsigned i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
uint8_t value = i;
if (i < arraySize) {
value = sbufReadU8(src);
}
motorConfigMutable()->dev.motorOutputReordering[i] = value;
}
}
break;
#ifdef USE_CAMERA_CONTROL
case MSP_CAMERA_CONTROL:
{
if (ARMING_FLAG(ARMED)) {
return MSP_RESULT_ERROR;
}
const uint8_t key = sbufReadU8(src);
cameraControlKeyPress(key, 0);
}
break;
#endif
case MSP_SET_ARMING_DISABLED:
{
const uint8_t command = sbufReadU8(src);
uint8_t disableRunawayTakeoff = 0;
#ifndef USE_RUNAWAY_TAKEOFF
UNUSED(disableRunawayTakeoff);
#endif
if (sbufBytesRemaining(src)) {
disableRunawayTakeoff = sbufReadU8(src);
}
if (command) {
mspArmingDisableByDescriptor(srcDesc);
setArmingDisabled(ARMING_DISABLED_MSP);
if (ARMING_FLAG(ARMED)) {
disarm(DISARM_REASON_ARMING_DISABLED);
}
#ifdef USE_RUNAWAY_TAKEOFF
runawayTakeoffTemporaryDisable(false);
#endif
} else {
mspArmingEnableByDescriptor(srcDesc);
if (mspIsMspArmingEnabled()) {
unsetArmingDisabled(ARMING_DISABLED_MSP);
#ifdef USE_RUNAWAY_TAKEOFF
runawayTakeoffTemporaryDisable(disableRunawayTakeoff);
#endif
}
}
}
break;
#ifdef USE_FLASHFS
case MSP_DATAFLASH_ERASE:
flashfsEraseCompletely();
break;
#endif
#ifdef USE_GPS
case MSP_SET_RAW_GPS:
if (sbufReadU8(src)) {
ENABLE_STATE(GPS_FIX);
} else {
DISABLE_STATE(GPS_FIX);
}
gpsSol.numSat = sbufReadU8(src);
gpsSol.llh.lat = sbufReadU32(src);
gpsSol.llh.lon = sbufReadU32(src);
gpsSol.llh.altCm = sbufReadU16(src) * 100; // alt changed from 1m to 0.01m per lsb since MSP API 1.39 by RTH. Received MSP altitudes in 1m per lsb have to upscaled.
gpsSol.groundSpeed = sbufReadU16(src);
GPS_update |= GPS_MSP_UPDATE; // MSP data signalisation to GPS functions
break;
#endif // USE_GPS
case MSP_SET_FEATURE_CONFIG:
featureConfigReplace(sbufReadU32(src));
break;
#ifdef USE_BEEPER
case MSP_SET_BEEPER_CONFIG:
beeperConfigMutable()->beeper_off_flags = sbufReadU32(src);
if (sbufBytesRemaining(src) >= 1) {
beeperConfigMutable()->dshotBeaconTone = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 4) {
beeperConfigMutable()->dshotBeaconOffFlags = sbufReadU32(src);
}
break;
#endif
case MSP_SET_BOARD_ALIGNMENT_CONFIG:
boardAlignmentMutable()->rollDegrees = sbufReadU16(src);
boardAlignmentMutable()->pitchDegrees = sbufReadU16(src);
boardAlignmentMutable()->yawDegrees = sbufReadU16(src);
break;
case MSP_SET_MIXER_CONFIG:
#ifndef USE_QUAD_MIXER_ONLY
mixerConfigMutable()->mixerMode = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
if (sbufBytesRemaining(src) >= 1) {
mixerConfigMutable()->yaw_motors_reversed = sbufReadU8(src);
}
break;
case MSP_SET_RX_CONFIG:
rxConfigMutable()->serialrx_provider = sbufReadU8(src);
rxConfigMutable()->maxcheck = sbufReadU16(src);
rxConfigMutable()->midrc = sbufReadU16(src);
rxConfigMutable()->mincheck = sbufReadU16(src);
rxConfigMutable()->spektrum_sat_bind = sbufReadU8(src);
if (sbufBytesRemaining(src) >= 4) {
rxConfigMutable()->rx_min_usec = sbufReadU16(src);
rxConfigMutable()->rx_max_usec = sbufReadU16(src);
}
if (sbufBytesRemaining(src) >= 4) {
rxConfigMutable()->rcInterpolation = sbufReadU8(src);
rxConfigMutable()->rcInterpolationInterval = sbufReadU8(src);
rxConfigMutable()->airModeActivateThreshold = (sbufReadU16(src) - 1000) / 10;
}
if (sbufBytesRemaining(src) >= 6) {
#ifdef USE_RX_SPI
rxSpiConfigMutable()->rx_spi_protocol = sbufReadU8(src);
rxSpiConfigMutable()->rx_spi_id = sbufReadU32(src);
rxSpiConfigMutable()->rx_spi_rf_channel_count = sbufReadU8(src);
#else
sbufReadU8(src);
sbufReadU32(src);
sbufReadU8(src);
#endif
}
if (sbufBytesRemaining(src) >= 1) {
rxConfigMutable()->fpvCamAngleDegrees = sbufReadU8(src);
}
if (sbufBytesRemaining(src) >= 6) {
// Added in MSP API 1.40
rxConfigMutable()->rcInterpolationChannels = sbufReadU8(src);
#if defined(USE_RC_SMOOTHING_FILTER)
configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_type, sbufReadU8(src));
configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_input_cutoff, sbufReadU8(src));
configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_derivative_cutoff, sbufReadU8(src));
configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_input_type, sbufReadU8(src));
configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_derivative_type, sbufReadU8(src));
#else
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
#endif
}
if (sbufBytesRemaining(src) >= 1) {
// Added in MSP API 1.40
// Kept separate from the section above to work around missing Configurator support in version < 10.4.2
#if defined(USE_USB_CDC_HID)
usbDevConfigMutable()->type = sbufReadU8(src);
#else
sbufReadU8(src);
#endif
}
if (sbufBytesRemaining(src) >= 1) {
// Added in MSP API 1.42
#if defined(USE_RC_SMOOTHING_FILTER)
// Added extra validation/range constraint for rc_smoothing_auto_factor as a workaround for a bug in
// the 10.6 configurator where it was possible to submit an invalid out-of-range value. We might be
// able to remove the constraint at some point in the future once the affected versions are deprecated
// enough that the risk is low.
configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_auto_factor, constrain(sbufReadU8(src), RC_SMOOTHING_AUTO_FACTOR_MIN, RC_SMOOTHING_AUTO_FACTOR_MAX));
#else
sbufReadU8(src);
#endif
}
break;
case MSP_SET_FAILSAFE_CONFIG:
failsafeConfigMutable()->failsafe_delay = sbufReadU8(src);
failsafeConfigMutable()->failsafe_off_delay = sbufReadU8(src);
failsafeConfigMutable()->failsafe_throttle = sbufReadU16(src);
failsafeConfigMutable()->failsafe_switch_mode = sbufReadU8(src);
failsafeConfigMutable()->failsafe_throttle_low_delay = sbufReadU16(src);
failsafeConfigMutable()->failsafe_procedure = sbufReadU8(src);
break;
case MSP_SET_RXFAIL_CONFIG:
i = sbufReadU8(src);
if (i < MAX_SUPPORTED_RC_CHANNEL_COUNT) {
rxFailsafeChannelConfigsMutable(i)->mode = sbufReadU8(src);
rxFailsafeChannelConfigsMutable(i)->step = CHANNEL_VALUE_TO_RXFAIL_STEP(sbufReadU16(src));
} else {
return MSP_RESULT_ERROR;
}
break;
case MSP_SET_RSSI_CONFIG:
rxConfigMutable()->rssi_channel = sbufReadU8(src);
break;
case MSP_SET_RX_MAP:
for (int i = 0; i < RX_MAPPABLE_CHANNEL_COUNT; i++) {
rxConfigMutable()->rcmap[i] = sbufReadU8(src);
}
break;
case MSP_SET_CF_SERIAL_CONFIG:
{
uint8_t portConfigSize = sizeof(uint8_t) + sizeof(uint16_t) + (sizeof(uint8_t) * 4);
if (dataSize % portConfigSize != 0) {
return MSP_RESULT_ERROR;
}
uint8_t remainingPortsInPacket = dataSize / portConfigSize;
while (remainingPortsInPacket--) {
uint8_t identifier = sbufReadU8(src);
serialPortConfig_t *portConfig = serialFindPortConfigurationMutable(identifier);
if (!portConfig) {
return MSP_RESULT_ERROR;
}
portConfig->identifier = identifier;
portConfig->functionMask = sbufReadU16(src);
portConfig->msp_baudrateIndex = sbufReadU8(src);
portConfig->gps_baudrateIndex = sbufReadU8(src);
portConfig->telemetry_baudrateIndex = sbufReadU8(src);
portConfig->blackbox_baudrateIndex = sbufReadU8(src);
}
}
break;
case MSP2_COMMON_SET_SERIAL_CONFIG: {
if (dataSize < 1) {
return MSP_RESULT_ERROR;
}
unsigned count = sbufReadU8(src);
unsigned portConfigSize = (dataSize - 1) / count;
unsigned expectedPortSize = sizeof(uint8_t) + sizeof(uint32_t) + (sizeof(uint8_t) * 4);
if (portConfigSize < expectedPortSize) {
return MSP_RESULT_ERROR;
}
for (unsigned ii = 0; ii < count; ii++) {
unsigned start = sbufBytesRemaining(src);
uint8_t identifier = sbufReadU8(src);
serialPortConfig_t *portConfig = serialFindPortConfigurationMutable(identifier);
if (!portConfig) {
return MSP_RESULT_ERROR;
}
portConfig->identifier = identifier;
portConfig->functionMask = sbufReadU32(src);
portConfig->msp_baudrateIndex = sbufReadU8(src);
portConfig->gps_baudrateIndex = sbufReadU8(src);
portConfig->telemetry_baudrateIndex = sbufReadU8(src);
portConfig->blackbox_baudrateIndex = sbufReadU8(src);
// Skip unknown bytes
while (start - sbufBytesRemaining(src) < portConfigSize && sbufBytesRemaining(src)) {
sbufReadU8(src);
}
}
break;
}
#ifdef USE_LED_STRIP_STATUS_MODE
case MSP_SET_LED_COLORS:
for (int i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
hsvColor_t *color = &ledStripStatusModeConfigMutable()->colors[i];
color->h = sbufReadU16(src);
color->s = sbufReadU8(src);
color->v = sbufReadU8(src);
}
break;
#endif
#ifdef USE_LED_STRIP
case MSP_SET_LED_STRIP_CONFIG:
{
i = sbufReadU8(src);
if (i >= LED_MAX_STRIP_LENGTH || dataSize != (1 + 4)) {
return MSP_RESULT_ERROR;
}
#ifdef USE_LED_STRIP_STATUS_MODE
ledConfig_t *ledConfig = &ledStripStatusModeConfigMutable()->ledConfigs[i];
*ledConfig = sbufReadU32(src);
reevaluateLedConfig();
#else
sbufReadU32(src);
#endif
// API 1.41 - selected ledstrip_profile
if (sbufBytesRemaining(src) >= 1) {
ledStripConfigMutable()->ledstrip_profile = sbufReadU8(src);
}
}
break;
#endif
#ifdef USE_LED_STRIP_STATUS_MODE
case MSP_SET_LED_STRIP_MODECOLOR:
{
ledModeIndex_e modeIdx = sbufReadU8(src);
int funIdx = sbufReadU8(src);
int color = sbufReadU8(src);
if (!setModeColor(modeIdx, funIdx, color)) {
return MSP_RESULT_ERROR;
}
}
break;
#endif
case MSP_SET_NAME:
memset(pilotConfigMutable()->name, 0, ARRAYLEN(pilotConfig()->name));
for (unsigned int i = 0; i < MIN(MAX_NAME_LENGTH, dataSize); i++) {
pilotConfigMutable()->name[i] = sbufReadU8(src);
}
#ifdef USE_OSD
osdAnalyzeActiveElements();
#endif
break;
#ifdef USE_RTC_TIME
case MSP_SET_RTC:
{
// Use seconds and milliseconds to make senders
// easier to implement. Generating a 64 bit value
// might not be trivial in some platforms.
int32_t secs = (int32_t)sbufReadU32(src);
uint16_t millis = sbufReadU16(src);
rtcTime_t t = rtcTimeMake(secs, millis);
rtcSet(&t);
}
break;
#endif
case MSP_SET_TX_INFO:
setRssiMsp(sbufReadU8(src));
break;
#if defined(USE_BOARD_INFO)
case MSP_SET_BOARD_INFO:
if (!boardInformationIsSet()) {
uint8_t length = sbufReadU8(src);
char boardName[MAX_BOARD_NAME_LENGTH + 1];
sbufReadData(src, boardName, MIN(length, MAX_BOARD_NAME_LENGTH));
if (length > MAX_BOARD_NAME_LENGTH) {
sbufAdvance(src, length - MAX_BOARD_NAME_LENGTH);
}
boardName[length] = '\0';
length = sbufReadU8(src);
char manufacturerId[MAX_MANUFACTURER_ID_LENGTH + 1];
sbufReadData(src, manufacturerId, MIN(length, MAX_MANUFACTURER_ID_LENGTH));
if (length > MAX_MANUFACTURER_ID_LENGTH) {
sbufAdvance(src, length - MAX_MANUFACTURER_ID_LENGTH);
}
manufacturerId[length] = '\0';
setBoardName(boardName);
setManufacturerId(manufacturerId);
persistBoardInformation();
} else {
return MSP_RESULT_ERROR;
}
break;
#if defined(USE_SIGNATURE)
case MSP_SET_SIGNATURE:
if (!signatureIsSet()) {
uint8_t signature[SIGNATURE_LENGTH];
sbufReadData(src, signature, SIGNATURE_LENGTH);
setSignature(signature);
persistSignature();
} else {
return MSP_RESULT_ERROR;
}
break;
#endif
#endif // USE_BOARD_INFO
#if defined(USE_RX_BIND)
case MSP2_BETAFLIGHT_BIND:
if (!startRxBind()) {
return MSP_RESULT_ERROR;
}
break;
#endif
default:
// we do not know how to handle the (valid) message, indicate error MSP $M!
return MSP_RESULT_ERROR;
}
return MSP_RESULT_ACK;
}
static mspResult_e mspCommonProcessInCommand(mspDescriptor_t srcDesc, int16_t cmdMSP, sbuf_t *src, mspPostProcessFnPtr *mspPostProcessFn)
{
UNUSED(mspPostProcessFn);
const unsigned int dataSize = sbufBytesRemaining(src);
UNUSED(dataSize); // maybe unused due to compiler options
switch (cmdMSP) {
#ifdef USE_TRANSPONDER
case MSP_SET_TRANSPONDER_CONFIG: {
// Backward compatibility to BFC 3.1.1 is lost for this message type
uint8_t provider = sbufReadU8(src);
uint8_t bytesRemaining = dataSize - 1;
if (provider > TRANSPONDER_PROVIDER_COUNT) {
return MSP_RESULT_ERROR;
}
const uint8_t requirementIndex = provider - 1;
const uint8_t transponderDataSize = transponderRequirements[requirementIndex].dataLength;
transponderConfigMutable()->provider = provider;
if (provider == TRANSPONDER_NONE) {
break;
}
if (bytesRemaining != transponderDataSize) {
return MSP_RESULT_ERROR;
}
if (provider != transponderConfig()->provider) {
transponderStopRepeating();
}
memset(transponderConfigMutable()->data, 0, sizeof(transponderConfig()->data));
for (unsigned int i = 0; i < transponderDataSize; i++) {
transponderConfigMutable()->data[i] = sbufReadU8(src);
}
transponderUpdateData();
break;
}
#endif
case MSP_SET_VOLTAGE_METER_CONFIG: {
int8_t id = sbufReadU8(src);
//
// find and configure an ADC voltage sensor
//
int8_t voltageSensorADCIndex;
for (voltageSensorADCIndex = 0; voltageSensorADCIndex < MAX_VOLTAGE_SENSOR_ADC; voltageSensorADCIndex++) {
if (id == voltageMeterADCtoIDMap[voltageSensorADCIndex]) {
break;
}
}
if (voltageSensorADCIndex < MAX_VOLTAGE_SENSOR_ADC) {
voltageSensorADCConfigMutable(voltageSensorADCIndex)->vbatscale = sbufReadU8(src);
voltageSensorADCConfigMutable(voltageSensorADCIndex)->vbatresdivval = sbufReadU8(src);
voltageSensorADCConfigMutable(voltageSensorADCIndex)->vbatresdivmultiplier = sbufReadU8(src);
} else {
// if we had any other types of voltage sensor to configure, this is where we'd do it.
sbufReadU8(src);
sbufReadU8(src);
sbufReadU8(src);
}
break;
}
case MSP_SET_CURRENT_METER_CONFIG: {
int id = sbufReadU8(src);
switch (id) {
case CURRENT_METER_ID_BATTERY_1:
currentSensorADCConfigMutable()->scale = sbufReadU16(src);
currentSensorADCConfigMutable()->offset = sbufReadU16(src);
break;
#ifdef USE_VIRTUAL_CURRENT_METER
case CURRENT_METER_ID_VIRTUAL_1:
currentSensorVirtualConfigMutable()->scale = sbufReadU16(src);
currentSensorVirtualConfigMutable()->offset = sbufReadU16(src);
break;
#endif
default:
sbufReadU16(src);
sbufReadU16(src);
break;
}
break;
}
case MSP_SET_BATTERY_CONFIG:
batteryConfigMutable()->vbatmincellvoltage = sbufReadU8(src) * 10; // vbatlevel_warn1 in MWC2.3 GUI
batteryConfigMutable()->vbatmaxcellvoltage = sbufReadU8(src) * 10; // vbatlevel_warn2 in MWC2.3 GUI
batteryConfigMutable()->vbatwarningcellvoltage = sbufReadU8(src) * 10; // vbatlevel when buzzer starts to alert
batteryConfigMutable()->batteryCapacity = sbufReadU16(src);
batteryConfigMutable()->voltageMeterSource = sbufReadU8(src);
batteryConfigMutable()->currentMeterSource = sbufReadU8(src);
if (sbufBytesRemaining(src) >= 6) {
batteryConfigMutable()->vbatmincellvoltage = sbufReadU16(src);
batteryConfigMutable()->vbatmaxcellvoltage = sbufReadU16(src);
batteryConfigMutable()->vbatwarningcellvoltage = sbufReadU16(src);
}
break;
#if defined(USE_OSD)
case MSP_SET_OSD_CONFIG:
{
const uint8_t addr = sbufReadU8(src);
if ((int8_t)addr == -1) {
/* Set general OSD settings */
#ifdef USE_MAX7456
vcdProfileMutable()->video_system = sbufReadU8(src);
#else
sbufReadU8(src); // Skip video system
#endif
#if defined(USE_OSD)
osdConfigMutable()->units = sbufReadU8(src);
// Alarms
osdConfigMutable()->rssi_alarm = sbufReadU8(src);
osdConfigMutable()->cap_alarm = sbufReadU16(src);
sbufReadU16(src); // Skip unused (previously fly timer)
osdConfigMutable()->alt_alarm = sbufReadU16(src);
if (sbufBytesRemaining(src) >= 2) {
/* Enabled warnings */
// API < 1.41 supports only the low 16 bits
osdConfigMutable()->enabledWarnings = sbufReadU16(src);
}
if (sbufBytesRemaining(src) >= 4) {
// 32bit version of enabled warnings (API >= 1.41)
osdConfigMutable()->enabledWarnings = sbufReadU32(src);
}
if (sbufBytesRemaining(src) >= 1) {
// API >= 1.41
// selected OSD profile
#ifdef USE_OSD_PROFILES
changeOsdProfileIndex(sbufReadU8(src));
#else
sbufReadU8(src);
#endif // USE_OSD_PROFILES
}
if (sbufBytesRemaining(src) >= 1) {
// API >= 1.41
// OSD stick overlay mode
#ifdef USE_OSD_STICK_OVERLAY
osdConfigMutable()->overlay_radio_mode = sbufReadU8(src);
#else
sbufReadU8(src);
#endif // USE_OSD_STICK_OVERLAY
}
if (sbufBytesRemaining(src) >= 2) {
// API >= 1.43
// OSD camera frame element width/height
osdConfigMutable()->camera_frame_width = sbufReadU8(src);
osdConfigMutable()->camera_frame_height = sbufReadU8(src);
}
#endif
} else if ((int8_t)addr == -2) {
#if defined(USE_OSD)
// Timers
uint8_t index = sbufReadU8(src);
if (index > OSD_TIMER_COUNT) {
return MSP_RESULT_ERROR;
}
osdConfigMutable()->timers[index] = sbufReadU16(src);
#endif
return MSP_RESULT_ERROR;
} else {
#if defined(USE_OSD)
const uint16_t value = sbufReadU16(src);
/* Get screen index, 0 is post flight statistics, 1 and above are in flight OSD screens */
const uint8_t screen = (sbufBytesRemaining(src) >= 1) ? sbufReadU8(src) : 1;
if (screen == 0 && addr < OSD_STAT_COUNT) {
/* Set statistic item enable */
osdStatSetState(addr, (value != 0));
} else if (addr < OSD_ITEM_COUNT) {
/* Set element positions */
osdElementConfigMutable()->item_pos[addr] = value;
osdAnalyzeActiveElements();
} else {
return MSP_RESULT_ERROR;
}
#else
return MSP_RESULT_ERROR;
#endif
}
}
break;
case MSP_OSD_CHAR_WRITE:
{
osdCharacter_t chr;
size_t osdCharacterBytes;
uint16_t addr;
if (dataSize >= OSD_CHAR_VISIBLE_BYTES + 2) {
if (dataSize >= OSD_CHAR_BYTES + 2) {
// 16 bit address, full char with metadata
addr = sbufReadU16(src);
osdCharacterBytes = OSD_CHAR_BYTES;
} else if (dataSize >= OSD_CHAR_BYTES + 1) {
// 8 bit address, full char with metadata
addr = sbufReadU8(src);
osdCharacterBytes = OSD_CHAR_BYTES;
} else {
// 16 bit character address, only visible char bytes
addr = sbufReadU16(src);
osdCharacterBytes = OSD_CHAR_VISIBLE_BYTES;
}
} else {
// 8 bit character address, only visible char bytes
addr = sbufReadU8(src);
osdCharacterBytes = OSD_CHAR_VISIBLE_BYTES;
}
for (unsigned ii = 0; ii < MIN(osdCharacterBytes, sizeof(chr.data)); ii++) {
chr.data[ii] = sbufReadU8(src);
}
displayPort_t *osdDisplayPort = osdGetDisplayPort(NULL);
if (!osdDisplayPort) {
return MSP_RESULT_ERROR;
}
if (!displayWriteFontCharacter(osdDisplayPort, addr, &chr)) {
return MSP_RESULT_ERROR;
}
}
break;
#endif // OSD
default:
return mspProcessInCommand(srcDesc, cmdMSP, src);
}
return MSP_RESULT_ACK;
}
/*
* Returns MSP_RESULT_ACK, MSP_RESULT_ERROR or MSP_RESULT_NO_REPLY
*/
mspResult_e mspFcProcessCommand(mspDescriptor_t srcDesc, mspPacket_t *cmd, mspPacket_t *reply, mspPostProcessFnPtr *mspPostProcessFn)
{
int ret = MSP_RESULT_ACK;
sbuf_t *dst = &reply->buf;
sbuf_t *src = &cmd->buf;
const int16_t cmdMSP = cmd->cmd;
// initialize reply by default
reply->cmd = cmd->cmd;
if (mspCommonProcessOutCommand(cmdMSP, dst, mspPostProcessFn)) {
ret = MSP_RESULT_ACK;
} else if (mspProcessOutCommand(cmdMSP, dst)) {
ret = MSP_RESULT_ACK;
} else if ((ret = mspFcProcessOutCommandWithArg(srcDesc, cmdMSP, src, dst, mspPostProcessFn)) != MSP_RESULT_CMD_UNKNOWN) {
/* ret */;
} else if (cmdMSP == MSP_SET_PASSTHROUGH) {
mspFcSetPassthroughCommand(dst, src, mspPostProcessFn);
ret = MSP_RESULT_ACK;
#ifdef USE_FLASHFS
} else if (cmdMSP == MSP_DATAFLASH_READ) {
mspFcDataFlashReadCommand(dst, src);
ret = MSP_RESULT_ACK;
#endif
} else {
ret = mspCommonProcessInCommand(srcDesc, cmdMSP, src, mspPostProcessFn);
}
reply->result = ret;
return ret;
}
void mspFcProcessReply(mspPacket_t *reply)
{
sbuf_t *src = &reply->buf;
UNUSED(src); // potentially unused depending on compile options.
switch (reply->cmd) {
case MSP_ANALOG:
{
uint8_t batteryVoltage = sbufReadU8(src);
uint16_t mAhDrawn = sbufReadU16(src);
uint16_t rssi = sbufReadU16(src);
uint16_t amperage = sbufReadU16(src);
UNUSED(rssi);
UNUSED(batteryVoltage);
UNUSED(amperage);
UNUSED(mAhDrawn);
#ifdef USE_MSP_CURRENT_METER
currentMeterMSPSet(amperage, mAhDrawn);
#endif
}
break;
}
}
void mspInit(void)
{
initActiveBoxIds();
}